Spring Boot實踐——AOP實現

借鑑:http://www.cnblogs.com/xrq730/p/4919025.htmlhtml

      https://blog.csdn.net/zhaokejin521/article/details/50144753程序員

    http://www.importnew.com/24305.htmlspring

AOP介紹

1、AOP

  AOP(Aspect Oriented Programming),即面向切面編程,能夠說是OOP(Object Oriented Programming,面向對象編程)的補充和完善。OOP引入封裝、繼承、多態等概念來創建一種對象層次結構,用於模擬公共行爲的一個集合。不過OOP容許開發者定義縱向的關係,但並不適合定義橫向的關係,例如日誌功能。日誌代碼每每橫向地散佈在全部對象層次中,而與它對應的對象的核心功能毫無關係對於其餘類型的代碼,如安全性、異常處理和透明的持續性也都是如此,這種散佈在各處的無關的代碼被稱爲橫切(cross cutting),在OOP設計中,它致使了大量代碼的重複,而不利於各個模塊的重用。express

AOP技術偏偏相反,它利用一種稱爲"橫切"的技術,剖解開封裝的對象內部,並將那些影響了多個類的公共行爲封裝到一個可重用模塊,並將其命名爲"Aspect",即切面。所謂"切面",簡單說就是那些與業務無關,卻爲業務模塊所共同調用的邏輯或責任封裝起來,便於減小系統的重複代碼,下降模塊之間的耦合度,並有利於將來的可操做性和可維護性。編程

使用"橫切"技術,AOP把軟件系統分爲兩個部分:核心關注點橫切關注點。業務處理的主要流程是核心關注點,與之關係不大的部分是橫切關注點。橫切關注點的一個特色是,他們常常發生在覈心關注點的多處,而各處基本類似,好比權限認證、日誌、事物。AOP的做用在於分離系統中的各類關注點,將核心關注點和橫切關注點分離開來。瀏覽器

  AOP(Aspect Orient Programming),咱們通常稱爲面向方面(切面)編程,做爲面向對象的一種補充,用於處理系統中分佈於各個模塊的橫切關注點,好比事務管理、日誌、緩存等等。AOP實現的關鍵在於AOP框架自動建立的AOP代理,AOP代理主要分爲靜態代理和動態代理,靜態代理的表明爲AspectJ;而動態代理則以Spring AOP爲表明。緩存

  與AspectJ的靜態代理不一樣,Spring AOP使用的動態代理,所謂的動態代理就是說AOP框架不會去修改字節碼,而是在內存中臨時爲方法生成一個AOP對象,這個AOP對象包含了目標對象的所有方法,而且在特定的切點作了加強處理,並回調原對象的方法。安全

  Spring AOP中的動態代理主要有兩種方式,JDK動態代理和CGLIB動態代理。JDK動態代理經過反射來接收被代理的類,而且要求被代理的類必須實現一個接口。JDK動態代理的核心是InvocationHandler接口和Proxy類。springboot

  若是目標類沒有實現接口,那麼Spring AOP會選擇使用CGLIB來動態代理目標類。CGLIB(Code Generation Library),是一個代碼生成的類庫,是利用asm開源包,能夠在運行時動態的生成某個類的子類注意,CGLIB是經過繼承的方式作的動態代理,所以若是某個類被標記爲final,那麼它是沒法使用CGLIB作動態代理的。框架

2、AOP核心概念

一、橫切關注點

對哪些方法進行攔截,攔截後怎麼處理,這些關注點稱之爲橫切關注點

二、切面(aspect)

類是對物體特徵的抽象,切面就是對橫切關注點的抽象

三、鏈接點(joinpoint)

被攔截到的點,由於Spring只支持方法類型的鏈接點,因此在Spring中鏈接點指的就是被攔截到的方法,實際上鍊接點還能夠是字段或者構造器

四、切入點(pointcut)

對鏈接點進行攔截的定義

五、通知(advice)

所謂通知指的就是指攔截到鏈接點以後要執行的代碼,通知分爲前置、後置、異常、最終、環繞通知五類

六、目標對象

代理的目標對象

七、織入(weave)

將切面應用到目標對象並致使代理對象建立的過程

