前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring AOP 实现 原

Spring AOP 实现 原

作者头像
chinotan
发布2019-04-03 15:26:38
4010
发布2019-04-03 15:26:38
举报

AOP(Aspect Orient Programming),我们一般称为面向切面编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务、日志、缓存、分布式锁等等。AOP实现的关键在于AOP框架自动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;而动态代理则以Spring AOP为代表。Spring的主要动态代理有CGLib和JDK自动代理。

使用AspectJ的编译时增强实现AOP

AspectJ是静态代理的增强,所谓的静态代理就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强。

编译成字节码.class比原来的.java会多了一些代码,这就是AspectJ的静态代理,它会在编译阶段将Aspect织入Java字节码中, 运行的时候就是经过增强之后的AOP对象。

使用Spring AOP

与AspectJ的静态代理不同,Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射来接收被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。

如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成某个类的子类,注意,CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

现在我们做一个测试:

首先定义一个接口:

代码语言:javascript
复制
package cn.chinotan.service;

/**
 * @program: test
 * @description: 动物
 * @author: xingcheng
 **/
public interface Animal {

    /**
     * 跑
     * @param where 在什么地方跑
     * @return
     */
    String run (String where);
    
}

其实现类:

代码语言:javascript
复制
package cn.chinotan.service.impl;

import cn.chinotan.aop.Action;
import cn.chinotan.service.Animal;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;

/**
 * @program: test
 * @description: 狗
 * @author: xingcheng
 **/
@Service
public class Dog implements Animal {

    @Action
    @Override
    public String run(String where) {
        System.out.println("狗往" + where + "跑");
        return "地点是:" + where;
    }
}

其中@Action为自定义的注解,用来指定aop代理的切入点

代码语言:javascript
复制
package cn.chinotan.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @program: test
 * @description: 动作
 * @author: xingcheng
 **/
@Target(ElementType.METHOD)
public @interface Action {
    
}

定义Aspect:

代码语言:javascript
复制
package cn.chinotan.aspect;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @program: test
 * @description: 动作aop实现
 * @author: xingcheng
 **/
@Aspect
@Component
public class ActionAspect {

    @Pointcut("@annotation(cn.chinotan.aop.Action)")
    void actionPointCut() {
    }

    @Before("actionPointCut()")
    void beforeAction() {
        System.out.println("热身运动");
    }
}

其中有几种aop的通知注解:

  1. @Before: 前置通知, 在方法执行之前执行
  2. @After: 后置通知, 在方法执行之后执行
  3. @AfterRunning:返回通知, 在方法成功执行返回结果之后执行
  4. @AfterThrowing: 异常通知, 在方法抛出异常之后
  5. @Around: 环绕通知,围绕着方法执行

@Pointcut是切入点的注解:

这里使用了@annotation 可以在使用了自定义注解的配置方法上实现切入

也可以使用execution(* *(..))的形式:

    声明切入点     第一个*表示 方法  返回值(例如public int)     第二个* 表示方法的全限定名(即包名+类名)     perform表示目标方法参数括号两个.表示任意类型参数     方法表达式以“*”号开始,表明了我们不关心方法返回值的类型。然后,我们指定了全限定类名和方法名。对于方法参数列表,     我们使用两个点号(..)表明切点要选择任意的perform()方法,无论该方法的入参是什么     execution表示执行的时候触发

在启动的application.yml配置文件中加入

代码语言:javascript
复制
spring.aop.proxy-target-class: false

这个是控制aop的具体实现方式,为true 的话使用cglib,为false的话使用java的Proxy,默认是false

之后运行controller:

代码语言:javascript
复制
package cn.chinotan.controller;

import cn.chinotan.service.Animal;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @program: test
 * @description: test类
 * @author: xingcheng
 **/
@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    Animal animal;

    @GetMapping("/aopRun")
    public String aopRun() {
        animal.run("狗窝");
        System.out.println("dog代理为:" + animal.getClass());
        
        return "ok";
    }
    
}

打印日志:

可以看到类型是com.sun.proxy.$Proxy71,也就是前面提到的Proxy类,因此这里Spring AOP使用了JDK的动态代理。

再来看看不实现接口的情况,修改Dog类:

配置proxy-target-class: false依旧

代码语言:javascript
复制
package cn.chinotan.service.impl;

import cn.chinotan.aop.Action;
import cn.chinotan.service.Animal;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;

/**
 * @program: test
 * @description: 狗
 * @author: xingcheng
 **/
@Service
public class Dog {

    @Action
    public String run(String where) {
        System.out.println("狗往" + where + "跑");
        return "地点是:" + where;
    }
}

打印日志:

可以看到类被CGLIB增强了,也就是动态代理。这里的CGLIB代理就是Spring AOP的代理,这个类也就是所谓的AOP代理,AOP代理类在切点动态地织入了增强处理。

可以看到:

    AspectJ在编译时就增强了目标对象,Spring AOP的动态代理则是在每次运行时动态的增强,生成AOP代理对象,区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

    java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP,如果目标对象实现了接口,可以强制使用CGLIB实现AOP,如果目标对象没有实现了接口,必须采用CGLIB库,spring会自动在JDK动态代理和CGLIB之间转换

误区注意:

在平时开发中,我们通常在Service中定义了一个方法并且切入之后,从Controller里面调用该方法可以实现切入,但是当在同一个Service中实现另一方法并调用改方法时却无法切入

类似于:

代码语言:javascript
复制
package cn.chinotan.service.impl;

import cn.chinotan.aop.Action;
import cn.chinotan.service.Animal;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;

/**
 * @program: test
 * @description: 狗
 * @author: xingcheng
 * @create: 2018-10-27 16:00
 **/
@Service
public class Dog implements Animal {

    @Action
    @Override
    public String run(String where) {
        System.out.println("狗往" + where + "跑");
        return "地点是:" + where;
    }

    @Override
    public void runToEat(String food) {
        run("狗窝");
        System.out.println("狗在吃" + food);
    }
}


package cn.chinotan.service.impl;

import cn.chinotan.aop.Action;
import cn.chinotan.service.Animal;
import org.springframework.aop.framework.AopContext;
import org.springframework.stereotype.Service;

/**
 * @program: test
 * @description: 狗
 * @author: xingcheng
 * @create: 2018-10-27 16:00
 **/
@Service
public class Dog implements Animal {

    @Action
    @Override
    public String run(String where) {
        System.out.println("狗往" + where + "跑");
        return "地点是:" + where;
    }

    @Override
    public void runToEat(String food) {
        run("狗窝");
        System.out.println("狗在吃" + food);
    }
}

我们在执行runToEat方法时,调用了自己类中的另一个方法,结果为:

可以看到run()的切面方法并没有执行,以上结果的出现与Spring AOP的实现原理息息相关,由于Spring AOP采用了动态代理实现AOP,在Spring容器中的bean(也就是目标对象)会被代理对象代替,代理对象里加入了我们需要的增强逻辑,当调用代理对象的方法时,目标对象的方法就会被拦截,

通过调用代理对象的action方法,在其内部会经过切面增强,然后方法被发射到目标对象,在目标对象上执行原有逻辑,如果在原有逻辑中嵌套调用了work方法,则此时work方法并没有被进行切面增强,因为此时它已经在目标对象内部。而解决方案很好地说明了,将嵌套方法发射到代理对象,这样就完成了切面增强。可以看下源码:

在代码3处,如果配置了exposeProxy开关,则会将代理对象暴露在当前线程中,以供其它需要的地方使用,通过使用静态的全局ThreadLocal变量就解决了问题。

spring提供了一个这样的类:

可以看到他可以获取到当前的aop代理,但是在获取之前,得开启exposeProxy开关

代码语言:javascript
复制
@EnableAspectJAutoProxy(proxyTargetClass = false, exposeProxy = true)

这样就可以进行代理了,打印日志为:

既然这样可以,那是不是直接applicationContext.getBean()也可以呢?实验过后得到的结果是可行,而且配置中的expose-proxy也不用设置成true,那试一下:

代码语言:javascript
复制
package cn.chinotan.service.impl;

import cn.chinotan.aop.Action;
import cn.chinotan.service.Animal;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Service;

/**
 * @program: test
 * @description: 狗
 * @author: xingcheng
 * @create: 2018-10-27 16:00
 **/
@Service
public class Dog implements Animal {
    
    @Autowired
    ApplicationContext applicationContext;

    @Action
    @Override
    public String run(String where) {
        System.out.println("狗往" + where + "跑");
        return "地点是:" + where;
    }

    @Override
    public void runToEat(String food) {
//        Dog dog = (Dog) AopContext.currentProxy();
        Dog dog = (Dog) applicationContext.getBean("dog");
        dog.run("狗窝");
        System.out.println("狗在吃" + food);
    }
}

打印日志为:

可见同样可以

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018/10/28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 使用AspectJ的编译时增强实现AOP
  • 使用Spring AOP
  • 误区注意:
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档