在本节中,我们介绍Spring非常重要的一个工具类AopUtils。稍微注意一点就是AopUtils主要是针对于AOP过程中的一些工具方法,还有一个叫做AopProxyUtils,这个工具类是针对怎么去做Proxy的工具方法;
SpringAOP JavaConfig案例准备
因为是作为AOP相关工具类,所以我们需要准备一些基本的测试案例,或者先对Spring AOP测试做一个准备。在这里我们选择基于JavaConfig的配置方式,来准备一个AOP测试案例:
首先使用maven引入依赖,因为只是测试AOP,所以只需要引入Spring的一些基础包即可:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
</dependency>
<!-- spring -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${org.springframework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
</dependency>
<!-- aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.7.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.7.4</version>
</dependency>
<!-- cglib -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
不出意外,首先先准备一个业务接口及其实现:
public interface IEmployeeService {
//业务接口
void someLogic();
}
public class EmployeeServiceImpl implements IEmployeeService {
@Override
public void someLogic() {
System.out.println("do something");
}
}
接下来创建一个用于增强的类,我们使用Annotation的方式完成:
@Aspect
@Component
public class Advice {
@Before("execution(* cn.wolfcode.spring.utilstest.*Service.*(..))")
public void advisor() {
System.out.println("do before");
}
}
使用@Aspect将该类变成一个Spring能够识别的切面提供类;标记@Component,能够让Spring配置对象扫描到;
接下来创建主配置类:
@Configuration
@ComponentScan
@EnableAspectJAutoProxy
public class AopConfig {
@Bean
public IEmployeeService target() {
return new EmployeeServiceImpl();
}
}
注意:
1,@Configuration代表这是Spring的java配置类;
2,@ComponentScan代表自动扫描组件,所以Advice类会自动加载;
3,@EnableAspectJAutoProxy,如果要使用annotation的方式完成AOP,必须要在主配置类上添加这个标签,相当于XML中配置的
<aop:aspectj-autoproxy/>
4,提供被代理的目标对象,EmployeeServiceImpl;
至此,基本的AOP测试环境搭建完成,我们后面的AopUtils测试会基于这个环境进行测试。
判定是否是代理对象相关
1,boolean isJdkDynamicProxy(Object object)
第一个方法,也是使用最多的方法,判定一个对象,是否是代理对象;
我们先来做一个简单测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = AopConfig.class)
public class AopUtilsTest {
@Autowired
private IEmployeeService service;
@Test
public void testIsAop() {
assertTrue(AopUtils.isAopProxy(service));
}
}
注意:
1,这是一个标准的Spring测试;@ContextConfiguration标签中确定加载的主配置类;
2,测试service应该是代理对象,因为IEmployeeService匹配Advice中定义的前置增强切入点:execution(cn.wolfcode.spring.utilstest.Service.*(..))
这个方法的实现非常简单:
public static boolean isAopProxy(Object object) {
return (object instanceof SpringProxy &&
(Proxy.isProxyClass(object.getClass())||
ClassUtils.isCglibProxyClass(object.getClass())));
}
可以看到,做了三个判断:
1,判断是否是SpringProxy类型,Spring中的所有的代理对象,都会实现这个接口,这个接口是一个标记接口:
public interface SpringProxy {
}
2,使用JDK的Proxy的isProxyClass方法,判定该对象是否是JDK的代理实现,即是否是基于接口代理;
3,使用ClassUtils的isCglibProxyClass方法判断该对象是否是cglib代理实现;ClassUtils也是Spring中针对反射提供的非常有用的工具类,后面会介绍;
4,为什么要这样判断?因为Spring需要判定这个对象是否是由spring完成的AOP代理;
boolean isJdkDynamicProxy(Object object)
第二个方法,判断是否是JDK代理对象;
简单的测试:
@Test
public void testIsJdk(){
assertTrue(AopUtils.isJdkDynamicProxy(service));
}
该方法功能很简单,实现也很简单:
public static boolean isJdkDynamicProxy(Object object) {
return (object instanceof SpringProxy && Proxy.isProxyClass(object.getClass()));
}
类似的还有一个方法:
boolean isCglibProxy(Object object),很明显该方法用于判定一个类是否是cglib完成的代理;我们就不具体测试了;这个方法的实现应该也能很容易想到:
public static boolean isCglibProxy(Object object) {
return (object instanceof SpringProxy && ClassUtils.isCglibProxy(object));
}
Class<?> getTargetClass(Object candidate)
这个方法用于获取对象的真实类型;为了完成方法测试,我们增加一个没有接口的组件:
@Component
public class MyComponent {
public void someLogic() {
System.out.println("component");
}
}
在Advice类中增加一个针对MyComponent的增强:
@Before("execution(* cn.wolfcode.springboot.utilstest.MyComponent.*(..))")
public void advisor2() {
System.out.println("do before");
}
完成测试代码:
@Test
public void testTargetClass() {
System.out.println(component.getClass());
System.out.println(AopUtils.getTargetClass(component));
}
输出:
class cn.wolfcode.springboot.utilstest.MyComponentEnhancerBySpringCGLIB238e719a
class cn.wolfcode.springboot.utilstest.MyComponent
很好理解,因为MyComponent是没有接口的,所以使用cglib完成代理,那么component的类型肯定是代理之后的类型,那我们要想得到真实类型,使用getTargetClass方法完成。
其实该方法的实现也非常简单:
public static Class<?> getTargetClass(Object candidate) {
Assert.notNull(candidate, "Candidate object must not be null");
Class<?> result = null;
if (candidate instanceof TargetClassAware) {
result = ((TargetClassAware) candidate).getTargetClass();
}
if (result == null) {
result = (isCglibProxy(candidate) ? candidate.getClass().getSuperclass()
: candidate.getClass());
}
return result;
}
首先,判断该类是否实现了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方法,并执行真实方法。来写一个测试验证:
@Test
public void testMostSpecificMethod() throws Exception{
Method m=component.getClass().getMethod("someLogic");
System.out.println(m);
Method om=AopUtils.getMostSpecificMethod(m,
AopUtils.getTargetClass(component));
System.out.println(om);
}
打印结果:
public final void cn.wolfcode.springboot.utilstest.MyComponent$$
EnhancerBySpringCGLIB$$238e719a.someLogic()
public void cn.wolfcode.springboot.utilstest.MyComponent.someLogic()
代码解释:
m是通过代理对象的class得到的someLogic方法,从打印结果可以非常明显的看出,这个method隶属于MyComponentEnhancerBySpringCGLIB238e719a;那么使用这个方法肯定是没法去执行真实MyComponent的someLogic方法,所以我们执行
Method om=AopUtils.getMostSpecificMethod(m,
AopUtils.getTargetClass(component));
传入的方法是代理类的方法,通过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<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz):在一组建议(advisor)中,返回能够匹配指定类型的建议者列表;
这两组方法也算AOP核心方法了,用于判断一个切入点是否匹配一个类型;
我们先做一个简单的测试:
@Test
public void testApply(){
AspectJExpressionPointcut pc=new AspectJExpressionPointcut();
pc.setExpression("execution(* cn.wolfcode.springboot.utilstest.MyComponent.*(..))");
assertTrue(AopUtils.canApply(pc, MyComponent.class));
assertFalse(AopUtils.canApply(pc, IEmployeeService.class));
}
可以看到,我们创建了一个AspectJExpressionPointcut,看名字也能明白就是使用AspectJ的表达式设置的切入点,去分别测试MyComponent和IEmployeeService,结果和我们预期相同。
两组方法中都有一个hasIntroductions的boolean类型参数,这个参数是用于指定,判定是否包含introduction,如果不包含introduction,匹配会更加的有效;
那什么是introduction?字面意思是引入,其实这是一个AOP概念,简单理解就是使用AOP来改变对象本来的行为,比如使用AOP为一个类动态的添加一个父类,或者额外实现一个接口,甚至增加一个字段等等操作。这种操作使用的场景很有限,但是感觉很牛X,我们就来简单看一个例子,体会一下introduction的概念:
我们额外准备一个接口和实现:
public interface IAddition {
void addtional();
}
public class AdditionImpl implements IAddition {
@Override
public void addtional() {
System.out.println("out additional...");
}
}
我们的目标是,在不修改IEmployeeService的前提下,为EmployeeServiceImpl额外添加IAddition接口,并使用AdditionImpl作为其实现。意思就是,当我得到EmployeeServiceImpl对象的时候,我可以强转为IAddition接口,并执行additional方法,打印出”out additional…”;
要实现这个需求非常方便,只需要在Advice中添加:
@DeclareParents(value = "cn.wolfcode.springboot.utilstest.IEmployeeService+",
defaultImpl = AdditionImpl.class)
public IAddition addition;
简单解释一下这个代码:
1,public IAddition addition字段,代表我要引入的接口;
2,@DeclareParents标签说明这个接口要引入的目标,其中value=”cn.wolfcode.springboot.utilstest.IEmployeeService+”代表要引入到的目标类是IEmployeeService及其所有子类;
3,defaultImpl代表接口默认的实现,即我们的AdditionImpl类;
到这里,一个基本的introduction就已经配置完成;
接下来看测试代码:
@Test
public void testIntroductions(){
((IAddition)service).addtional();
}
执行测试,输出:
out additional...
Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
执行一个目标方法;这个方法其实就是method.invoke方法的更完善的方法,指在target对象上,使用args参数列表执行method;
我们可以来看看代码的实现:
public static Object invokeJoinpointUsingReflection(Object target, Method method, Object[] args)
throws Throwable {
// 直接使用反射执行方法.
try {
ReflectionUtils.makeAccessible(method);
return method.invoke(target, args);
}
catch (InvocationTargetException ex) {
// 所有执行过程中的异常都需要包装之后抛回给调用者.
// We must rethrow it. The client won't see the interceptor.
throw ex.getTargetException();
}
catch (IllegalArgumentException ex) {
throw new AopInvocationException("AOP configuration seems to be invalid: tried calling method [" +
method + "] on target [" + target + "]", ex);
}
catch (IllegalAccessException ex) {
throw new AopInvocationException("Could not access method [" + method + "]", ex);
}
}
代码很简单,就是在默认的反射调用之上,对异常做了一些包装,其中使用到了ReflectionUtils,这也是Spring中非常有用的一个工具类,后面介绍;
小结
本节介绍了AopUtils,其中也想补充一下Spring中使用JavaConfig实现AOP和introduction的知识。