八、引入(introduction)

在不修改代碼的前提下,引入能夠在運行期爲類動態地添加一些方法或字段

 

3、Spring對AOP的支持

  Spring中AOP代理由Spring的IOC容器負責生成、管理,其依賴關係也由IOC容器負責管理。所以,AOP代理能夠直接使用容器中的其它bean實例做爲目標,這種關係可由IOC容器的依賴注入提供。Spring建立代理的規則爲:

一、默認使用Java動態代理來建立AOP代理,這樣就能夠爲任何接口實例建立代理了

二、當須要代理的類不是代理接口的時候,Spring會切換爲使用CGLIB代理,也可強制使用CGLIB

AOP編程實際上是很簡單的事情,縱觀AOP編程,程序員只須要參與三個部分:

一、定義普通業務組件

二、定義切入點,一個切入點可能橫切多個業務組件

三、定義加強處理,加強處理就是在AOP框架爲普通業務組件織入的處理動做

因此進行AOP編程的關鍵就是定義切入點和定義加強處理,一旦定義了合適的切入點和加強處理,AOP框架將自動生成AOP代理,即:代理對象的方法=加強處理+被代理對象的方法。

實現方式

Spring除了支持Schema方式配置AOP,還支持註解方式:使用@AspectJ風格的切面聲明。

1、Aspectj介紹

@AspectJ 做爲經過 Java 5 註釋註釋的普通的 Java 類,它指的是聲明 aspects 的一種風格。

AspectJ是靜態代理的加強,所謂的靜態代理就是AOP框架會在編譯階段生成AOP代理類,所以也稱爲編譯時加強。

AspectJ: 基於字節碼操做(Bytecode Manipulation),經過編織階段(Weaving Phase),對目標Java類型的字節碼進行操做,將須要的Advice邏輯給編織進去,造成新的字節碼。畢竟JVM執行的都是Java源代碼編譯後獲得的字節碼,因此AspectJ至關於在這個過程當中作了一點手腳,讓Advice可以參與進來。

而編織階段能夠有兩個選擇,分別是加載時編織(也能夠成爲運行時編織)和編譯時編織

  • 加載時編織(Load-Time Weaving):顧名思義,這種編織方式是在JVM加載類的時候完成的。
  • 編譯時編織(Compile-Time Weaving):須要使用AspectJ的編譯器來替換JDK的編譯器。

  詳情:AOP的兩種實現-Spring AOP以及AspectJ

一、添加spirng aop支持和AspectJ依賴

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>5.0.7.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.3.RELEASE</version>
</parent>


<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
    </dependency>
</dependencies>

二、啓用對@AspectJ的支持

  Spring默認不支持@AspectJ風格的切面聲明,爲了支持須要使用以下配置:

<!-- 自動掃描使用了aspectj註解的類 -->
<aop:aspectj-autoproxy/>

或者在配置類上加註解

@Configuration
@ComponentScan("com.only.mate.springboot.aop")
@EnableAspectJAutoProxy//開啓AspectJ註解
public class CustomAopConfigurer {
}

三、聲明切面

@Aspect
@Component
public class CustomLogAspect {
}

或者

定一個普通類

public class CustomAuthorityAspect {
}

在配置文件中定義一個POJO

<bean id="customAuthorityAspect" class="com.only.mate.springboot.aop.CustomAuthorityAspect" />

而後在該切面中進行切入點及通知定義,接着往下看吧。

四、聲明切入點

@AspectJ風格的命名切入點使用org.aspectj.lang.annotation包下的@Pointcut+方法(方法必須是返回void類型)實現。

@Pointcut(value="切入點表達式", argNames = "參數名列表")  
public void pointcutName(……) {}  

  value:指定切入點表達式;

       argNames:指定命名切入點方法參數列表參數名字,能夠有多個用「,」分隔,這些參數將傳遞給通知方法同名的參數,同時好比切入點表達式「args(param)」將匹配參數類型爲命名切入點方法同名參數指定的參數類型。

       pointcutName:切入點名字,可使用該名字進行引用該切入點表達式。

案例:

@Pointcut(value="execution(* com.only.mate.springboot.controller.*.sayAdvisorBefore(..)) && args(param)", argNames = "param")  
public void pointCut(String param) {} 

