Spring Aop

目录

Spring AOP

AOP实现模式

动态代理

  1. jdk
  2. cglib

AspectJ静态代理

  1. aspectj compiler

AOP失效

动态代理模式下

@Service
public class ApplicationService {
    @Autowired
    private DomainService domainService;
  
  	/**
     * 可以通过如下方式获取代理对象持有的原始对象
     * @return
     * @throws Exception
     */
    public DomainService getTargetDomainService() throws Exception {
        return (DomainService) ((Advised)domainService).getTargetSource().getTarget();
    }

    public void callTestA() {
        //testA事务生效,但testB事务失效
        domainService.testA();
    }

    public void callTestB() {
        //testB事务生效
        domainService.testB();
    }
}

@Service
public class DomainService {

    @Transactional
    public void testA() {
        //testB事务失效:调用者是被代理对象而非增强后的对象
        testB();
        //do business A
    }

    @Transactional
    public void testB() {
        //do business B
    }
}

生效条件:

被调用方法的当前调用者必须为spring增强后的代理对象

失效原因:

被调用方法的当前调用者不是spring增强后的代理对象而是被代理对象

解决方案:

  1. 从容器中获取代理对象进行调用
  2. 采用AspectJ静态代理模式进行静态织入

根本原因:

cglib动态代理实现方式:

  1. 代理对象要求被代理对象有无参构造器以便初始化,而被代理对象可以有复杂的初始化逻辑
  2. 代理对象需要持有被代理对象的实例,并使用代理对象进行代理方法的转发,因为代理对象虽然是子类,但是其是由无参构造起初始化而来,可能不具备被代理对象的执行前提(参数注入、初始化逻辑等),因此不能简单的通过super进行调用
//cglib生成的代理类大致结构
public class DomainServiceProxy extends DomainService {
    private DomainService target;

    public DomainServiceProxy() {
    }
    
    public void setTarget(DomainService target) {
        this.target = target;
    }

    @Override
    public void testA() {
        //before advice
        target.testA();
        //after advice
    }

    @Override
    public void testB() {
        //before advice
        target.testB();
        //after advice
    }
}

静态代理实现方式:

方法原地增强

总结:

spring动态代理由于采用的是继承被代理对象并持有被代理对象的实例,调用时使用该实例进行调用而非使用super进行调用

静态代理采用原地增强的方式,因此内部调用不会出现失效的情况

AOP优先级

  1. 使用@Aspectj注解时可以使用@Order()注解
  2. 使用编程式AOP时可通过AbstractPointcutAdvisor提供的setOrder()进行设置

AOP应用形式

注解式

@Aspect
@Component
public class TimeAspect {

    /**
     * spring aop没有aspectj的只配置注解会调用两次bug
     *
     * @param executionTime
     */
    @Pointcut("@annotation(executionTime)")
    public void callAt(ExecutionTime executionTime) {
    }


    @Around("callAt(executionTime)")
    public Object around(ProceedingJoinPoint pjp, ExecutionTime executionTime) throws Throwable {
        long start = System.nanoTime();
        Object proceed = pjp.proceed();
        long end = System.nanoTime();
        System.out.println(String.format("[%s] method [%s] execution time: %s %s",
                Thread.currentThread().getName(),
                pjp.getSignature().getName(),
                Duration.of(end - start, ChronoUnit.NANOS).get(executionTime.unit()),
                executionTime.unit().name()));
        return proceed;
    }
}

@Service
public class TestService {

    @ExecutionTime(unit = ChronoUnit.NANOS)
    public void execute() throws InterruptedException {
        long timeout = ThreadLocalRandom.current().nextLong(600);
        TimeUnit.MILLISECONDS.sleep(timeout);
        //内部调用AOP失效
        innerCall();
    }

    @ExecutionTime(unit = ChronoUnit.NANOS)
    public void innerCall() throws InterruptedException {
        long timeout = ThreadLocalRandom.current().nextLong(500);
        TimeUnit.MILLISECONDS.sleep(timeout);
    }
}

编程式

@Configuration
@ConditionalOnProperty(value = "aop.time.expression")
@Role(value = BeanDefinition.ROLE_INFRASTRUCTURE)
public class TimeAspectConfiguration {

    @Value("${aop.time.expression:}")
    private String expression;

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    public PointcutAdvisor aspectJTimePointcutAdvisor() {
        AspectJExpressionPointcutAdvisor pointcutAdvisor = new AspectJExpressionPointcutAdvisor();
        pointcutAdvisor.setExpression(expression);
        pointcutAdvisor.setAdvice(timeAroundAdvice());
        //先进后出
        pointcutAdvisor.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
        return pointcutAdvisor;
    }

    @Bean
    public TimeAroundAdvice timeAroundAdvice() {
        return new TimeAroundAdvice();
    }

    private static class TimeAroundAdvice implements MethodInterceptor {

        private static Logger logger = LoggerFactory.getLogger(TimeAroundAdvice.class);

        @Override
        public Object invoke(MethodInvocation invocation) throws Throwable {
            logger.info("before advice");
            long start = System.nanoTime();
            Object proceed = invocation.proceed();
            long end = System.nanoTime();
            ExecutionTime anno = invocation.getMethod().getAnnotation(ExecutionTime.class);
            logger.info("method {} execution time: {} {}",
                    invocation.getMethod().getName(),
                    Duration.of(end - start, ChronoUnit.NANOS).get(anno.unit()),
                    anno.unit().name());
            return proceed;
        }
    }
}

优缺点比较

  1. 注解式实现简单直观方便,但是切点规则无法动态调整(更推荐注解形式)
  2. 编程式实现较为复杂,但是可配置能力较强

AOP代理对象生成

普通AOP代理对象

