Spring中的各种Utils(三):AopUtils详解

在本节中,我们介绍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的知识。


  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值