定義了一個切入點,名字爲「pointCut」,該切入點將匹配目標方法的第一個參數類型爲通知方法實現中參數名爲「param」的參數類型。

五、聲明通知

@AspectJ風格的聲明通知也支持5種通知類型:

A、前置通知:使用org.aspectj.lang.annotation 包下的@Before註解聲明。

@Before(value = "切入點表達式或命名切入點", argNames = "參數列表參數名")  

       value:指定切入點表達式或命名切入點。

       argNames:與Schema方式配置中的同義。

B、後置返回通知:使用org.aspectj.lang.annotation 包下的@AfterReturning註解聲明。

@AfterReturning(  
value="切入點表達式或命名切入點",  
pointcut="切入點表達式或命名切入點",  
argNames="參數列表參數名",  
returning="返回值對應參數名")  

       value:指定切入點表達式或命名切入點。

       pointcut:一樣是指定切入點表達式或命名切入點,若是指定了將覆蓋value屬性指定的,pointcut具備高優先級。

       argNames:與Schema方式配置中的同義。

       returning:與Schema方式配置中的同義。

C、後置異常通知:使用org.aspectj.lang.annotation 包下的@AfterThrowing註解聲明。

@AfterThrowing (  
value="切入點表達式或命名切入點",  
pointcut="切入點表達式或命名切入點",  
argNames="參數列表參數名",  
throwing="異常對應參數名")

       value:指定切入點表達式或命名切入點。

       pointcut:一樣是指定切入點表達式或命名切入點,若是指定了將覆蓋value屬性指定的,pointcut具備高優先級。

       argNames:與Schema方式配置中的同義。

       throwing:與Schema方式配置中的同義。

D、後置最終通知:使用org.aspectj.lang.annotation 包下的@After註解聲明。

@After (  
value="切入點表達式或命名切入點",  
argNames="參數列表參數名") 

       value:指定切入點表達式或命名切入點。

       argNames:與Schema方式配置中的同義。

E、環繞通知:使用org.aspectj.lang.annotation 包下的@Around註解聲明。

@Around (  
value="切入點表達式或命名切入點",  
argNames="參數列表參數名")  

       value:指定切入點表達式或命名切入點。

       argNames:與Schema方式配置中的同義。

2、實踐

一、Schema方式配置AOP

A、定一個切入點

/**
 * 自定義一個切入點-權限校驗
 * @ClassName: CustomAuthorityAspect 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年9月7日 下午2:24:24  
 *
 */
public class CustomAuthorityAspect {
    private Logger logger = LoggerFactory.getLogger(CustomLogAspect.class);
    /**
     * 加密
     * @Title: encode 
     * @Description: TODO
     * @Date 2018年9月7日 下午2:30:05 
     * @author OnlyMate
     */
    public void encode() {
        logger.info("CustomAuthorityAspect ==> encode method: encode data");
    }
    
    /**
     * 解密
     * @Title: decode 
     * @Description: TODO
     * @Date 2018年9月7日 下午2:30:11 
     * @author OnlyMate
     */
    public void decode() {
        logger.info("CustomAuthorityAspect ==> decode method: decode data");
    }
}

B、經過Schema方式配置AOP

<bean id="customAuthorityAspect" class="com.only.mate.springboot.aop.CustomAuthorityAspect" />

<aop:config proxy-target-class="false">
    <!-- AOP實現 -->
    <aop:aspect id="customAuthority" ref="customAuthorityAspect">
        <aop:pointcut id="addAllMethod" expression="execution(* com.only.mate.springboot.controller.*.*(..))" />
        <aop:before method="encode" pointcut-ref="addAllMethod" />
        <aop:after method="decode" pointcut-ref="addAllMethod" />
    </aop:aspect>
</aop:config>

  前面說過Spring使用動態代理或是CGLIB生成代理是有規則的,高版本的Spring會自動選擇是使用動態代理仍是CGLIB生成代理內容,固然咱們也能夠強制使用CGLIB生成代理,那就是<aop:config>裏面有一個"proxy-target-class"屬性,這個屬性值若是被設置爲true,那麼基於類的代理將起做用,若是proxy-target-class被設置爲false或者這個屬性被省略,那麼基於接口的代理將起做用

