Spring Boot实践——Spring AOP实现之动态代理

Spring AOP 介绍

  AOP的介绍可以查看 Spring Boot实践——AOP实现

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

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

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

  这里有注意的几点如下:

  • 从Spring 3.2以后不再将CGLIB放在项目的classpath下,而是将CGLIB类打包放在spring-core下面的org.springframework中。这个就意味着基于CGLIB的动态代理与JDK的动态代理在支持“just works”就一样了。
  • 在Spring 4.0中,因为CGLIB代理实例是通过Objenesis创建的,所以代理对象的构造器不再有两次调用。
  • 在 Spring Boot 2.0 中,Spring Boot现在默认使用CGLIB动态代理(基于类的动态代理), 包括AOP. 如果需要基于接口的动态代理(JDK基于接口的动态代理) , 需要设置spring.aop.proxy-target-class属性为false。

 

实现方式

一、验证Spring AOP动态代理

博主使用的是Spring Boot 2.0版本

1、JDK动态代理

定义接口

public interface Person {
    String say(String name);
    
    void eat(String food);
}

实现类

@Component
public class Chinese implements Person {
    private Logger logger = LoggerFactory.getLogger(Person.class);
    
    public Chinese() {
        super();
        logger.info("Chinese ==> Chinese method : 正在生成一个Chinese实例");
    }

    @Override
    @PersonAnnotation(name="Chinese")//该注解是用来定义切点
    public String say(String name) {
        logger.info("Chinese ==> say method : say {}", name);
        return name + " hello, JDK implement AOP";
    }

    @Override
    public void eat(String food) {
        logger.info("Chinese ==> eat method : eat {}", food);
    }
    
}

定义Aspect

@Aspect
@Component
public class PersonAspect {
    private Logger logger = LoggerFactory.getLogger(PersonAspect.class);
    
    @Pointcut("@annotation(com.only.mate.springboot.annotation.PersonAnnotation)")
    public void pointCut(){
        
    }
    
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) throws Throwable {
        logger.info("PersonAspect ==> before method : {}", joinPoint.getClass());

    }
    
    @After("pointCut()")
    public void after(JoinPoint joinPoint){
        logger.info("PersonAspect ==> after method : {}", joinPoint.getClass());
    }
}

 

2、CGLIB动态代理

定义一个类

@Component
public class American {
    private Logger logger = LoggerFactory.getLogger(American.class);
    
    public American() {
        super();
        logger.info("American ==> American method : 正在生成一个American实例");
    }

    @PersonAnnotation(name="American")//该注解是用来定义切点
    public String say(String name) {
        logger.info("American ==> say method : say {}", name);
        return name + " hello, CGLIB implement AOP";
    }

    public void eat(String food) {
        logger.info("American ==> eat method : eat {}", food);
    }
    
}

3、配置

<!-- 自动扫描使用了aspectj注解的类 -->
<aop:aspectj-autoproxy/>

@Configuration
@ComponentScan("com.only.mate.springboot.aop")
@EnableAspectJAutoProxy//开启AspectJ注解
public class CustomAopConfigurer {
}

4、运行

@Controller
@RequestMapping(value="/aop")
public class AopImplementController {
    private Logger logger = LoggerFactory.getLogger(AopImplementController.class);
    
    @Autowired
    private Person chinese;
    @Autowired
    private American american;
    
    @ResponseBody
    @RequestMapping(value="/talk")
    public String talk() {
        chinese.say("中国人说汉语");
        american.say("American say english");
        
        logger.info("AopImplementController ==> talk method : {}", chinese.getClass());
        logger.info("AopImplementController ==> talk method : {}", american.getClass());

        return "success";
    }
}

5、效果图

问题出现了,按照之前的说法,Chinese应该是用JDK动态代理,American使用CGLIB动态代理才对。


 

  由于博主使用的是Spring Boot 2.0 ,在 Spring Boot 2.0 中,Spring Boot现在默认使用CGLIB动态代理(基于类的动态代理), 包括AOP。 如果需要基于接口的动态代理(JDK基于接口的动态代理) ,需要设置spring.aop.proxy-target-class属性为false。

因此在application.properties加上配置spring.aop.proxy-target-class=false。重启访问

如图:

这样才达到了预期的目的


通过网上查找:

  • 想要强制使用CGLIB,那么就设置<aop:config>下面的proxy-target-class属性为true
<aop:config proxy-target-class="true">
        <!-- other beans defined here... -->
</aop:config>

