天天看點

最簡單的基于注解進行面向切面AOP開發案例1. 前言2. 功能說明3. 項目結構4. 測試執行5. 寫在最後的話

目錄

1. 前言

2. 功能說明

3. 項目結構

3.1. pom.xml maven配置檔案

3.2. application.xml Spring配置檔案

3.3. TestTarget類檔案

3.4. AopEnhance類檔案

3.4.1. enhance1方法的@Before注解

3.4.2 enhance2方法的@Around注解

3.4.3 enhance3方法的@AfterThrowing注解

3.5. AopTest類檔案

4. 測試執行

4.1 執行 target1 方法

4.2 執行 target2 方法

4.3 執行 target3 方法

5. 寫在最後的話

1. 前言

Java開發的時候,面向切面的開發還是有點兒饒頭的。

本文是一個簡單的面向切面開發的案例。

本文的閱讀對象是面向切面開發的初學者,旨在幫助初學者了解該基礎。

本文并不适合Java技術高手。

本文放棄了一切多餘的技術,旨在提供最單純的面向切面開發的案例,幫助初學者快速了解面向切面開發。

本文的技術棧: Maven + Spring 

2. 功能說明

本文建立了一個項目,項目中有 一個類 TestTarget ,還有 切面類 AopEnhance ,

TestTarget 中有3個方法: target1()  , target2()  ,  target3() 

AopEnhance類中也有3個方法: enhance1() ,  enhance2() , enhance3() 

1 TestTarget.target1方法執行, 會在之前切入 AopEnhance.enhance1方法,即 target1方法執行前會先執行 enhance1 方法。

2 TestTarget.target2方法執行,會環繞切入 AopEnhance.enhance2方法,即 target2方法執行的時候,會環繞執行 enhance2 方法。

3 TestTarget.target3方法執行,如果有異常會切入 AopEnhance.enhance3方法,即 target3方法執行的時候如果出現了異常,就會先執行 enhance2方法。

3. 項目結構

項目的結構見下圖

最簡單的基于注解進行面向切面AOP開發案例1. 前言2. 功能說明3. 項目結構4. 測試執行5. 寫在最後的話

看上圖可以知道,該項目隻有 5 個檔案

pom.xml maven配置檔案: 不贅述

application.xml spring配置檔案: 不贅述

AopTest類檔案: 是為了測試的類,裡面有一個 main 方法,用于執行aop 相關的測試代碼。 

TestTarget 類檔案: 這個類裡面有3個方法,target1 target2 和 target3 ,這三個方法會被面向切面切入。

AopEnhance增強類檔案: 這個類是一個切面類,裡面有三個方法 ,會切入到 TestTarget類相關的方法中。

3.1. pom.xml maven配置檔案

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wanshi</groupId>
    <artifactId>week1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>week1</name>
    <url>http://maven.apache.org</url>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.10</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.3.10</version>
        </dependency>

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.7</version>

        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.7</version>

        </dependency>


    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <!-- 配置項目中需要的資源檔案,不加這個配置, application.xml不會被編譯打包 -->
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
</project>
           

3.2. application.xml Spring配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd
		http://www.springframework.org/schema/aop
 		http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	<!-- 開啟包掃描 -->
	<context:component-scan base-package="com.wanshi.week1"></context:component-scan>
	
	<!-- 開啟AOP注解 -->
	<aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>
	
</beans>
           

這個spring的配置資訊也很簡單,就做了兩件事:

1 context:component-scan  : 掃描哪些包下的類。這個掃描的目的就是檢索 哪些類上加了 Spring支援的注解。

2 aop:aspectj-autoproxy  : 開啟了AOP注解支援,配置了這個,就可以通過注解方式進行AOP注入了。

既然有注解方式的AOP注入,就一定有配置檔案方式進行AOP注入的,有興趣的朋友請自行百度,我沒計劃寫配置檔案方式實作AOP的文章。

3.3. TestTarget類檔案

package com.wanshi.week1.target;

import org.springframework.stereotype.Component;

@Component(value="testTarget")
public class TestTarget {

	public void target1() {
		System.out.println("run target1");
	}

	public void target2() {
		System.out.println("run target2");
	}

	public void target3() throws Exception {

		String a="a12";
		
		try{
			Integer i = Integer.valueOf(a);
			System.out.println(i);
		}catch(Exception err){
			throw err;
		}
		
		
	}

}
           

這個類定義了3個方法,前兩個方法不說了。

target3() 方法,嘗試将字元串 "a12" 轉換為整型,一定會抛出異常。 這個方法出現異常後,會執行 AopEnhance.enhance3方法。

3.4. AopEnhance類檔案

