Spring5.0源碼學習系列之Spring AOP簡述
在學習Spring AOP源碼之前,您是否對AOP有足夠熟悉的理解?在對應(yīng)用都不熟悉之前就去學習源碼,肯定是很難理解的,所以本文先不描述源碼的實現(xiàn),先通過本篇博客了解熟悉Spring AOP,然后再學習源碼
1、什么是AOP技術(shù)?
引用Spring官網(wǎng)對AOP技術(shù)的概述:
Aspect-Oriented Programming (AOP) complements Object-Oriented Programming (OOP) by providing another way of thinking about program structure. The key unit of modularity in OOP is the class, whereas in AOP the unit of modularity is the aspect. Aspects enable the modularization of concerns such as transaction management that cut across multiple types and objects. (Such concerns are often termed crosscutting concerns in AOP literature.)
挑重點來說,所謂AOP(Aspect-Oriented Programming)也即面向方面的編程,是指通過跨領(lǐng)域關(guān)注點的分離來實現(xiàn)模塊化的一種面向?qū)ο蠹夹g(shù)。
跨領(lǐng)域也即跨多種類型和對象的事務(wù)管理等等;
關(guān)注點通常被稱為橫切關(guān)注點,OOP中模塊化的關(guān)鍵單元是類,而在AOP中模塊化是方面關(guān)注點通常被稱為橫切關(guān)注點
2、AOP的本質(zhì)目的
AOP本質(zhì):在不改變原有業(yè)務(wù)邏輯的情況下增強橫切邏輯,這個橫切邏輯可以是權(quán)限校驗邏輯、日志監(jiān)控、事務(wù)控制等等
AOP相關(guān)知識詳情可以參考:Spring AOP官方文檔
3、AOP的相關(guān)術(shù)語
| 名詞 | 描述 |
|---|---|
| 連接點(Joinpoint) | 連接點是一個程序的執(zhí)行,如方法的執(zhí)行或異常的處理過程中的一個點 |
| 切入點(Pointcut) | 指的是將增強代碼織入到業(yè)務(wù)主線進來之后的連接點 |
| 通知/增強(Advice) | Advice可以翻譯為通知或者增強,指的是切面類中用于提供增強功能的方法 |
| 目標對象(Target) | 指代理的目標對象,即被代理對象 |
| 代理(Proxy) | 指一個類被AOP織入增強之后,產(chǎn)生的代理類,即代理對象 |
| 織入(Weaving) | 指的是將增強(Advice)應(yīng)用到目標對象(Target)產(chǎn)生代理對象(Proxy)的過程。ps:AspectJ采用的是編譯期織入和類裝載期織入,而Spring AOP采用的是動態(tài)代理織入 |
| 切面(Aspect) | 切面也就是AOP的關(guān)注點,也就是說是Advice代碼的關(guān)注點,切面是對上述概念的一個綜合。將這些Advice代碼放在一個類中,這個類就是切面類,切面類是跨多個類的關(guān)注點的模塊化類 |
看了前面這些理論,您可能不是很理解,所以引用國外網(wǎng)站的圖例進行說明AOP概念,圖來自鏈接
綜上所述,其實所謂的目的其實只是要鎖定在某個切入點(Pointcut)織入(Weaving)特定的增強邏輯(Advice)
4、Spring AOP和AspectJ
有了前面對AOP概念的概述之后,我們能夠大致理解AOP了,不過本文還是要理理Spring AOP和AspectJ的關(guān)系