此处没有验证,仅供参考。

  • 要是使用@AspectJ强制使用CGLIB的话,可以配置<aop:aspectj-autoproxy>下的proxy-target-class属性为true
<aop:aspectj-autoproxy proxy-target-class="true"/>

此处博主用Spring Boot 2.0,加以上配置无效,还是使用默认的CGLIB动态代理。失效的具体原因有待查验!

  • 要是使用@AspectJ强制使用CGLIB的话,向@EnableAspectJAutoProxy注解中添加属性proxyTargetClass = true

此处博主用Spring Boot 2.0,加以上配置无效,还是使用默认的CGLIB动态代理。失效的具体原因有待查验!

二、Spring AOP动态代理实现

1、JDK动态代理

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @Description: Spring AOP之JDK动态代理实现
 * @ClassName: JDKDynamicSubject 
 * @author OnlyMate
 * @Date 2018年9月11日 下午4:47:53  
 *
 */
public class JDKDynamicObject implements InvocationHandler {
    private Logger logger = LoggerFactory.getLogger(JDKDynamicObject.class);

    private Object target;

    public JDKDynamicObject() {
    }

    /**
     * @Description: 绑定对象,并生成代理对象
     * @Title: bind 
     * @author OnlyMate
     * @Date 2018年9月11日 下午4:48:31 
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        // 取得代理对象
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        logger.info("JDKDynamicObject ==> invoke method : {},{},{}", proxy.getClass(), method.getName(),
                args.toString());
        method.invoke(target, args);
        return null;
    }
}

2、CGLIB动态代理

import java.lang.reflect.Method;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;

/**
 * @Description: Spring AOP之CGLIB动态代理实现
 * @ClassName: CGLIBDynamicObject 
 * @author OnlyMate
 * @Date 2018年9月11日 下午4:57:30  
 *
 */
public class CGLIBDynamicObject implements InvocationHandler {
    private Logger logger = LoggerFactory.getLogger(CGLIBDynamicObject.class);

    private Object target;

    public CGLIBDynamicObject() {
    }

    /**
     * @Description: 绑定对象,并生成代理对象
     * @Title: bind 
     * @author OnlyMate
     * @Date 2018年9月11日 下午4:48:31 
     * @param target
     * @return
     */
    public Object bind(Object target) {
        this.target = target;
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(this.target.getClass());
        // 回调方法
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }

    
    @Override
    public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
        logger.info("CGLIBDynamicObject ==> invoke method : {},{},{}", arg0.getClass(), arg1.getName(),
                arg2.toString());
        arg1.invoke(target, arg2);
        return null;
    }
}

 

3、运行

@Controller
@RequestMapping(value="/aop")
public class AopImplementController {
    private Logger logger = LoggerFactory.getLogger(AopImplementController.class);
    
    @Autowired
    private Person chinese;
    @Autowired
    private American american;
    
    @ResponseBody
    @RequestMapping(value="/talk")
    public String talk() {
//        chinese.say("中国人说汉语");
//        american.say("American say english");
//        
//        logger.info("AopImplementController ==> talk method : {}", chinese.getClass());
//        logger.info("AopImplementController ==> talk method : {}", american.getClass());
        
        //自定义JDK动态代理
//        Chinese chinese1 = new Chinese();
//        InvocationHandler dsc = new JDKDynamicObject(chinese1);
//        Person person = (Person) Proxy.newProxyInstance(chinese1.getClass().getClassLoader(), chinese1.getClass().getInterfaces(), dsc);
//        person.say("中国人说汉语");
//        logger.info("AopImplementController ==> talk method : JDKDynamicObject {}", person.getClass());
        
        JDKDynamicObject dsc1 = new JDKDynamicObject();
        Person person1 = (Person)dsc1.bind(new Chinese());
        person1.say("中国人说汉语");
        logger.info("AopImplementController ==> talk method : JDKDynamicObject {}", person1.getClass());
        
        //自定义CGLIB动态代理
        CGLIBDynamicObject dsm = new CGLIBDynamicObject();
        American american1 = (American) dsm.bind(new American()); 
        american1.say("American say english");
        logger.info("AopImplementController ==> talk method : CGLIBDynamicObject {}", american1.getClass());
        return "success";
        
    }

}

4、效果图

简单的实现了Spring AOP的JDK动态代理和CGLIB动态代理。

posted @ 2018-09-11 22:23  路途寻码人  阅读(33342)  评论(6编辑  收藏  举报