【Spring】Spring之初識AOP

一、AOP的概念

AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,Spring提供了面向切面編程的豐富支持,允許通過分離應用的業務邏輯與系統級服務進行內聚性的開發。

二、AOP的相關術語

JoinPoint(連接點):可以被攔截到的方法;
Pointcut(切入點):在開發中真正被攔截到的方法;
Advice(通知/增強):在切入點執行的時候觸發的其他方法,如權限校驗、事務管理等;
Introduction(引介):與Advice是方法層面的增強不同的是,Introduction是類層面的增強;
Target(目標):被增強的對象;
Weaving(織入):將通知(Advice)應用到目標(Target)的過程;
Proxy(代理):將通知(Advice)應用到目標(Target)之後產生的代理對象;
Aspect(切面):多個通知(Advice)和切入點(Pointcut)的組合。

三、AOP作用

AOP可以用於權限校驗、日誌輸出、性能監控、異常處理和事務管理等。應用對象只實現它們應該做的——完成業務邏輯——僅此而已。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,簡化代碼的同時提高了開發的效率。假如不使用AOP,例如下面的業務邏輯層代碼:

package cn.jingpengchong.calculator.service;

import org.springframework.stereotype.Service;

@Service
public class CalculatorService implements ICalculatorService {

	public int div(int a, int b) {
		System.out.println("日誌:The div method begins");
		System.out.println("日誌:The div method args:["+a+","+b+"]");
		return a/b;
	}
}

該例沒有使用Spring的AOP功能,因此其主要方法只有“return a/b;”這一句,然而爲了打印日誌信息,卻需要兩行代碼,顯得特別臃腫。並且如果要是再擴展一個乘法功能,那麼仍然需要寫這兩行代碼,這是非常繁瑣的。那麼如何解決這麼繁瑣的事情呢?當然就是使用spring提供的AOP功能了。
使用Spring的基於Aspectj的AOP開發需要的jar包有:
1、Spring所需的基本的jar包:
在這裏插入圖片描述
2、Spring日誌輸出規範包:
在這裏插入圖片描述
3、Log4j的相關jar包:
在這裏插入圖片描述
4、Spring的AOP的jar包:
在這裏插入圖片描述
5、AOP聯盟的jar包:
在這裏插入圖片描述
6、Aspectj的jar包:
在這裏插入圖片描述
7、Spring與Aspectj的整合包:
在這裏插入圖片描述

四、利用註解方式使用AOP

將所需要的jar包導入當前工程之後,首先在xml配置文件中加入“<aop:aspectj-autoproxy/>”標籤,其次編寫切面Aspect和通知Advice,在類前面添加@Aspect註解就告知了Spring該類是一個切面,在方法前面添加對應通知的註解就表明該方法是一個通知(或叫做增強),該註解的value屬性聲明的表達式匹配到的方法被稱爲切入點,當這些方法得執行時會觸發其所對應的通知。

1、切入點前的註解有以下幾種:
  • @Before(value=""):前置處理,用於在該註解匹配的方法執行前進行一些操作;
  • @After(value=""):後置處理,用於在該註解匹配的方法執行後進行一些操作;
  • @AfterReturning(value="",returning=""):返回處理,用於在該註解匹配的方法返回結果後進行一些操作;
  • @AfterThrowing(value="",throwing=""):異常處理,用於在該註解匹配的方法返回異常後進行一些操作;
  • @Around(value=""):環繞處理,可以根據需求自編代碼在相關環節進行處理。

注:

  • value屬性用於匹配要處理的方法;
  • returning屬性用於接收返回值;
  • throwing屬性用於接收異常對象;
  • @After與@AfterReturning的區別:
    • @After註解的方法先於@AfterReturning註解的方法執行;
    • @在遇到異常時,After註解的方法仍執行,而@AfterReturning註解的方法不執行。
2、例:分別使用@Before、@After、@AfterReturning和@Around註解對上面的service層方法執行的各個環節進行處理

a.將service層方法打印日誌的代碼刪了

package cn.jingpengchong.calculator.service;

import org.springframework.stereotype.Service;

@Service
public class CalculatorService implements ICalculatorService {

	public int div(int a, int b) {
		return a/b;
	}
}

b.在spring xml文件中添加該標籤:自動代理

<aop:aspectj-autoproxy/>

c.新建獨立模塊“cn.jingpengchong.aspect”,在其中新建文件“CalculatorAspect.java”如下:

