AopUtils详解

网友投稿 291 2022-11-06

AopUtils详解

在本节中,我们介绍Spring非常重要的一个工具类AopUtils。稍微注意一点就是AopUtils主要是针对于AOP过程中的一些工具方法,还有一个叫做AopProxyUtils,这个工具类是针对怎么去做Proxy的工具方法;

SpringAOP JavaConfig案例准备

因为是作为AOP相关工具类,所以我们需要准备一些基本的测试案例,或者先对Spring AOP测试做一个准备。在这里我们选择基于JavaConfig的配置方式,来准备一个AOP测试案例:

首先使用maven引入依赖,因为只是测试AOP,所以只需要引入Spring的一些基础包即可:

不出意外,首先先准备一个业务接口及其实现:

接下来创建一个用于增强的类,我们使用Annotation的方式完成:

使用@Aspect将该类变成一个Spring能够识别的切面提供类;标记@Component,能够让Spring配置对象扫描到;

接下来创建主配置类:

注意:

1,@Configuration代表这是Spring的java配置类;

2,@ComponentScan代表自动扫描组件,所以Advice类会自动加载;

3,@EnableAspectJAutoProxy,如果要使用annotation的方式完成AOP,必须要在主配置类上添加这个标签,相当于XML中配置的

4,提供被代理的目标对象,EmployeeServiceImpl;

至此,基本的AOP测试环境搭建完成,我们后面的AopUtils测试会基于这个环境进行测试。

判定是否是代理对象相关

1,boolean isJdkDynamicProxy(Object object)

第一个方法,也是使用最多的方法,判定一个对象,是否是代理对象;

我们先来做一个简单测试:

注意:

1,这是一个标准的Spring测试;@ContextConfiguration标签中确定加载的主配置类;

2,测试service应该是代理对象,因为IEmployeeService匹配Advice中定义的前置增强切入点:execution(cn.wolfcode.spring.utilstest.Service.*(..))

这个方法的实现非常简单:

可以看到,做了三个判断:

1,判断是否是SpringProxy类型,Spring中的所有的代理对象,都会实现这个接口,这个接口是一个标记接口:

2,使用JDK的Proxy的isProxyClass方法,判定该对象是否是JDK的代理实现,即是否是基于接口代理;

3,使用ClassUtils的isCglibProxyClass方法判断该对象是否是cglib代理实现;ClassUtils也是Spring中针对反射提供的非常有用的工具类,后面会介绍;

4,为什么要这样判断?因为Spring需要判定这个对象是否是由spring完成的AOP代理;

boolean isJdkDynamicProxy(Object object)

第二个方法,判断是否是JDK代理对象;

简单的测试:

该方法功能很简单,实现也很简单:

类似的还有一个方法:

boolean isCglibProxy(Object object),很明显该方法用于判定一个类是否是cglib完成的代理;我们就不具体测试了;这个方法的实现应该也能很容易想到:

Class getTargetClass(Object candidate)

这个方法用于获取对象的真实类型;为了完成方法测试,我们增加一个没有接口的组件:

在Advice类中增加一个针对MyComponent的增强:

完成测试代码:

输出:

class cn.wolfcode.springboot.utilstest.MyComponentEnhancerBySpringCGLIB238e719a

class cn.wolfcode.springboot.utilstest.MyComponent

很好理解,因为MyComponent是没有接口的,所以使用cglib完成代理,那么component的类型肯定是代理之后的类型,那我们要想得到真实类型,使用getTargetClass方法完成。

其实该方法的实现也非常简单:

首先,判断该类是否实现了TargetClassAware接口,该接口是SpringAOP代理类去实现的接口,比如通过ProxyFactoryBean实现的代理对象,都会实现这个接口。这个接口中定义了getTargetClass方法,用于获取动态代理对象的原始类型;

如果没有实现TargetClassAware接口,接着判定,如果是Cglib的动态代理实现,则直接返回该类的父类,我们知道,cglib就是通过继承来完成动态代理的,所以代理对象的父类即为真实对象类型;否则,剩下的就是JDK完成的代理,只需要得到本身类型即可,因为这就是接口的类型;

boolean isEqualsMethod(Method method)

boolean isHashCodeMethod(Method method)

boolean isToStringMethod(Method method)

boolean isFinalizeMethod(Method method)

