目錄
一、關于SpringAOP
1、AOP 架構
2、AOP 術語
3、通知的類型
二、基于 AOP 的 XML架構
1、代碼示例
2、Aspectj切入點execution()文法定義
三、基于 AOP 的 @AspectJ
四、使用環繞通知
一、關于SpringAOP
1、AOP 架構
Spring 架構的一個關鍵元件是面向切面的程式設計(AOP)架構。面向切面的程式設計需要把程式邏輯分解成不同的部分稱為所謂的關注點。跨一個應用程式的多個點的功能被稱為橫切關注點,這些橫切關注點在概念上獨立于應用程式的業務邏輯。有各種各樣的常見的很好的方面的例子,如日志記錄、審計、聲明式事務、安全性和緩存等。
在 OOP 中,關鍵單元子產品度是類,而在 AOP 中單元子產品度是面。依賴注入幫助你對應用程式對象互相解耦和 AOP 可以幫助你從它們所影響的對象中對橫切關注點解耦。AOP 是像程式設計語言的觸發物,如 Perl,.NET,Java 或者其他。
Spring AOP 子產品提供攔截器來攔截一個應用程式,例如,當執行一個方法時,你可以在方法執行之前或之後添加額外的功能。
2、AOP 術語
在我們開始使用 AOP 工作之前,讓我們熟悉一下 AOP 概念和術語。這些術語并不特定于 Spring,而是與 AOP 有關的。
3、通知的類型
Spring 方面可以使用下面提到的五種通知工作:
二、基于 AOP 的 XML架構
1、代碼示例
使用切面的步驟:
(1)聲明切面:如:<aop:aspect id="myAspect" ref="aBean"></aop:aspect>
(2)申明切入點:命名切入點,定義比對規則
如:<aop:pointcut id="businessService" expression="execution(* com.xyz.myapp.service.*.*(..))"/>
(3)申明通知:定義在什麼時候觸發切面中的特定方法
如:<aop:before pointcut-ref="businessService" method="doRequiredTask"/>
本文采用SpringBoot架構,使用切面首先需要引入以下依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
代碼示範:
1、首先定義一個切面Bean:Logging.java
public class Logging { // 定義切面
/**
* This is the method which I would like to execute
* before a selected method execution.
*/
public void beforeAdvice(){
System.out.println("切面方法-beforeAdvice執行...");
}
/**
* This is the method which I would like to execute
* after a selected method execution.
*/
public void afterAdvice(){
System.out.println("切面方法-afterAdvice執行...");
}
/**
* This is the method which I would like to execute
* when any method returns.
*/
public void afterReturningAdvice(Object retVal){
System.out.println("切面方法-Returning入參:" + retVal.toString() );
}
/**
* This is the method which I would like to execute
* if there is an exception raised.
*/
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("切面方法-抛出異常列印入參: " + ex.toString());
}
}
2、定義需要使用到切面的執行個體Bean:Student.java
public class Student {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
System.out.println("Age : " + age );
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
System.out.println("Name : " + name );
return name;
}
public void printThrowException(){
System.out.println("Student sout列印...");
throw new IllegalArgumentException();
}
}
3、使用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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<aop:config>
<!--申明一個切面,使用ref屬性引用支援的Bean-->
<aop:aspect id="log" ref="logging">
<!--聲明一個切入點命名為:selectAll,比對springdemo包下所有類中的所有方法-->
<aop:pointcut id="selectAll"
expression="execution(* springdemo.*.*(..))"/>
<aop:before pointcut-ref="selectAll" method="beforeAdvice"/>
<aop:after pointcut-ref="selectAll" method="afterAdvice"/>
<!--returning="retVal" 相當于傳參,傳回值傳入-->
<aop:after-returning pointcut-ref="selectAll"
returning="retVal"
method="afterReturningAdvice"/>
<aop:after-throwing pointcut-ref="selectAll"
throwing="ex"
method="AfterThrowingAdvice"/>
</aop:aspect>
</aop:config>
<!-- Definition for student bean -->
<bean id="student" class="springdemo.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>
<!-- Definition for logging aspect -->
<bean id="logging" class="springdemo.Logging"/>
</beans>
4、編寫測試程式
public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
Student student = (Student) context.getBean("student");
student.getName();
student.getAge();
student.printThrowException();
}
}
測試結果:
2、Aspectj切入點execution()文法定義
例:定義切入點表達式 execution(* com.sample.service.impl..*.*(..))
execution()是最常用的切點函數,其文法如下所示:
整個表達式可以分為五個部分:
1、execution(): 表達式主體。
2、第一個*号:表示傳回類型,*号表示所有的類型。
3、包名:表示需要攔截的包名,後面的兩個句點表示目前包和目前包的所有子包,com.sample.service.impl包、子孫包下所有類的方法。
4、第二個*号:表示類名,*号表示所有的類。
5、*(..):最後這個星号表示方法名,*号表示所有的方法,後面括弧裡面表示方法的參數,兩個句點表示任何參數。
注:表達式支援比對多個
如 : "execution(* com.ws..*.*(*)) || execution(* com.db..*.*(*))";
三、基于 AOP 的 @AspectJ
使用@AspectJ注解,需要開啟注解支援 ???
<aop:aspectj-autoproxy/>
代碼示例:
1、使用注解定義切面、切入點和通知
@Aspect // 聲明一個切面
@Component // springboot中替代xml檔案開啟注解支援
public class Logging {
/**
* 申明一個切點
*/
@Pointcut("execution(* com.swadian.annotation..*.*(..))")
private void selectAll(){}
/**
* 前置通知
*/
@Before("selectAll()")
public void beforeAdvice(){
System.out.println("Going to setup student profile.");
}
/**
* 後置通知
*/
@After("selectAll()")
public void afterAdvice(){
System.out.println("Student profile has been setup.");
}
/**
* 傳回通知
*/
@AfterReturning(pointcut = "selectAll()", returning="retVal")
public void afterReturningAdvice(Object retVal){
if(retVal != null){
System.out.println("Returning:" + retVal.toString() );
}
}
/**
* 異常通知
*/
@AfterThrowing(pointcut = "selectAll()", throwing = "ex")
public void AfterThrowingAdvice(IllegalArgumentException ex){
System.out.println("There has been an exception: " + ex.toString());
}
}
2、定義需要使用到切面的執行個體Bean:Student.java
public class Student {
private Integer age;
private String name;
public void setAge(Integer age) {
this.age = age;
}
public Integer getAge() {
System.out.println("Age : " + age );
return age;
}
public void setName(String name) {
this.name = name;
}
public String getName() {
System.out.println("Name : " + name );
return name;
}
public void printThrowException(){
System.out.println("Student sout列印...");
throw new IllegalArgumentException();
}
}
3、使用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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!--申明@AspectJ支援是可用的-->
<aop:aspectj-autoproxy/>
<!-- Definition for student bean -->
<bean id="student" class="springdemo.Student">
<property name="name" value="Zara" />
<property name="age" value="11"/>
</bean>
<!-- Definition for logging aspect -->
<bean id="logging" class="springdemo.Logging"/>
</beans>
4、編寫測試類
public class MainApp {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
Student student = (Student) context.getBean("student");
student.getName();
student.getAge();
student.printThrowException();
}
}
測試結果:
四、使用環繞通知
1、建立Login注解和登陸服務方法
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Login {
String userName() default "遊客";
String passWord();
}
public class LoginService{
@Login(userName = "Lucy",passWord = "XXX")
public void loginService(){
System.out.println("使用者認證後,方法執行...");
}
}
2、使用環繞通知解析登陸方法的注解
@Aspect // 定義一個切面
public class LoggerAop {
// 定義一個環繞通知,直接跳過定義切面點的步驟
@Around("execution(* aopdemo.*.loginService(..))")
public void recordLog(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("切面使用者資訊認證start...");
//擷取method 操作方法
//首先擷取ProceedingJoinPoint 簽名 (方法有簽名 方法名稱 傳回值類型 參數類型 及 參數個數)
Signature signature = pjp.getSignature();
if(signature instanceof MethodSignature){
// 如果是,進行強轉
MethodSignature methodSignature = (MethodSignature) signature;
// 獲知方法
Method method = methodSignature.getMethod();
// 判斷注解是不是登陸注解
if(method.isAnnotationPresent(Login.class)){
// 如果是,擷取注解
Login annotation = method.getAnnotation(Login.class);
// 使用注解上的值進行驗證
if(annotation.passWord().equals("XXX")){
// 密碼校驗通過,執行方法
System.out.println("切面使用者資訊認證pass...");
pjp.proceed();// 調用業務層方法
System.out.println("方法執行後,切面continue...");
}else{
// 否則抛出一個異常,這裡随便抛的
throw new NullPointerException();
}
}
}
}
}
可以看到在上面的代碼中,定義通知的時候在通知方法中添加了入參:ProceedingJoinPoint。在建立環繞通知的時候,這個參數是必須寫的。因為在需要在通知中使用ProceedingJoinPoint.proceed()方法調用被通知的方法。
另外,如果忘記調用proceed()方法,那麼通知實際上會阻塞對被通知方法的調用。
3、建立配置檔案Bean.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:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd ">
<!--申明@AspectJ支援是可用的-->
<aop:aspectj-autoproxy/>
<!-- Definition for student bean -->
<bean id="loginService" class="aopdemo.LoginService"></bean>
<!-- Definition for logging aspect -->
<bean id="logging" class="aopdemo.LoggerAop"/>
</beans>
4、編寫執行程式,通過上下文(ClassPathXmlApplicationContext)加載配置檔案
public class MainApp {
public static void main(String[] args) {
// 代碼加載配置檔案
ApplicationContext context =
new ClassPathXmlApplicationContext("Beans.xml");
LoginService login = (LoginService) context.getBean("loginService");
login.loginService();
}
}
5、示範結果