package cn.jingpengchong.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class CalculatorAspect {

	//前置處理
	@Before("execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))")
	public void before(JoinPoint jp) {
		Object[] args = jp.getArgs();
		Signature signature = jp.getSignature();
		String name = signature.getName();
		System.out.println("日誌:The " + name + " method begins");
		System.out.println("日誌:The " + name + " method args:[" + args[0] + "," + args[1] + "]");
	}
	
	//後置處理
	@After("execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))")
	public void after(JoinPoint jp) {
		Signature signature = jp.getSignature();
		String name = signature.getName();
		System.out.println("日誌:The " + name + " method ends");
	}
	
	//返回處理
	@AfterReturning(value = "execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))", returning = "result")
	public void afterReturning(JoinPoint jp, Object result) {
		Signature signature = jp.getSignature();
		String name = signature.getName();
		System.out.println("日誌:The " + name + " method result:" + result);
	}
	
	//異常處理
	@AfterThrowing(value = "execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))", throwing = "e")
	public void afterThrowing(JoinPoint jp, Exception e) {
		Signature signature = jp.getSignature();
		String name = signature.getName();
		System.out.println("日誌:The " + name + " method exception:" + e);
	}
}

d.編寫測試類:

package cn.jingpengchong.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.jingpengchong.calculator.service.ICalculatorService;

public class Test {
	
	public static void main(String[] args) {
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
		ICalculatorService calculatorService = applicationContext.getBean(ICalculatorService.class);
		System.out.println(calculatorService.div(6, 2));
		applicationContext.close();
	}
}

運行結果如下:
在這裏插入圖片描述

附:

1、上面“CalculatorAspect.java”文件中每個方法都要寫帶有很長參數的註解,我們可以在該文件中將切入點的表達式提取出來放在一個空方法上,其作用類似於一個全局變量:

@Pointcut("execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))")
public void pointCut() {		
}

這樣的話上面的註解的參數就可以寫成“pointCut()”,進一步簡化了代碼。
2、如果覺得在“CalculatorAspect.java”文件中定義四個方法太繁瑣,那麼可以用環繞註解@Around將處理過程封裝在一個方法中:

//環繞處理
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) {
	Object result = null;
	Object[] args = pjp.getArgs();
	String name = pjp.getSignature().getName();
	try {
		try {
			//前置處理
			System.out.println("The " + name + " method begins");
			System.out.println("The " + name + " method args:[" + args[0] + "," + args[1] + "]");
			result = pjp.proceed();
		}finally {
			//後置處理
			System.out.println("The " + name + " method ends");
		}
		//返回處理
		System.out.println("The " + name + " method result:" + result);
	} catch (Throwable e) {
		//異常處理
		System.out.println("The " + name + " method exception:" + e);
	}
	return result;
}

五、利用Spring的xml配置文件使用AOP

同樣需要將所需jar包導入當前工程

1、仍然以處理該service層方法的日誌爲例:
package cn.jingpengchong.calculator.service;

import org.springframework.stereotype.Service;

@Service
public class CalculatorService implements ICalculatorService {
	public int div(int a, int b) {
		return a/b;
	}
}
2、新建獨立模塊“cn.jingpengchong.aspect”,在其中新建文件“LogAspect.java”如下:
package cn.jingpengchong.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;

public class LogAspect {

	public void before(JoinPoint jp) {
		Object[] args = jp.getArgs();
		Signature signature = jp.getSignature();
		String name = signature.getName();
		System.out.println("日誌:The " + name + " method begins");
		System.out.println("日誌:The " + name + " method args:[" + args[0] + "," + args[1] + "]");
	}
}
3、在spring的xml配置文件中做如下配置:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<context:component-scan base-package="cn.jingpengchong"></context:component-scan>
 
	<bean id = "logAspect" class = "cn.jingpengchong.aspect.LogAspect"/>
	
	<aop:config>
		<!--該標籤是爲了簡化代碼,指定一個id來表示該表達式,下面再引用時,直接用該id就行了-->
		<aop:pointcut expression="execution(public int cn.jingpengchong.calculator.service.CalculatorService.*(..))" id="pointCut"/>
		<aop:aspect ref="logAspect">
			<!--以前置處理爲例,其他處理類似:
			method表明觸發哪個通知;
			pointcut-ref引入表達式;
			pointcut生命表達式-->
			<aop:before method="before" pointcut-ref="pointCut"/>
		</aop:aspect>
	</aop:config>
</beans>
4、編寫測試類:
package cn.jingpengchong.test;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import cn.jingpengchong.calculator.service.ICalculatorService;

public class Test {
	
	public static void main(String[] args) {
		ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
		ICalculatorService calculatorService = applicationContext.getBean(ICalculatorService.class);
		System.out.println(calculatorService.div(6, 2));
		applicationContext.close();
	}
}

運行結果如下:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章