一个基于 CGLIB 的 AOP 动态代理 bean,真实的执行逻辑是在 DynamicAdvisedInterceptor 中

Spring 在创建一个普通 Bean 之后,对其包装并生成动态代理对象都是后置的举动(生命周期活动及依赖注入已完成),故会先生成真实类的实例 bean,再动态创建动态代理 bean,在动态代理 bean 中,会持有真实的 bean 的实例。真实bean的实例才是一个满足了spring依赖及生命周期的全功能对象,代理对象仅执行代理功能并未注入相关依赖以及完成初始化工作。

因为 AOP 动态代理的方法真实调用,会使用真实被代理对象实例进行方法调用,故在实例方法中通过 this 获取的都是被代理的真实对象的实例,而不是代理对象自身

配置类代理对象

配置类代理对象会利用cglib生成class并设置到BeanDefinition 中的 beanClass 属性中,在 BeanDefinition 初始化时会自动初始化子类,并未创建原始对象。

配置类的代理

配置类如果开启代理@Configuration(proxyBeanMethods = true)其内部调用为了始终返回单例Bean会触发代理。

其原因是配置类是采用代理对象调用代理方法而不是像普通代理类采用原始对象调用代理方法

/**
 * 默认代理内部方法的调用,保证单例Bean
 */
@Configuration(proxyBeanMethods = true)
public class ConfigProxyEnableTest {
    private static final Logger logger = LoggerFactory.getLogger(ConfigProxyEnableTest.class);

    @Bean
    public TestObject testObject() {
        //should init only once
        logger.info("init TestObject");
        return new TestObject();
    }

    /**
     * @return
     */
    @Bean
    public String check() {
        TestObject o1 = testObject();
        TestObject o2 = testObject();
        Asserts.check(o1 == o2, "must be same bean");
        return "";
    }

    @Bean
    public String inject(TestObject testObject) {
        Asserts.notNull(testObject, "must be not null");
        return "";
    }

    private static class TestObject {
    }
}

cglib动态代理

内部调用代理生效与否取决于MethodProxy的调用方式

public class CglibProxyFactory {
    /**
     * 生成内部调用不生效的代理,spring AOP普通代理实现方式
     *
     * @param clazz
     * @param target
     * @param <T>
     * @return
     */
    public static <T> T getCallTargetProxy(Class<T> clazz, T target) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
            String methodName = method.getName();
            if (List.of("execute", "innerCall").contains(methodName)) {
                System.out.println("target: before " + methodName);
                Object result = proxy.invoke(target, args);
                System.out.println("target: after " + method.getName());
                return result;
            } else {
                return proxy.invoke(obj, args);
            }
        });
        return (T) enhancer.create();
    }

    /**
     * 生成内部调生效的代理,spring Configuration class代理实现方式
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getCallProxyProxy(Class<T> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
            String methodName = method.getName();
            if (List.of("execute", "innerCall").contains(methodName)) {
                System.out.println("proxy: before " + methodName);
                Object result = proxy.invokeSuper(obj, args);
                System.out.println("proxy: after " + method.getName());
                return result;
            } else {
                return proxy.invokeSuper(obj, args);
            }
        });
        return (T) enhancer.create();
    }
}

MethodProxy注意事项

public class MethodProxy {
  //在代理类(子类)中使用super.method()形式调用被代理对象的方法,由于java多态机制内部调用代理也会生效
  public Object invokeSuper(Object obj, Object[] args) throws Throwable;
  //该方法用于在相同类型不同实例上调用代理方法,此时内部调用由于不是在代理对象上,因此内部调用代理失效
  //注意:不能在代理对象上调用该方法,否则会触发死循环,即proxy.invoke(obj, args)触发死循环
  public Object invoke(Object obj, Object[] args) throws Throwable;
}

动态代理对象的生成

ProxyFactory:统一代理对象生成方式,底层动态选择使用jdk动态代理或cglib动态代理,如果定义了用户接口优先使用jdk动态代理

使用方式:

public class ProxyFactoryExamples {
    public static class TestObject {
        public void test() {
            System.out.println("run test");
        }
    }

    public static interface TestInterface {
        void execute();
    }

    public static class TestInterfaceImpl implements TestInterface {

        @Override
        public void execute() {
            System.out.println("run execute");
        }
    }

    public static void testJDKProxy() {
        TestInterface proxy = createJDKProxy(new TestInterfaceImpl(), TestInterface.class);
        proxy.execute();
    }

    public static void testCglibProxy() {
        TestObject proxy = (TestObject) createCglibProxy(new TestObject());
        proxy.test();
    }

    public static <T> T createJDKProxy(T sourceTarget, Class<T>... interfaces) {
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(sourceTarget);
        factory.setInterfaces(interfaces);
        factory.addAdvice((MethodBeforeAdvice) (method, args, target) -> {
            System.out.println("before " + method.getName() + " target: " + target);
        });
        return (T) factory.getProxy();
    }

    public static <T> T createCglibProxy(T sourceTarget) {
        ProxyFactory factory = new ProxyFactory();
        factory.setTarget(sourceTarget);
        factory.addAdvice((MethodBeforeAdvice) (method, args, target) -> {
            System.out.println("before " + method.getName() + " target: " + target);
        });
        return (T) factory.getProxy();
    }

    public static void main(String[] args) {
        testJDKProxy();
        testCglibProxy();
    }

}

JDK proxy:对接口进行代理,如果不执行原方法可以不需要被代理对象

Cglib proxy:对类进行代理,如果不执行原方法可以不需要被代理对象

由于普通代理要求调用原对象方法,因此在ProxyFactory的实现中要求必须传入被代理对象

参考资料

示例代码

透过现象看原理:详解 Spring 中 Bean 的 this 调用导致 AOP 失效的原因

完全读懂 Spring 框架之 AOP 实现原理