自研Spring AOP2.0(三)
声明
本篇文章除部分引用外,均为原创内容,如有雷同纯属巧合,引用转载请附上原文链接与声明。
阅读条件
读本篇文章需掌握java基础知识,熟悉代理模式,了解ClassLoader加载类的流程,掌握注解的使用方式,灵活运用反射,阅读自研Spring IOC(一),阅读自研Spring AOP(二),Spring AOP切点定义
注意
本文若包含部分下载内容,本着一站式阅读的想法,本站提供其对应软件的直接下载方式,但是由于带宽原因下载缓慢是必然的,建立读者去相关官网进行下载,若某些软件禁止三方传播,请在主页上通过联系作者的方式将相关项目进行取消。
相关文章
文章大纲
- 问题分析
- 改进方案
- 使用举例
简述
项目的github源码地址传送门点此,该篇文章对应的分支为aop-2.0
该篇文章将在自研Spring AOP(二)基础上,在切点定义上新增对于Aspect语法树的支持,以达到灵活的切点定义能力,事实上Spring AOP也是采用的该语法树来对切点进行的描述,所以从理论上讲,本框架所支持的AOP切点定义与Spring AOP所支持的相同。
问题分析
在自研Spring AOP(二)中,完成了对于AOP功能的实现,主要是通过动态代理来进行的实现,具体有以下流程
- 定义切点
- 识别切点位置
- 对切点生效处创建对应的动态代理对象
- IOC过程中优先扫描代理对象缓存池
上述流程确实是体现了AOP的横向织入概念,但是通过@Aspect定义切面时,所声明的切点只能是某注解标记的类,该切点粒度较大,也就意味着如果一个类在某切点的效果范围内时,那么该类的所有方法均会被代理。但Spring AOP中通过AspectJ语法树做到了对方法级别上的切点定义,即切点可以声明只对哪些方法生效,而在自研Spring AOP(二)可以理解为只能对目标代理类的全部方法生效。所以为了解决该问题,在本文中将引入AspectJ语法树以支持同样粒度的切点定义,需要注意的是,本文只是对AOP切点的增加,并未在AOP的概念上做任何延展,仅是提供一个升级版的AOP实现。
改进方案
引入AspectJ语法树
在spring-aop模块中的pom.xml中添加以下依赖,该包有AspectJ语法树支持
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
<scope>runtime</scope>
</dependency>
修改@Aspect注解
在aop-1.0分支的@Aspect注解,如下,可以看到value()只能声明为对某一类注解标记的类生效
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
/**
* 对哪一类注解进行切面织入
*
* @return
*/
Class<? extends Annotation> value();
/**
* 该切面类的执行顺序,按照从小到大执行
*
* @return
*/
int order() default Integer.MAX_VALUE;
}
修改后的@Aspect注解,如下,可以看到value改为了pointCut,为String类型,保持与Spring AOP中的定义相同
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
/**
* 该切面的切点定义,这里引用了AspectJ语法树,使用官方Spring aop相同
*
* @return
*/
String pointcut();
/**
* 该切面类的执行顺序,按照从小到大执行
*
* @return
*/
int order() default Integer.MAX_VALUE;
}
修改 DefaultAspect
在aop-1.0分支中的DefaultAspect类,如下,仅声明了被代理方法执行前的方法和被代理方法执行后的方法
public abstract class DefaultAspect {
public void before() throws Exception {
}
public void afterReturn() throws Exception {
}
}
而现在由于切点的定义发生了改变,所以需要新增对于匹配类和匹配方法的判断方法,当切点匹配到某个类时,则需要创建该类的代理类。若切点匹配到指定方法时,则说明该代理类的此方法被执行时需要被代理,并不是所有代理类的方法都会被代理执行,只有与切点相互匹配时(在切点的管理范围内),才需要执行代理逻辑。
在新的切面中,新增了类变量PARSER,这是AspectJ提供的语法支持,可以在初始化时指定需要支持的原语种类,这里选择的是支持所有原语,即execution表达式,within表达式...等等。新增了expession成员变量,改变量是用来判断切点定义是否匹配指定类(isMathcClass方法),是否匹配指定方法(isMatchMethod),需要注意的是expression在对指定类判断是否匹配时只对within语法起效,其他类型表达式均返回true,而对指定方法判断是否匹配时则可以做到精准匹配,所以在执行时需要在相关代理类中对切面进行进一步筛选。
修改后的DefaultAspect类,在aop-2.0分支中,如下
public abstract class DefaultAspect {
private static final PointcutParser PARSER = PointcutParser.getPointcutParserSupportingAllPrimitivesAndUsingContextClassloaderForResolution();
private PointcutExpression expression;
public DefaultAspect() {
this.expression = PARSER.parsePointcutExpression(this.getClass().getAnnotation(Aspect.class).pointcut());
}
/**
* 被代理对象方法执行前的逻辑
*
* @throws Exception
*/
public abstract void before() throws Exception;
/**
* 被代理对象方法执行后的逻辑
*
* @throws Exception
*/
public abstract void afterReturn() throws Exception;
/**
* 该切面是否匹配指定class对象
*
* @param targetClass
* @return
*/
public boolean isMatchClass(Class<?> targetClass) {
return expression.couldMatchJoinPointsInType(targetClass);
}
/**
* 该切面是否匹配指定的方法
*
* @param method
* @return
*/
public boolean isMatchMethod(Method method) {
return expression.matchesMethodExecution(method).alwaysMatches();
}
}
修改AspectChainMethodInterceptor
根据上文,在执行时,对切面进行过滤,修改后如下
public class AspectChainMethodInterceptor implements MethodInterceptor {
private Object target;
private List<? extends DefaultAspect> sortedAspectList;
public AspectChainMethodInterceptor(Object target, List<? extends DefaultAspect> aspectList) {
this.target = target;
this.sortedAspectList = aspectList.stream()
.sorted(Comparator.comparingInt(c -> c.getClass().getAnnotation(Aspect.class).order()))
.collect(Collectors.toList());
}
@Override
public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
List<? extends DefaultAspect> filterAspectList = filterAspectList(method, sortedAspectList);
for (DefaultAspect defaultAspect : filterAspectList) {
defaultAspect.before();
}
Object returnValue = method.invoke(target, args);
List<? extends DefaultAspect> reverseOrderAspectList = filterAspectList.stream()
.sorted(Comparator.comparingInt(c -> -1 * c.getClass().getAnnotation(Aspect.class).order()))
.collect(Collectors.toList());
for (DefaultAspect defaultAspect : reverseOrderAspectList) {
defaultAspect.afterReturn();
}
return returnValue;
}
private List<? extends DefaultAspect> filterAspectList(Method method, List<? extends DefaultAspect> sortedAspectList) {
return sortedAspectList.stream()
.filter(v -> v.isMatchMethod(method))
.collect(Collectors.toList());
}
}
修改AspectChainInvocationHandler
同理修改AspectChainMethodInterceptor,修改后如下
public class AspectChainInvocationHandler implements InvocationHandler {
private Object target;
private List<? extends DefaultAspect> sortedAspectList;
public AspectChainInvocationHandler(Object target, List<? extends DefaultAspect> aspectList) {
this.target = target;
this.sortedAspectList = aspectList.stream()
.sorted(Comparator.comparingInt(c -> c.getClass().getAnnotation(Aspect.class).order()))
.collect(Collectors.toList());
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
List<? extends DefaultAspect> filterAspectList = filterAspectList(method, sortedAspectList);
for (DefaultAspect defaultAspect : filterAspectList) {
defaultAspect.before();
}
Object returnValue = method.invoke(target, args);
List<? extends DefaultAspect> reverseOrderAspectList = filterAspectList.stream()
.sorted(Comparator.comparingInt(c -> -1 * c.getClass().getAnnotation(Aspect.class).order()))
.collect(Collectors.toList());
for (DefaultAspect defaultAspect : reverseOrderAspectList) {
defaultAspect.afterReturn();
}
return returnValue;
}
private List<? extends DefaultAspect> filterAspectList(Method method, List<? extends DefaultAspect> sortedAspectList) {
return sortedAspectList.stream()
.filter(v -> v.isMatchMethod(method))
.collect(Collectors.toList());
}
}
修改容器的动态代理创建流程
这里只需要修改容器判断一个类是否需要创建代理的逻辑,即判断bean是否在某些切面的切点定义范围内,修改后的beanContainer如下,可以看到getAspectByClass发生了改变,通过isMatchClass方法来进行过滤,如果存在切面,则说明需要创建代理类来融合切面和被代理类的逻辑。
public class BeanContainer {
private static Map<Class<?>, Object> container = new ConcurrentHashMap<>();
private static Map<Class<?>, Object> proxyBeanContainer = new ConcurrentHashMap<>();
private static List<Class<? extends Annotation>> beanAnnotations = Arrays.asList(Controller.class, Service.class, Repository.class, Component.class, Aspect.class);
private static Set<Class<?>> getClasses() {
// 未作改变
}
public static Map<Class<?>, Object> getContainer() {
// 未作改变
}
public static <T> T getBean(Class<T> clazz) {
// 未作改变
}
public static void addBean(Class<?> clazz, Object bean) {
// 未作改变
}
public static void addProxyBean(Class<?> clazz, Object bean) {
// 未作改变
}
public static Object remove(Class<?> clazz) {
// 未作改变
}
public static void doIoc() {
// 未作改变
}
public static void doAop() {
// 未作改变
}
public static void doBeanScan(String basePackage) {
// 未作改变
}
private static Object createProxy(Map.Entry<Class<?>, Object> entry) {
// 未作改变
}
/**
* 判断该类是否存在其相应的切面配置
* @param clazz
* @return 该类是否存在其相关的切面配置
*/
private static boolean needProxy(Class<?> clazz) {
return !clazz.isAnnotationPresent(Aspect.class) && !CollectionUtil.isNullOrEmpty(getAspectByClass(clazz));
}
public static Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation) {
// 未作改变
}
/**
* 获取class需要织入的切面类
* @param clazz
* @return
*/
public static List<? extends DefaultAspect> getAspectByClass(Class<?> clazz) {
return getBeansByAnnotation(Aspect.class).stream()
.map(v -> (DefaultAspect) v)
.filter(v -> v.isMatchClass(clazz))
.collect(Collectors.toList());
}
private static void extractClassPath(String basePackage, String path, Set<Class<?>> classes) {
// 未作改变
}
private static boolean needLoad(Class<?> clazz) {
// 未作改变
}
}
使用举例
相关代码请见aop-2.0分支的demo模块
创建切面
@Aspect(pointcut = "within(tech.chenx.service.*)", order = 2)
public class CommAspect extends DefaultAspect {
@Override
public void before() throws Exception {
log.info("CommAspect.before() was invoke at [{}]", System.currentTimeMillis());
}
@Override
public void afterReturn() throws Exception {
log.info("CommAspect.afterReturn() was invoke at [{}]", System.currentTimeMillis());
}
}
@Aspect(pointcut = "execution(* tech.chenx..*.*(..))", order = 1)
public class LogAspect extends DefaultAspect {
@Override
public void before() throws Exception {
log.info("LogAspect.before() was invoke at [{}]", System.currentTimeMillis());
}
@Override
public void afterReturn() throws Exception {
log.info("LogAspect.afterReturn() was invoke at [{}]", System.currentTimeMillis());
}
}
创建Controller
@Controller
public class ExampleController{
@Autowired
private IExampleService service;
@Override
public String show() {
log.info("ExampleController.show()");
service.show();
return "controller show method";
}
}
创建Service
public interface IExampleService {
void show();
}
@Service
public class ExampleServiceImpl implements IExampleService {
@Autowired
private IExampleRepository repository;
@Override
public void show() {
log.info("ExampleServiceImpl.show()");
repository.show();
}
}
创建Repository
@Repository
public class ExampleRepository implements IExampleRepository {
@Override
public void show() {
log.info("ExampleRepository.show()");
}
}
执行NobitaApplication
执行结果看出,LogAspect切面生效,对每一个类的方法均进行了拦截,而CommAspect仅仅拦截了Service的请求,且在LogAspect之后执行。结果正确。