AOP及内部调用

AOP简介

将相同逻辑的重复代码横向抽取出来,使用动态代理技术将这些重复代码织入到目标对象方法中,实现和原来一样的功能。
这样一来,我们就在写业务时只关心业务代码,而不用关心与业务无关的代码。

拦截器的实现原理就是动态代理,实现AOP机制。

Spring 的代理实现有两种:

  • 基于 JDK Dynamic Proxy 技术而实现的
  • 基于 CGLIB 技术而实现的。

如果该类实现了接口,Java的Proxy做动态代理;如果没有实现,则是CGLIB做的动态代理(以子类的方式)。

JDK 动态代理基于接口,所以只有接口中的方法会被增强,而 CGLIB 基于类继承,需要注意就是如果方法使用了 final 修饰,或者是 private 方法,是不能被增强的。

常见Spring AOP注解有

  • @Async,异步
  • @Transational,事务

术语

连接点(Join point):

  • 能够被拦截的地方:Spring AOP是基于动态代理的,所以是方法拦截的。每个成员方法都可以称之为连接点~

切点(Poincut):

  • 具体定位的连接点:上面也说了,每个方法都可以称之为连接点,我们具体定位到某一个方法就成为切点。

增强/通知(Advice):

  • 表示添加到切点的一段逻辑代码,并定位连接点的方位信息。
    简单来说就定义了是干什么的,具体是在哪干
  • Spring AOP提供了5种Advice类型给我们:前置、后置、返回、异常、环绕给我们使用!

织入(Weaving):

  • 将增强/通知添加到目标类的具体连接点上的过程。

引入/引介(Introduction):

  • 引入/引介允许我们向现有的类添加新方法或属性。是一种特殊的增强!

切面(Aspect):

  • 切面由切点和增强/通知组成,它既包括了横切逻辑的定义、也包括了连接点的定义。

源码

DefaultAopProxyFactory

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
// 如果要代理的类本身就是接口,也会用 JDK 动态代理
return new JdkDynamicAopProxy(config);
} else {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
} else {
//如果被代理的目标类实现了一个或多个自定义的接口,那么就会使用 JDK 动态代理
//如果没有实现任何接口,会使用 CGLIB 实现代理
//如果设置了 proxy-target-class="true",那么都会使用 CGLIB
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
// 判断是否有实现自定义的接口
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}

内部调用

1
2
3
4
5
6
7
8
9
public class ServiceTest {
public void foo(){
this.bar();//调用自身的方法;
}
@Transactional
public void bar(){
System.out.println("this is bar");
}
}

结果:bar是事务执行的,而foo不是事务执行;当foo方法内部调用bar方法后,bar方法的事务是不生效的。

原因

  • Spring中通过注解来完成事务的功能,实际是通过SpringAOP来实现的。
  • 而SpringAOP中,使用this来调用自身的方法时,此对象引用上的方法直接会被调用,不会调用代理的方法(SpringAOP原理是产生代理类);因此bar方法的事务不会生效。
  • 如果不通过foo,而直接调用bar方法,此时事务是生效的。

解决

  • 将bar方法放在另一个service类中,类外调用;这种方法简单,但是造成代码的冗余。
  • 可以将注解@Transactional放在foo方法上;加入foo方法的一些操作是不需要事务的,这会延长事务执行的时间。
  • 在foo方法中不要直接使用this来调用bar方法,通过调用代理类的bar方法。

    1
    2
    3
    4
    5
    6
    public void foo(){
    if(null != AopContext.currentProxy()){ ((ServiceTest)AopContext.currentProxy()).bar();
    }else{
    bar();
    }
    }

    且配置

    1
    <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>

    它是ProxyConfig的一个参数,默认是false,如果不设置这个参数,那么上述java代码将无法获取当前线程中的代理对象。

    这种方法可以成功触发拦截,但是也带来了其他问题,比如代码的织入,我们的代码将变得复杂而且晦涩,而且严格要求系统针对于当前的bean必须配置拦截器,否则会因为找不到拦截器而抛出异常。

------ 本文结束------

本文标题:AOP及内部调用

文章作者:Perkins

发布时间:2020年01月09日

原始链接:https://perkins4j2.github.io/posts/43132/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。