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動態代理。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM