Spring AOP实战和源码解析

\color{green}{Spring AOP基本概念}

  1. Spring AOP简介

说起Spring框架,我们最先想到的就是IOC和AOP了,如果说IOC是Spring的核心,那AOP就是Spring最重要的功能之一,AOP,即Aspect Oriented Programming,面向切面编程。我们实际项目中,业务除了像数据库的增删改查等核心功能之外,还有一些业务逻辑相同的切面功能,如日志打印和性能统计等。AOP的目的是将业务中共同调用的逻辑封装起来,减少系统中的重复代码,降低模块间的耦合度,提高代码的可扩展性和可维护性。

  1. Spring AOP 基本术语

1)连接点(Joinpoint)
一个类或者一段代码中具有边界性质的特定点,如方法调用前后,抛出异常后或者某个类初始化之前和初始化之后等,是客观存在的边界点;

2)切点(Pointcut)
切点是某些我们感兴趣的连接点,通过切点来定位特定的连接点,可以将连接点和切点的关系看做数据库中的记录和查询条件的关系,一个查询条件可以对应多条记录,一个切点也可以对应多个连接点。在Spring中切点用Pointcut接口来描述;

3)增强(Advice)
增强是织入目标类连接点上的一段程序代码,Spring中增强除了程序代码之外还有一段和连接点相关的方位信息,如Spring提供的BeforeAdvice、AfterReturningAdvice、ThrowsAdvice等;

4)目标对象(Target)
增强逻辑织入的目标类;

5)引介(Introduction)
引介是一种特殊的增强,表示为类添加一些属性或者方法,即使一个类没有实现某个接口,也能通过引介为这个类添加该接口的实现逻辑;

6)织入(Weaving)
将增强添加到目标类连接点上的过程,织入分为编译期织入、类装载期织入和动态代理织入,Spring AOP选择动态代理织入,在运行期为目标类添加增强生成子类的方式,AspectJ选择编译期织入和类装载期间织入,前者需要特殊的编译器,后者需要特殊的类装载器;

7)代理(Proxy)
一个类被AOP增强之后,生成一个结果类,这个类既有目标类的业务逻辑也有增强的逻辑;

8)切面(Aspect)
切面由切点和增强组成,包括横切逻辑的定义也包括特定连接点的定义。

\color{green}{Spring AOP实战}
使用Spring AOP实现一个环绕通知,在方法执行前后打印日志

  • 自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface LogAnnotation {
}
  • 目标方法
@Component
public class UserService {

    @LogAnnotation
    public String getSomething() {
        return "some thing";
    }
}
  • 切面
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Around("@annotation(liuxin.kkssyy.annotation.LogAnnotation)")
    public Object aroundOperateLog(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        Signature signature = proceedingJoinPoint.getSignature();
        if (!(signature instanceof MethodSignature)) {
            return new Object();
        }
        MethodSignature methodSignature = (MethodSignature) signature;
        Method targetMethod = methodSignature.getMethod();
        targetMethod.setAccessible(true);
        Object resVal = null;
        log.info("方法执行前,打印执行的方法名,方法名:[{}]", targetMethod.getName());
        try {
            resVal = proceedingJoinPoint.proceed();
            log.info("方法执行成功,打印执行结果,结果为:[{}]", resVal.toString());
        } catch (Throwable throwable) {
            log.error("方法执行失败", throwable.toString());
        }
        return resVal;
    }
}

  • 方法调用
@Component
public class Client {

    @Autowired
    UserService userService;

    @PostConstruct
    public void run() {
        userService.getSomething();
    }
}
  • 输出结果
2020-05-18 16:58:23.680  INFO 8940 --- [           main] liuxin.kkssyy.aop.LogAspect              : 方法执行前,打印执行的方法名,方法名:[getSomething]
2020-05-18 16:58:23.684  INFO 8940 --- [           main] liuxin.kkssyy.aop.LogAspect              : 方法执行成功,打印执行结果,结果为:[some thing]

\color{green}{Spring AOP源码解析}
Spring AOP动态代理的过程为:
创建AnnotationAwareAspectJAutoProxyCreator对象;
扫描容器中的切面;
创建PointcutAdvisor对象生成代理类

在AbstractAutoProxyCreator类中实现BeanPostProcessor中的下面方法中

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
   if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean) {
          //核心方法
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

核心方法为wrapIfNeccessary,其处理过程为:

  1. 如果已经处理过,且该bean没有被代理过,则直接返回该bean;
  2. 如果该bean是内部基础设置类Class 或 配置了该bean不需要代理,则直接返回bean(返回前标记该bean已被处理过);
  3. 获取所有适合该bean的增强Advisor如果增强不为null,则为该bean创建代理对象,并返回结果,标记该bean已经被处理过
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        
        if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }

        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }


        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

AbstractAutoProxyCreator中的createProxy方法中

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,
        @Nullable Object[] specificInterceptors, TargetSource targetSource) {
 
    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }
 
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);
    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        }
        else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }
 
    Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
    //加入增强器
    proxyFactory.addAdvisors(advisors);
    //设置要代理的类
    proxyFactory.setTargetSource(targetSource);
    //定制代理
    customizeProxyFactory(proxyFactory);
    //用来控制代理工厂被设置后是否还允许修改通知,缺省值为false
    proxyFactory.setFrozen(this.freezeProxy);
    if (advisorsPreFiltered()) {
        proxyFactory.setPreFiltered(true);
    }
 
    return proxyFactory.getProxy(getProxyClassLoader());
}