二、使用@AspectJ風格的切面聲明

A、定一個切入點

/**
 * @Description: 自定義切面
 * @ClassName: CustomLogAspect 
 * @author OnlyMate
 * @Date 2018年9月10日 下午3:51:32  
 *
 */
@Aspect
@Component
public class CustomLogAspect {
    private Logger logger = LoggerFactory.getLogger(CustomLogAspect.class);

    /**
     * @Description: 定義切入點
     * @Title: pointCut 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:17
     */
    //被註解CustomAopAnnotation表示的方法
    //@Pointcut("@annotation(com.only.mate.springboot.annotation.CustomAopAnnotation")
    @Pointcut("execution(public * com.only.mate.springboot.controller.*.*(..))")
    public void pointCut(){
        
    }

    /**
     * @Description: 定義前置通知
     * @Title: before 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:23 
     * @param joinPoint
     * @throws Throwable
     */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) throws Throwable {
        // 接收到請求,記錄請求內容
        logger.info("【註解:Before】------------------切面  before");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 記錄下請求內容
        logger.info("【註解:Before】瀏覽器輸入的網址=URL : " + request.getRequestURL().toString());
        logger.info("【註解:Before】HTTP_METHOD : " + request.getMethod());
        logger.info("【註解:Before】IP : " + request.getRemoteAddr());
        logger.info("【註解:Before】執行的業務方法名=CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        logger.info("【註解:Before】業務方法得到的參數=ARGS : " + Arrays.toString(joinPoint.getArgs()));

    }

    /**
     * @Description: 後置返回通知
     * @Title: afterReturning 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:30 
     * @param ret
     * @throws Throwable
     */
    @AfterReturning(returning = "ret", pointcut = "pointCut()")
    public void afterReturning(Object ret) throws Throwable {
        // 處理完請求,返回內容
        logger.info("【註解:AfterReturning】這個會在切面最後的最後打印,方法的返回值 : " + ret);
    }

    /**
     * @Description: 後置異常通知
     * @Title: afterThrowing 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:37 
     * @param jp
     */
    @AfterThrowing("pointCut()")
    public void afterThrowing(JoinPoint jp){
        logger.info("【註解:AfterThrowing】方法異常時執行.....");
    }

    /**
     * @Description: 後置最終通知,final加強,不論是拋出異常或者正常退出都會執行
     * @Title: after 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:48 
     * @param jp
     */
    @After("pointCut()")
    public void after(JoinPoint jp){
        logger.info("【註解:After】方法最後執行.....");
    }

    /**
     * @Description: 環繞通知,環繞加強,至關於MethodInterceptor
     * @Title: around 
     * @author OnlyMate
     * @Date 2018年9月10日 下午3:52:56 
     * @param pjp
     * @return
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) {
        logger.info("【註解:Around . 環繞前】方法環繞start.....");
        try {
            //若是不執行這句,會不執行切面的Before方法及controller的業務方法
            Object o =  pjp.proceed();
            logger.info("【註解:Around. 環繞後】方法環繞proceed,結果是 :" + o);
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }

}

 

B、使用@AspectJ風格的切面聲明

/**
 * 自定義AOP配置類
 * @ClassName: CustomAopConfigurer 
 * @Description: TODO
 * @author OnlyMate
 * @Date 2018年9月7日 下午3:43:21  
 *
 */
@Configuration
@ComponentScan("com.only.mate.springboot.aop")
@EnableAspectJAutoProxy//開啓AspectJ註解
public class CustomAopConfigurer {
}

效果圖

總結

AspectJ在編譯時就加強了目標對象,Spring AOP的動態代理則是在每次運行時動態的加強,生成AOP代理對象,區別在於生成AOP代理對象的時機不一樣,相對來講AspectJ的靜態代理方式具備更好的性能,可是AspectJ須要特定的編譯器進行處理,而Spring AOP則無需特定的編譯器處理。

詳情:http://www.importnew.com/24305.html

 

想知道更多@AspectJ的使用,請前往 Spring Boot實踐——三種攔截器的建立 查看。

相關文章
相關標籤/搜索