package com.wanshi.week1.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AopEnhance {

	@Before("execution(* com.wanshi.week1.target.TestTarget.target1(..))")
	public void enhance1() {
		System.out.println("=== run enhance1 ");
	}

	@Around("execution(* com.wanshi.week1.target.TestTarget.target2(..))")
	public void enhance2( ProceedingJoinPoint pjp ) {

		System.out.println("=== start Around ");
		try {
			pjp.proceed();
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("=== end Around");
	}

	@AfterThrowing(pointcut="execution(* com.wanshi.week1.target.TestTarget.target3(..))", throwing="ex")
	public void enhance3(JoinPoint pjp,Throwable ex) {
		String classname = pjp.getTarget().getClass().getName();//出錯類名
	    String methodname = pjp.getSignature().getName();//出錯方法名
	    String exname = ex.getClass().getName();//異常名稱
	    String exmsg = ex.getMessage();//異常資訊
	    
	    System.out.println("=== 執行"+classname+"."+methodname);
		System.out.println("=== 出現異常:"+exname +" ; 異常資訊:"+ exmsg );
	}

}
           

這個類要好好說說,整個項目就這個類重要。

3.4.1. enhance1方法的@Before注解

@Before("execution(* com.wanshi.week1.target.TestTarget.target1(..))")
public void enhance1() {
	System.out.println("=== run enhance1 ");
}
           

@Before注解的意思是當某個目标方法執行之前,會先執行目前方法。

那麼目标方法是哪個呢?從該注解的參數可以分析 當執行(execution) 的方法可以比對 * com.wanshi.week1.target.TestTarget.target1(..) 的時候,就會執行該方法。

第一個 * 表示傳回值是任何類型的都行。

target1(..) 裡面的兩個點兒表示,有參數或者沒有參數,或者任何參數類型都行。

注意:這個AOP比對表達式還可以使用更多的通配符,具體的寫法可以自行百度。

是以,寫了這個注解後,當執行 target1 方法前,會先執行 enhance1 方法

3.4.2 enhance2方法的@Around注解

@Around("execution(* com.wanshi.week1.target.TestTarget.target2(..))")
	public void enhance2( ProceedingJoinPoint pjp ) {

		System.out.println("=== start Around ");
		try {
			pjp.proceed();
		} catch (Throwable e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		System.out.println("=== end Around");
	}
           

@Around注解是環繞。

環繞的意思是,當執行目标方法的時候,切面會攔截目标方法的執行,轉而交由切面方法決定什麼時候執行目标方法,這樣切面方法就可以在目标方法執行前和執行後分别執行一些自定義的程式。

具體本例子中,在切面方法 enhance2 攔截了 target2方法的執行,然後先列印了一行 文字(=== start Around),然後又列印了一行文字(=== end Around) 。

由于切面方法可以在目标方法之前和之後分别執行一些代碼,這就形成了環繞的效果,是以實作了環繞切面。

3.4.3 enhance3方法的@AfterThrowing注解

@AfterThrowing(pointcut="execution(* com.wanshi.week1.target.TestTarget.target3(..))", throwing="ex")
	public void enhance3(JoinPoint pjp,Throwable ex) {
		String classname = pjp.getTarget().getClass().getName();//出錯類名
	    String methodname = pjp.getSignature().getName();//出錯方法名
	    String exname = ex.getClass().getName();//異常名稱
	    String exmsg = ex.getMessage();//異常資訊
	    
	    System.out.println("=== 執行"+classname+"."+methodname);
		System.out.println("=== 出現異常:"+exname +" ; 異常資訊:"+ exmsg );
	}
           

@AfterThrowing 當目标方法出現異常的時候,才會執行。

@AfterThrowing 有一個參數 throwing ,這個參數是異常對象的命名,必須與 enhance3方法中的形參 Throwable 的命名一緻。否則會無法注入異常,這是因為所有的注入都是基于反射的,而反射是無法擷取方法的參數命名的。

這個方法将拿到目标類、目标方法、以及異常對象,并列印一行異常資訊。

3.5. AopTest類檔案

這是一個測試類

package com.wanshi.week1;

import com.wanshi.week1.target.TestTarget;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopTest
{
    public static void main( String[] args )
    {
        // 加載spring的配置檔案 application.xml 檔案
        ClassPathXmlApplicationContext context=new ClassPathXmlApplicationContext("classpath:application.xml");
        context.start();
        
        // 從spring中擷取 testTarget對象
        TestTarget testTarget = (TestTarget) context.getBean("testTarget");
        
        try {
            // 執行測試,分别執行 target1 target2 target3 這三個方法
            testTarget.target1();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			
		}
    }
}
           

這個方法就是分别調用 三個方法,完成測試執行。

4. 測試執行

4.1 執行 target1 方法

執行結果如下:

=== run enhance1 
run target1
           

可以看到 在 run target1 之前,先執行了 === run enhance1 。

即在 target1 方法之前,先執行了 enhance1 方法。

4.2 執行 target2 方法

執行結果

=== start Around 
run target2
=== end Around
           

可以看到,在 run target2 之前和之後,分别列印了一行内容。

這就實作了環繞切面。

4.3 執行 target3 方法

執行結果:

=== 執行com.wanshi.week1.target.TestTarget.target3
=== 出現異常:java.lang.NumberFormatException ; 異常資訊:For input string: "a12"
           

target3 代碼會出現異常,然後 enhance3 方法捕獲到了異常,并列印出異常資訊。

5. 寫在最後的話

文章就寫到這裡了,希望對您有幫助。