通过ProxyFactory获取Proxy,方法如下

public Object getProxy(ClassLoader classLoader) {
   return createAopProxy().getProxy(classLoader);
}

可以看到进一步通过AopProxy类的getProxy获取代理对象,

Object getProxy(ClassLoader classLoader);方法有两个实现,即JdkDynamicAopProxy和CglibAopProxy,那么究选择jdk的动态代理还是cglib的动态代理呢?是在DefaultAopProxyFactory类中的createAopProxy方法中处理的

public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
   if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
      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.");
      }
      if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
         return new JdkDynamicAopProxy(config);
      }
      return new ObjenesisCglibAopProxy(config);
   }
   else {
      return new JdkDynamicAopProxy(config);
   }
}

\color{green}{JDK动态代理}
前面介绍过AOP的底层是通过动态代理来实现的,动态代理包括基于JDK的动态代理和CGlib的动态代理,JDK的动态代理的两个核心点是:JDK动态代理是通过继承InvocationHandler接口,通过Proxy的静态方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvacationHandler h)来获取代理对象;从newProxyInstance的方法签名可以看出来,JDK动态代理对象必须实现某一个接口,否则不能通过JDK动态代理来进行增强,下面通过一个简单的例子进行介绍。有一个EmployeeService的接口,接口中定义了一个方法showName(String),打印员工的姓名,现在我们需要在打印员工姓名之前展示公司的信息。
EmployeeService接口

public interface EmployeeService {

    void showName(String name);
}

EmployeeServiceImpl

public class EmployeeServiceImpl implements EmployeeService{

    @Override
    public void showName(String name) {
        System.out.println(name);
    }
}

EmployeeServiceHandler

public class EmployeeServiceHandler implements InvocationHandler {

    private Object target;

    public EmployeeServiceHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        System.out.println("the name of the worker is: ");
        Object result = method.invoke(target, args);
        return result;
    }
}

EmployeeServiceClient

public class EmployeeServiceClient {
    public static void main(String[] args) {
        EmployeeService employeeService = new EmployeeServiceImpl();

        EmployeeServiceHandler employeeServiceHandler = new EmployeeServiceHandler(employeeService);

        EmployeeService employeeService1 = (EmployeeService) Proxy.newProxyInstance(employeeService.getClass().getClassLoader(),employeeService.getClass().getInterfaces(), employeeServiceHandler);
        employeeService1.showName("Tom");
        
        //以下为尝试用另外一种思路实现动态代理,目前还没成功
        Constructor cons = employeeServiceHandler .getClass().getConstructor(constructorParams);
        EmployeeService proxy = (EmployeeService ) cons.newInstance(new Object[]{employeeServiceHandler });
    }
}

JDK动态代理的原理就是实现InvocationHandler接口,通过Proxy.newProxyInstance方法生成代理对象,jdk动态代理只能代理接口的原因就在这里,该方法的第二个参数需要传入接口数组。该方法如下:

public static Object newProxyInstance(ClassLoader loader,
                                      Class<?>[] interfaces,
                                      InvocationHandler h)
    throws IllegalArgumentException
{
    Objects.requireNonNull(h);

    final Class<?>[] intfs = interfaces.clone();
    final SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
    }

    /*
     * Look up or generate the designated proxy class.
     */
    Class<?> cl = getProxyClass0(loader, intfs);

    /*
     * Invoke its constructor with the designated invocation handler.
     */
    try {
        if (sm != null) {
            checkNewProxyPermission(Reflection.getCallerClass(), cl);
        }

        final Constructor<?> cons = cl.getConstructor(constructorParams);
        final InvocationHandler ih = h;
        if (!Modifier.isPublic(cl.getModifiers())) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    cons.setAccessible(true);
                    return null;
                }
            });
        }
        //最重要的一行
        return cons.newInstance(new Object[]{h});
    } catch (IllegalAccessException|InstantiationException e) {
        throw new InternalError(e.toString(), e);
    } catch (InvocationTargetException e) {
        Throwable t = e.getCause();
        if (t instanceof RuntimeException) {
            throw (RuntimeException) t;
        } else {
            throw new InternalError(t.toString(), t);
        }
    } catch (NoSuchMethodException e) {
        throw new InternalError(e.toString(), e);
    }
}

\color{green}{CGlib动态代理}

CGlib的动态代理实例如下
继承MethodInterceptor

public class EmployeeServiceImplInterceptor implements MethodInterceptor{

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("======插入前置通知======");
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("======插入后者通知======");
        return object;
    }
}

调用实例代码如下:

public class CGLibClient {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(EmployeeServiceImpl .class);
        enhancer.setCallback(new EmployeeServiceImplInterceptor ());
        EmployeeServiceImpl employeeServiceImpl = (EmployeeServiceImpl )enhancer.create();
        employeeServiceImpl ..showName("Tom");
    }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352