这四个方法很简单,用于判定给定的方法是否是equals方法,hashCode方法,toString方法或者finalize方法。因为在动态代理的时候,都是针对类级别的代理,加入匹配的切入点是 .(..),那么是否是该类所有方法都会被代理?但是Object本身的equals,hashCode,toString,finalize方法,都是需要剔除在代理方法之外的,这四个方法就是来辅助做判断的。如果有兴趣的童鞋可以看一下JdkDynamicAopProxy类中的invoke方法中的相关实现;

Method getMostSpecificMethod(Method method, Class targetClass)

该方法是一个有趣的方法,他能从代理对象上的一个方法,找到真实对象上对应的方法。举个例子,MyComponent代理之后的对象上的someLogic方法,肯定是属于cglib代理之后的类上的method,使用这个method是没法去执行目标MyComponent的someLogic方法,这种情况下,就可以使用getMostSpecificMethod,找到真实对象上的someLogic方法,并执行真实方法。来写一个测试验证:

打印结果:

代码解释:

m是通过代理对象的class得到的someLogic方法,从打印结果可以非常明显的看出,这个method隶属于MyComponentEnhancerBySpringCGLIB238e719a;那么使用这个方法肯定是没法去执行真实MyComponent的someLogic方法,所以我们执行

传入的方法是代理类的方法,通过targetClass得到真实类型,返回的方法就是MyComponent类上的方法了。

boolean canApply(Pointcut pc, Class targetClass):判断一个切入点能否匹配一个指定的类型;

boolean canApply(Pointcut pc, Class targetClass, boolean hasIntroductions):判断一个切入点能否匹配一个指定的类型,是否支持引入匹配;

boolean canApply(Advisor advisor, Class targetClass):判断一个建议(advisor)能否匹配一个指定的类型;

boolean canApply(Advisor advisor, Class targetClass, boolean hasIntroductions):判断一个建议(advisor)能否匹配一个指定的类型,是否支持引入匹配;

List findAdvisorsThatCanApply(List candidateAdvisors, Class clazz):在一组建议(advisor)中,返回能够匹配指定类型的建议者列表;

这两组方法也算AOP核心方法了,用于判断一个切入点是否匹配一个类型;

我们先做一个简单的测试:

可以看到,我们创建了一个AspectJExpressionPointcut,看名字也能明白就是使用AspectJ的表达式设置的切入点,去分别测试MyComponent和IEmployeeService,结果和我们预期相同。

两组方法中都有一个hasIntroductions的boolean类型参数,这个参数是用于指定,判定是否包含introduction,如果不包含introduction,匹配会更加的有效;

那什么是introduction?字面意思是引入,其实这是一个AOP概念,简单理解就是使用AOP来改变对象本来的行为,比如使用AOP为一个类动态的添加一个父类,或者额外实现一个接口,甚至增加一个字段等等操作。这种操作使用的场景很有限,但是感觉很牛X,我们就来简单看一个例子,体会一下introduction的概念:

我们额外准备一个接口和实现:

我们的目标是,在不修改IEmployeeService的前提下,为EmployeeServiceImpl额外添加IAddition接口,并使用AdditionImpl作为其实现。意思就是,当我得到EmployeeServiceImpl对象的时候,我可以强转为IAddition接口,并执行additional方法,打印出”out additional…”;

要实现这个需求非常方便,只需要在Advice中添加:

简单解释一下这个代码:

1,public IAddition addition字段,代表我要引入的接口;

2,@DeclareParents标签说明这个接口要引入的目标,其中value=”cn.wolfcode.springboot.utilstest.IEmployeeService+”代表要引入到的目标类是IEmployeeService及其所有子类;

3,defaultImpl代表接口默认的实现,即我们的AdditionImpl类;

到这里,一个基本的introduction就已经配置完成;

接下来看测试代码:

执行测试,输出:

Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)

执行一个目标方法;这个方法其实就是method.invoke方法的更完善的方法,指在target对象上,使用args参数列表执行method;

我们可以来看看代码的实现:

代码很简单,就是在默认的反射调用之上,对异常做了一些包装,其中使用到了ReflectionUtils,这也是Spring中非常有用的一个工具类,后面介绍;

小结

本节介绍了AopUtils,其中也想补充一下Spring中使用JavaConfig实现AOP和introduction的知识。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:英创信息技术JAVA操作英创主板I2C接口简介
下一篇:springcloud整合gateway实现网关的示例代码
相关文章

 发表评论

暂时没有评论,来抢沙发吧~