Spring官網(wǎng)給指出了Spring AOP和AspectJ的關(guān)系,官網(wǎng)明確指出其立場,表明Spring AOP不會和AspectJ項目競爭哪個項目能提供更成熟的AOP解決方案,Spring AOP和AspectJ是一種互補的關(guān)系,Spring AOP無縫結(jié)合了IOC和AspectJ,從而使Spring AOP能夠符合大部分的AOP需求,這是一種能提供代理對象的模式
從官網(wǎng)也可以知道了Spring AOP和AspectJ的大致關(guān)系,其實這是兩種AOP的實現(xiàn)技術(shù),Spring AOP是集成了AspectJ部分功能,同時結(jié)合IOC實現(xiàn)的,AspectJ則是一種比較完善成熟的AOP解決方案,接著本文做下簡單對比
Spring AOP
Spring AOP是SpringFramework的組件,屬于Springframework的一個比較核心的功能,Spring AOP是結(jié)合了AspectJ和IOC實現(xiàn)的,提供了AspectJ的功能
Spring AOP 致力于解決的是企業(yè)級開發(fā)中最普遍的 AOP 需求(方法織入),Spring AOP不會和AspectJ競爭
Spring AOP 只能作用于 Spring 容器中的 Bean
性能方面,Spring AOP是在運行時進行動態(tài)織入的,所以性能比不上AspectJ
Spring AOP使用動態(tài)代理的方式進行方法織入,有兩種動態(tài)代理方法,一種是CGLIB,另外一種是JDK提供的動態(tài)代理
引用https://www.baeldung.com/spring-aop-vs-aspectj的圖進行說明
AspectJ
AspectJ來自Eclipse的開源項目,鏈接:https://www.eclipse.org/aspectj
AspectJ是一種比較成熟的AOP解決方案,能夠提供比Spring AOP更多的AOP功能
AspectJ的方法織入屬于靜態(tài)織入,它的織入時機可以是:compile-time(編譯期)、post-compile(編譯后)、load-time(JVM類加載器加載時候)
AspectJ在編譯時進行方法織入,所以性能比Spring AOP好
ok,前面已經(jīng)簡單列舉了Spring AOP和AspectJ的主要不同,現(xiàn)在可以用表格列舉出不同點對比,表格參考自國外網(wǎng)站:
| 對比 | Spring AOP | AspectJ |
|---|---|---|
| 實現(xiàn)語言 | 使用存Java語言 | 使用Java編程語言的擴展實現(xiàn) |
| 編譯過程 | 無需單獨的編譯過程 | 除非設(shè)置了LTW,否則需要AspectJ編譯器(ajc) |
| 織入時機 | 動態(tài)代理,在運行時織入 | 靜態(tài)織入,在編譯過程織入,它的織入時機可以是:compile-time(編譯期)、post-compile(編譯后)、load-time(JVM類加載器加載時候) |
| 功能 | 基本的方法織入 | 可以編織字段,方法,構(gòu)造函數(shù),靜態(tài)初始值設(shè)定項,最終類/方法等… |
| 范圍 | 只能作用于Spring容器管理的bean上 | 可以作用于所有領(lǐng)域?qū)ο笊蠈嵤?/td> |
| 性能 | 比AspectJ慢得多 | 更好的性能(編譯時編織比運行時編織要快得多) |
| 學習 | 易于學習和應(yīng)用 | 比Spring AOP復(fù)雜 |
補充,AspectJ靜態(tài)織入時機:
compile-time weaving:編譯期織入,在編譯時候就直接進行方法織入,直接編譯出包含織入代碼的 .class 文件
post-compile weaving:編譯后織入,也可以稱之為二進制織入,它將Advice織入于編織后現(xiàn)有的類文件和JAR文件
Load-time weaving(LTW):指的是在加載類的時候進行織入,與以前的二進制編織完全一樣,不同之處在于編織被推遲到類加載器將類文件加載到JVM的過程
5、Spring中AOP代理選擇
在前面知識,我們知道Spring AOP是使用動態(tài)代理技術(shù)實現(xiàn)Spring AOP中的代理選擇,方法織入實現(xiàn)有兩種方法,一種是JDK動態(tài)代理,一種是CGLIB
默認情況下,Spring框架會根據(jù)被代理對象(Target Object)是否實現(xiàn)接口來選擇JDK還是CGLIB。如果被代理對象沒有實現(xiàn)任何接口,Spring會選擇CGLIB,如果被代理對象有實現(xiàn)接口,Spring會選擇JDK提供的動態(tài)代理。
ps:雖然默認情況是這樣的,不過我們可以通過配置的方式來自定義選擇動態(tài)代理方式
6、實驗環(huán)境準備參考
學習了前面的理論知識之后,現(xiàn)在可以通過例子進行實踐,實踐之前,您需要如下的環(huán)境準備,實驗環(huán)境參考:
SpringFramework版本
Springframework5.0.x
開發(fā)環(huán)境
JAR管理:gradle 4.9/ Maven3.+
開發(fā)IDE:IntelliJ IDEA 2018.2.5
JDK:jdk1.8.0_31
Git Server:Git fro window 2.8.3
Git Client:SmartGit18.1.5(可選)
7、Spring AOP實現(xiàn)方式
在Spring AOP中,主要提供了三種配置方式:
Spring1.2 基于接口的配置:Spring最早的AOP實現(xiàn)是基于Spring提供的AOP接口實現(xiàn)的,通過實現(xiàn)接口,進行Advice邏輯代碼編寫等等
Spring2.0+ schema-based 配置 :Spring2.0之后,提供了 schema-based 配置,也就是xml類型的配置,使用命名空間
Spring2.0+ @Aspect配置:Spring2.0之后,也提供了
@Aspect這種方法,@Aspect是用AspectJ的jar,但是實現(xiàn)是Spring AOP自己實現(xiàn)的
8、Spring AOP例子參考
前面介紹了Spring AOP實現(xiàn)的三種方式,接著本文通過代碼例子進行驗證:
maven配置
5.0.19.RELEASE
1.9.4
org.springframework
spring-aop
${springframework.version}
org.springframework
spring-context
${springframework.version}
org.aspectj
aspectjweaver
${aspectj.version}
8.1、Spring1.2 基于接口的配置
8.1.1、基礎(chǔ)類編寫
User.java
package com.example.spring.aop.bean;
public class User {
private String username;
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
UserService .java:
package com.example.spring.aop.service;
import com.example.spring.aop.bean.User;
/**
*
* UserService
*
*
*
* @author mazq
* 修改記錄
* 修改后版本: 修改人:修改日期: 2020/11/20 18:02 修改內(nèi)容:
*
*/
public interface UserService {
User addUser(User user);
User getUser();
}
UserServiceImpl .java
package com.example.spring.aop.service.impl;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.service.UserService;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
/**
*
* UserServiceImpl
*
*
*
* @author mazq
* 修改記錄
* 修改后版本: 修改人:修改日期: 2020/11/20 17:57 修改內(nèi)容:
*
*/
@Service
public class UserServiceImpl implements UserService {
private static User user = null;
@Override
public User addUser(User userDto) {
user = new User();
BeanUtils.copyProperties(userDto,user);
return user;
}
@Override
public User getUser() {
return user;
}
}
8.1.2、使用Advice接口
LogMethodBeforeAdvice .java
package com.example.spring.aop.core.advice;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
*
* LogMethodBeforeAdvice
*
*
*
* @author mazq
* 修改記錄
* 修改后版本: 修改人:修改日期: 2020/11/20 17:38 修改內(nèi)容:
*
*/
public class LogMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println(String.format("執(zhí)行方法:%s,參數(shù)列表:%s", method.getName(), Arrays.toString(args) ));
}
}
LogAfterReturningAdvice .java
package com.example.spring.aop.core.advice;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
/**
*
* LogAfterReturningAdvice
*
*
*
* @author mazq
* 修改記錄
* 修改后版本: 修改人:修改日期: 2020/11/20 17:41 修改內(nèi)容:
*
*/
public class LogAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println(String.format("方法返回:%s", returnValue ));
}
}
spring_interfaces_config.xml:
xmlns ="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
id ="userServiceTarget" class="com.example.spring.aop.service.impl.UserServiceImpl">
id ="logMethodBeforeAdvice" class="com.example.spring.aop.core.advice.LogMethodBeforeAdvice">
id = "logAfterReturningAdvice" class="com.example.spring.aop.core.advice.LogAfterReturningAdvice">
id ="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
name ="proxyInterfaces">
com.example.spring.aop.service.UserService
name ="target" ref="userServiceTarget">
name ="interceptorNames">
logMethodBeforeAdvice
logAfterReturningAdvice
TestApplication.java:
package com.example.spring.aop;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestApplication {
public static void testAopProxy() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:spring_interfaces_config.xml");
UserService userService = (UserService) ioc.getBean("userServiceProxy");
User userDto = new User();
userDto.setUsername("tom");
userDto.setPassword("11");
userService.addUser(userDto);
System.out.println(String.format("用戶數(shù)據(jù)打印:%s",userService.getUser().toString()));
}
public static void main(String[] args) {
testAopProxy();
}
}
執(zhí)行方法:addUser,參數(shù)列表:[User{username='tom', password='11'}]
方法返回:User{username='tom', password='11'}
執(zhí)行方法:getUser,參數(shù)列表:[]
方法返回:User{username='tom', password='11'}
用戶數(shù)據(jù)打印:User{username='tom', password='11'}
8.1.3、使用Advisor接口
NameMatchMethodPointcutAdvisor使用
定義一個只會攔截查詢方法的Advisor,修改配置:
id ="logOnlyObtainQueryAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
name ="advice" ref="logMethodBeforeAdvice">
name ="mappedNames" value="getUser">
id ="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
name ="proxyInterfaces">
com.example.spring.aop.service.UserService
name ="target" ref="userServiceTarget">
name ="interceptorNames">
logOnlyObtainQueryAdvisor
執(zhí)行方法:getUser,參數(shù)列表:[]
用戶數(shù)據(jù)打印:User{username='tom', password='11'}
RegexpMethodPointcutAdvisor使用
前面介紹了NameMatchMethodPointcutAdvisor,不過不夠通用,所以Spring aop還提供了RegexpMethodPointcutAdvisor,可以支持正則表達式
id ="regexpMethodAdvisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
name ="advice" ref="logMethodBeforeAdvice">
name ="pattern" value="com.example.spring.aop.*.service.*.get.*">
id ="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
name ="proxyInterfaces">
com.example.spring.aop.service.UserService
name ="target" ref="userServiceTarget">
name ="interceptorNames">
regexpMethodAdvisor
執(zhí)行方法:getUser,參數(shù)列表:[]
用戶數(shù)據(jù)打印:User{username='tom', password='11'}
8.1.4、Interceptor接口使用
TestMethodInterceptor .java:
package com.example.spring.aop.core.interceptor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
*
* TestMethodInterceptor
*
*
*
* @author mazq
* 修改記錄
* 修改后版本: 修改人:修改日期: 2020/11/23 10:28 修改內(nèi)容:
*
*/
public class TestMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println(String.format("方法調(diào)用前(before method invoke) :%s",methodInvocation));
Object implObj = methodInvocation.proceed();
System.out.println(String.format("方法調(diào)用后(after method invoke) :%s",implObj));
return implObj;
}
}
修改配置文件:
id ="methodInterceptor" class="com.example.spring.aop.core.interceptor.TestMethodInterceptor">
id ="userServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
name ="proxyInterfaces">
com.example.spring.aop.service.UserService
name ="target" ref="userServiceTarget">
name ="interceptorNames">
logMethodBeforeAdvice
logAfterReturningAdvice
methodInterceptor
挑addUser方法的日志信息:
方法調(diào)用前(before method invoke) :ReflectiveMethodInvocation: public abstract com.example.spring.aop.bean.User com.example.spring.aop.service.UserService.addUser(com.example.spring.aop.bean.User); target is of class [com.example.spring.aop.service.impl.UserServiceImpl]
方法調(diào)用后(after method invoke) :User{username='tom', password='11'}
8.1.5、beanNameAutoProxy使用
前面介紹了ProxyFactoryBean配置對應(yīng)的業(yè)務(wù)代理進行調(diào)用,不過不夠靈活,所以Spring中還提供了beanNameAutoProxy,這種方式是自動匹配beanName id,不需要每個業(yè)務(wù)都配對應(yīng)的proxy進行代理
新建一個新的配置文件,spring_beanNameAutoProxy_config.xml:
xmlns ="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
id ="userServiceTarget" class="com.example.spring.aop.service.impl.UserServiceImpl">
id ="logMethodBeforeAdvice" class="com.example.spring.aop.core.advice.LogMethodBeforeAdvice">
id = "logAfterReturningAdvice" class="com.example.spring.aop.core.advice.LogAfterReturningAdvice">
id ="methodInterceptor" class="com.example.spring.aop.core.interceptor.TestMethodInterceptor">
id ="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
name ="interceptorNames">
logMethodBeforeAdvice
logAfterReturningAdvice
methodInterceptor
name ="beanNames" value="*ServiceTarget">
TestApplication.java:
package com.example.spring.aop;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestApplication {
public static void testBeanNameAutoProxy() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:spring_beanNameAutoProxy_config.xml");
UserService userService = ioc.getBean(UserService.class);
User userDto = new User();
userDto.setUsername("tom");
userDto.setPassword("11");
userService.addUser(userDto);
System.out.println(String.format("用戶數(shù)據(jù)打印:%s",userService.getUser().toString()));
}
public static void main(String[] args) {
// BeanNameAutoProxyCreator
testBeanNameAutoProxy();
}
}
8.2、Spring2.0+ @Aspect配置
@Aspect這種方式是比較常用的,pom需要加上aspectjweaver配置,spring aop引用了aspectJ的api,但是實現(xiàn)是spring自己進行實現(xiàn)拓展的
8.2.1、啟用@AspectJ支持
注解方式,使用@EnableAspectJAutoProxy開啟
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
xml方式,可以使用開啟
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.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
8.2.2、聲明方面
xml方式:
id ="myAspect" class="org.xyz.NotVeryUsefulAspect">
java方式,使用注解@Aspect:
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
8.2.3、聲明切入點
切入點(Pointcut)的類型,Spring官網(wǎng)給出了比較詳情的介紹:
在官網(wǎng)的建議是聲明一個通用的SystemArchitecture:
package com.xyz.someapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.someapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.service..*)")
public void inServiceLayer() {}
/**
* A join point is in the data access layer if the method is defined
* in a type in the com.xyz.someapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.dao..*)")
public void inDataAccessLayer() {}
/**
* A business service is the execution of any method defined on a service
* interface. This definition assumes that interfaces are placed in the
* "service" package, and that implementation types are in sub-packages.
*
* If you group service interfaces by functional area (for example,
* in packages com.xyz.someapp.abc.service and com.xyz.someapp.def.service) then
* the pointcut expression "execution(* com.xyz.someapp..service.*.*(..))"
* could be used instead.
*
* Alternatively, you can write the expression using the 'bean'
* PCD, like so "bean(*Service)". (This assumes that you have
* named your Spring service beans in a consistent fashion.)
*/
@Pointcut("execution(* com.xyz.someapp..service.*.*(..))")
public void businessService() {}
/**
* A data access operation is the execution of any method defined on a
* dao interface. This definition assumes that interfaces are placed in the
* "dao" package, and that implementation types are in sub-packages.
*/
@Pointcut("execution(* com.xyz.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
因為官網(wǎng)的介紹比較詳細,所以本博客只挑部分比較重要的進行介紹:
execution
execution:執(zhí)行,這是最基本的切入點類型:
// 匹配UserService里的任何方法
@Pointcut("execution(* com.example.spring.aop.service.UserService.*(..))")
public void regexpExecution(){}
ps:第一個
*表示匹配任何返回值,第二個*表示匹配任何方法,(..)表示匹配任何數(shù)量的方法參數(shù)
eg:execution(* *..find*(Long,..))用于匹配方法名為find...,而第一個參數(shù)是long類型的
@Pointcut("within(com.example.spring.aop..*) && execution(* *..find*(Long,..))")
public void regexpExecutionByMethodName(){}
within
within:表示服務(wù)包中的任何連接點(僅在Spring AOP中執(zhí)行方法)
@Pointcut("within(com.example.spring.aop..*)")
this和target
前者在Spring AOP創(chuàng)建基于CGLIB的代理時起作用,而后者在創(chuàng)建基于JDK的代理時使用
如下實例代碼:
public class UserServiceImpl implements UserService {
//...
}
對于UserService,使用target,這種情況是基于CGLIB的代理
@Pointcut("target(com.example.spring.aop.service.UserService)")
對于UserService,使用this,這種情況是基于JDK的代理
@Pointcut("this(com.example.spring.aop.service.impl.UserServiceImpl)")
args
限制匹配點(參數(shù)是給定類型的實例)的連接點(使用Spring AOP時方法的執(zhí)行)@target
@target不要和target混淆了,其中類的執(zhí)行的對象的具有給定類型的注釋
@Pointcut("@target(org.springframework.stereotype.Repository)")
@args
@args其中傳遞的實際參數(shù)的運行時類型具有給定類型的注釋
package com.example.spring.aop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
// 作用于類
@Target(ElementType.TYPE)
public @interface Entity {
}
@Pointcut("within(com.example.spring.aop..*) && @args(com.example.spring.aop.annotation.Entity)")
public void argsMethod(){}
@within
將匹配限制為具有給定注釋的類型內(nèi)的連接點
Pointcut("@within(org.springframework.stereotype.Repository)")
等效于:
@Pointcut("within(@org.springframework.stereotype.Repository *)")
@annotation
這個@annotation是用于匹配注解的,比較常用,我們可以自己寫個注解類:
package com.example.spring.aop.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* EnableLog
*
*
*
* @author mazq
* 修改記錄
* 修改后版本: 修改人:修改日期: 2020/11/23 18:27 修改內(nèi)容:
*
*/
@Retention(RetentionPolicy.RUNTIME)
// 作用于方法
@Target(ElementType.METHOD)
public @interface EnableLog {
}
然后,在對應(yīng)方法加上@EnableLog的方法都能被攔截到:
@Pointcut("within(com.example.spring.aop..*) && @annotation(com.example.spring.aop.annotation.EnableLog)")
public void annotationMethod(){}
組合表達式
組合表達式可以使用&&,||和!進行組合
@Pointcut("within(com.example.spring.aop..*) && execution(public String com.example.spring.aop.service.UserService.*(..))")
8.2.4、聲明Advice類型
Advice類型,Spring官網(wǎng)也有比較詳細的介紹:
歸納一下通知類型:
前置通知(
@Before):在方法執(zhí)行之前,使用@Before注釋聲明后置通知(
@AfterReturning):當匹配的方法執(zhí)行正常返回時運行建議。它使用@AfterReturning注釋聲明異常通知(
@AfterThrowing):程序拋出異常后執(zhí)行,不拋異常不會調(diào)用,使用@AfterThrowing注釋聲明最后通知(
@After):匹配的方法執(zhí)行退出,如果是有try...catch,一般是在finally執(zhí)行完成后,使用@After注釋聲明環(huán)繞通知(
@Around):圍繞建議在匹配的方法執(zhí)行過程中“圍繞”運行。它有機會在該方法執(zhí)行之前和之后進行工作,并確定該方法何時,如何以及什至完全可以執(zhí)行,使用@Around注釋聲明
package com.example.spring.aop.config;
import com.example.spring.aop.service.UserService;
import com.example.spring.aop.service.impl.UserServiceImpl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
/**
*
* SpringAspectJConfiguration
*
*
*
* @author mazq
* 修改記錄
* 修改后版本: 修改人:修改日期: 2020/11/24 10:52 修改內(nèi)容:
*
*/
@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = false)
@Aspect
public class SpringAspectJConfiguration {
@Bean
public UserService userService(){
return new UserServiceImpl();
}
private ThreadLocal simpleDateFormat =new ThreadLocal(){
@Override
protected SimpleDateFormat initialValue() {
//return super.initialValue();
return new SimpleDateFormat("[yyyy-mm-dd hh:mm:ss:SSS]");
}
};
//---------------------------------------------------------------------
// Types of pointcut
//---------------------------------------------------------------------
@Pointcut("within(com.example.spring.aop..*) && execution(public String com.example.spring.aop.service.UserService.*(..))")
public void regexpExecution(){}
@Pointcut("within(com.example.spring.aop..*) && execution(* *..find*(Long,..))")
public void regexpExecutionByMethodName(){}
@Pointcut("within(com.example.spring.aop..*) && target(com.example.spring.aop.service.UserService)")
public void targetInterface(){}
@Pointcut("within(com.example.spring.aop..*) && @args(com.example.spring.aop.annotation.Entity)")
public void argsMethod(){}
@Pointcut("within(com.example.spring.aop..*) && @annotation(com.example.spring.aop.annotation.EnableLog)")
public void annotationMethod(){}
//---------------------------------------------------------------------
// Types of advice
//---------------------------------------------------------------------
@Before(value = "regexpExecution()")
public void beforeAdvice(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(String.format("前置通知:beforeAdvice,參數(shù)是:%s", Arrays.toString(args)));
System.out.println(simpleDateFormat.get().format(new Date()) + methodName);
}
@AfterReturning(value = "regexpExecution()", returning = "returnVal")
public void afterReturningAdvice(Object returnVal){
System.out.println(String.format("后置通知:afterReturningAdvice,返回參數(shù)是:%s", returnVal));
}
@AfterThrowing(value = "regexpExecution()", throwing = "e")
public void afterThrowingAdvice(Throwable e) {
System.out.println(String.format("異常通知:afterThrowingAdvice,異常信息:%s", e));
}
@After(value = "regexpExecution()")
public void afterAdvice() {
System.out.println(String.format("最后通知:afterAdvice"));
}
@Around(value = "regexpExecution()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
Object rtValue = null;
try {
System.out.println("aroundAdvice前置通知!");
// 獲取參數(shù)
Object[] args = proceedingJoinPoint.getArgs();
// 執(zhí)行切入點方法
rtValue = proceedingJoinPoint.proceed(args);
System.out.println("aroundAdvice后置通知!");
} catch (Throwable e) {
System.out.println("aroundAdvice異常通知!");
e.printStackTrace();
} finally {
System.out.println("aroundAdvice最后通知!");
}
return rtValue;
}
}
aroundAdvice前置通知!
前置通知:beforeAdvice,參數(shù)是:[1]
[2020-13-24 02:13:48:509]findUserNameById
aroundAdvice后置通知!
aroundAdvice最后通知!
最后通知:afterAdvice
后置通知:afterReturningAdvice,返回參數(shù)是:tom
8.2.5、例子:實現(xiàn)日志監(jiān)控
Service加個方法:

AopConfiguration.java:
package com.example.spring.aop.config;
import com.example.spring.aop.core.interceptor.TestMonitoringInterceptor;
import com.example.spring.aop.service.UserService;
import com.example.spring.aop.service.impl.UserServiceImpl;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
*
* AOP日志監(jiān)控配置類
*
*
*
* @author mazq
* 修改記錄
* 修改后版本: V1.0.0 修改人:mazq 修改日期: 2020/11/23 14:30 修改內(nèi)容: 新增配置類
*
*/
@Configuration
@Aspect
@EnableAspectJAutoProxy
public class AopLogMonitorConfiguration {
@Pointcut("within(com.example.spring.aop..*) && execution(public String com.example.spring.aop.service.UserService.findUserNameById(Long, ..))")
public void monitor(){ }
@Bean
public TestMonitoringInterceptor monitoringInterceptor() {
return new TestMonitoringInterceptor(true);
}
@Bean
public Advisor monitoringAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression("com.example.spring.aop.config.AopConfiguration.monitor()");
return new DefaultPointcutAdvisor(pointcut, monitoringInterceptor());
}
@Bean
public UserService userService(){
return new UserServiceImpl();
}
}
TestMonitoringInterceptor.java
package com.example.spring.aop.core.interceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.springframework.aop.interceptor.AbstractMonitoringInterceptor;
/**
*
* TestMonitoringInterceptor
*
*
*
* 修改記錄
* 修改后版本: 修改人:修改日期: 2020/11/23 16:39 修改內(nèi)容:
*
*/
public class TestMonitoringInterceptor extends AbstractMonitoringInterceptor {
public TestMonitoringInterceptor(){}
public TestMonitoringInterceptor (boolean useDynamicLogger) {
setUseDynamicLogger(useDynamicLogger);
}
@Override
protected Object invokeUnderTrace(MethodInvocation methodInvocation, Log log) throws Throwable {
String name = createInvocationTraceName(methodInvocation);
long start = System.currentTimeMillis();
try {
return methodInvocation.proceed();
} finally {
long end = System.currentTimeMillis();
long time = end - start;
log.info(String.format("方法名:%s,執(zhí)行時間:%s ms",name,time));
if (time > 10) {
log.warn(String.format("方法名:%s,執(zhí)行時間超過10 ms! ",name));
}
}
}
}
pom.xml加上logback配置:
1.7.25
1.2.3
org.slf4j
slf4j-api
${slf4j.version}
ch.qos.logback
logback-core
${logback.version}
ch.qos.logback
logback-access
${logback.version}
ch.qos.logback
logback-classic
${logback.version}
logback.xml,copy @https://github.com/eugenp/tutorials/blob/master/spring-aop/src/main/resources/logback.xml,進行一點改寫:
name ="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
name ="org.springframework" level="WARN" />
name ="org.springframework.transaction" level="WARN" />
name ="org.springframework.web.servlet.mvc" level="WARN" />
name ="com.example.spring.aop.core.interceptor.TestMonitoringInterceptor" level="INFO" />
name ="org.springframework.aop.interceptor.PerformanceMonitorInterceptor" level="TRACE" />
level ="TRACE">
ref ="STDOUT" />
package com.example.spring.aop;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.config.AopConfiguration;
import com.example.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class TestApplication {
public static void testLogMonitoring() {
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext();
// 注冊配置類
ioc.register(AopConfiguration.class);
// 啟動IOC容器
ioc.refresh();
UserService userService = (UserService) ioc.getBean("userService");
System.out.println(userService.findUserNameById(1L));
}
public static void main(String[] args) {
// logging monitoring
testLogMonitoring();
}
}
17:54:05.553 [main] INFO c.e.s.a.service.impl.UserServiceImpl - 方法名:com.example.spring.aop.service.UserService.findUserNameById,執(zhí)行時間:2531 ms
17:54:05.559 [main] WARN c.e.s.a.service.impl.UserServiceImpl - 方法名:com.example.spring.aop.service.UserService.findUserNameById,執(zhí)行時間超過10 ms!
8.3、Spring2.0+ schema-based 配置
Spring2.0之后提供了基于??命名空間的 XML 配置,這也就是本文介紹的schema-based 配置
SchemaBasedAspect .java:
package com.example.spring.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
public class SchemaBasedAspect {
private ThreadLocal simpleDateFormat =new ThreadLocal(){
@Override
protected SimpleDateFormat initialValue() {
//return super.initialValue();
return new SimpleDateFormat("[yyyy-mm-dd hh:mm:ss:SSS]");
}
};
public void beforeAdvice(JoinPoint joinPoint){
Object[] args = joinPoint.getArgs();
String methodName = joinPoint.getSignature().getName();
System.out.println(String.format("前置通知:beforeAdvice,參數(shù)是:%s", Arrays.toString(args)));
System.out.println(simpleDateFormat.get().format(new Date()) + methodName);
}
public void afterReturningAdvice(Object returnVal){
System.out.println(String.format("后置通知:afterReturningAdvice,返回參數(shù)是:%s", returnVal));
}
public void afterThrowingAdvice(Throwable e) {
System.out.println(String.format("異常通知:afterThrowingAdvice,異常信息:%s", e));
}
public void afterAdvice() {
System.out.println(String.format("最后通知:afterAdvice"));
}
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) {
Object rtValue = null;
try {
System.out.println("aroundAdvice前置通知!");
// 獲取參數(shù)
Object[] args = proceedingJoinPoint.getArgs();
// 執(zhí)行切入點方法
rtValue = proceedingJoinPoint.proceed(args);
System.out.println("aroundAdvice后置通知!");
} catch (Throwable e) {
System.out.println("aroundAdvice異常通知!");
e.printStackTrace();
} finally {
System.out.println("aroundAdvice最后通知!");
}
return rtValue;
}
}
spring_schemaBased_config.xml:
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.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
id ="userService" class="com.example.spring.aop.service.impl.UserServiceImpl">
id ="LoggingAspect" class="com.example.spring.aop.aspect.SchemaBasedAspect">
id ="executionPointcut" expression="execution(* com.example.spring.aop.service.UserService.*(..))" />
id ="logAspect" ref="LoggingAspect">
method ="beforeAdvice"
pointcut-ref="executionPointcut">
method ="afterReturningAdvice"
pointcut-ref="executionPointcut" returning="returnVal">
method ="afterThrowingAdvice" pointcut-ref="executionPointcut" throwing="e"
>
method ="afterAdvice" pointcut-ref="executionPointcut">
method ="aroundAdvice" pointcut-ref="executionPointcut">
TestApplication.java:
package com.example.spring.aop;
import com.example.spring.aop.bean.User;
import com.example.spring.aop.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class TestApplication {
public static void testSchemaBasedAop(){
ApplicationContext ioc = new ClassPathXmlApplicationContext("classpath:spring_schemaBased_config.xml");
UserService userService = (UserService) ioc.getBean("userService");
User userDto = new User();
userDto.setUsername("tom");
userDto.setPassword("11");
userService.addUser(userDto);
System.out.println(String.format("用戶數(shù)據(jù)打印:%s",userService.getUser().toString()));
}
public static void main(String[] args) {
// schema Based config
testSchemaBasedAop();
}
}

騰訊、阿里、滴滴后臺面試題匯總總結(jié) — (含答案)
面試:史上最全多線程面試題 !
最新阿里內(nèi)推Java后端面試題
JVM難學?那是因為你沒認真看完這篇文章

關(guān)注作者微信公眾號 —《JAVA爛豬皮》
了解更多java后端架構(gòu)知識以及最新面試寶典


看完本文記得給作者點贊+在看哦~~~大家的支持,是作者源源不斷出文的動力
作者:smileNicky
出處:https://www.cnblogs.com/mzq123/p/14041943.html
