Spring?AOP注解配置與XML配置雙實(shí)戰(zhàn)教程
1. AOP

1.1 概念
? AOP為Aspect Oriented Programming的縮寫(xiě),意為:面向切面編程。他是一種可以在不修改原來(lái)的核心代碼的情況下給程序動(dòng)態(tài)統(tǒng)一進(jìn)行增強(qiáng)的一種技術(shù)。
? SpringAOP: 批量對(duì)Spring容器中bean的方法做增強(qiáng),并且這種增強(qiáng)不會(huì)與原來(lái)方法中的代碼耦合。
AOP:面向切面編程。簡(jiǎn)單說(shuō),就是把一些業(yè)務(wù)邏輯中的相同的代碼抽取到一個(gè)獨(dú)立的模塊中,讓業(yè)務(wù)邏輯更加清爽。

1.2 快速入門
1.2.1 需求
? 要求讓_08_SpringAOP模塊中service包下所有類的所有方法在調(diào)用前都輸出:方法被調(diào)用了。
1.2.2 準(zhǔn)備工作
①添加依賴
需要添加SpringIOC相關(guān)依賴和AOP相關(guān)依賴。
<!--SpringIOC相關(guān)依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--AOP相關(guān)依賴-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>②相關(guān)bean要注入容器中
開(kāi)啟組件掃描
<context:component-scan base-package="com.sangeng"></context:component-scan>
加@Service注解
@Service
public class PhoneService {
public void deleteAll(){
System.out.println("PhoneService中deleteAll的核心代碼");
}
}@Service
public class UserService {
public void deleteAll(){
System.out.println("UserService中deleteAll的核心代碼");
}
}1.2.3 實(shí)現(xiàn)AOP
①開(kāi)啟AOP注解支持
使用aop:aspectj-autoproxy標(biāo)簽
<?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"
xmlns:context="http://www.springframework.org/schema/context"
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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<!--開(kāi)啟組件掃描-->
<context:component-scan base-package="com.sangeng"></context:component-scan>
<!--開(kāi)啟aop注解支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>②創(chuàng)建切面類
創(chuàng)建一個(gè)類,在類上加上@Component和@Aspect
使用@Pointcut注解來(lái)指定要被增強(qiáng)的方法
使用@Before注解來(lái)給我們的增強(qiáng)代碼所在的方法進(jìn)行標(biāo)識(shí),并且指定了增強(qiáng)代碼是在被增強(qiáng)方法執(zhí)行之前執(zhí)行的。
@Component
@Aspect
public class MyAspect {
// 用Pointcut注解中的屬性來(lái)指定對(duì)哪些方法進(jìn)行增強(qiáng)
@Pointcut("execution(* com.sangeng.service.*.*(..))")
public void pt(){}
/*
用@Before注解來(lái)指定該方法中是增強(qiáng)的代碼,并且是在被增強(qiáng)方法執(zhí)行前執(zhí)行的
@Before的屬性寫(xiě)上加了@Pointcut注解的方法: 方法名()
*/
@Before("pt()")
public void methodbefore(){
System.out.println("方法被調(diào)用了");
}
}1.2.4 測(cè)試
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
PhoneService phoneService = applicationContext.getBean(PhoneService.class);
UserService userService = applicationContext.getBean(UserService.class);
phoneService.deleteAll();
}
1.3 AOP核心概念
- Joinpoint(連接點(diǎn)):所謂連接點(diǎn)是指那些可以被增強(qiáng)到的點(diǎn)。在spring中,這些點(diǎn)指的是方法,因?yàn)閟pring只支持方法類型的連接點(diǎn)
- Pointcut(切入點(diǎn)):所謂切入點(diǎn)是指被增強(qiáng)的連接點(diǎn)(方法)
- Advice(通知/ 增強(qiáng)):所謂通知是指具體增強(qiáng)的代碼
- Target(目標(biāo)對(duì)象):被增強(qiáng)的對(duì)象就是目標(biāo)對(duì)象
- Aspect(切面):是切入點(diǎn)和通知(引介)的結(jié)合
- Proxy (代理):一個(gè)類被 AOP 增強(qiáng)后,就產(chǎn)生一個(gè)結(jié)果代理類
1.4 切點(diǎn)確定
1.4.1 切點(diǎn)表達(dá)式
? 可以使用切點(diǎn)表達(dá)式來(lái)表示要對(duì)哪些方法進(jìn)行增強(qiáng)。
寫(xiě)法:execution([修飾符] 返回值類型 包名.類名.方法名(參數(shù)))
- 訪問(wèn)修飾符可以省略,大部分情況下省略
- 返回值類型、包名、類名、方法名可以使用星號(hào)* 代表任意
- 包名與類名之間一個(gè)點(diǎn) . 代表當(dāng)前包下的類,兩個(gè)點(diǎn) … 表示當(dāng)前包及其子包下的類
- 參數(shù)列表可以使用兩個(gè)點(diǎn) … 表示任意個(gè)數(shù),任意類型的參數(shù)列表
例如:
execution(* com.sangeng.service.*.*(..)) 表示com.sangeng.service包下任意類,方法名任意,參數(shù)列表任意,返回值類型任意 execution(* com.sangeng.service..*.*(..)) 表示com.sangeng.service包及其子包下任意類,方法名任意,參數(shù)列表任意,返回值類型任意 execution(* com.sangeng.service.*.*()) 表示com.sangeng.service包下任意類,方法名任意,要求方法不能有參數(shù),返回值類型任意 execution(* com.sangeng.service.*.delete*(..)) 表示com.sangeng.service包下任意類,要求方法不能有參數(shù),返回值類型任意,方法名要求已delete開(kāi)頭
1.4.2 切點(diǎn)函數(shù)@annotation
? 我們也可以在要增強(qiáng)的方法上加上注解。然后使用@annotation來(lái)表示對(duì)加了什么注解的方法進(jìn)行增強(qiáng)。
寫(xiě)法:@annotation(注解的全類名)
例如:
定義注解如下
@Target({ElementType.METHOD})//該注解可以加在方法上
@Retention(RetentionPolicy.RUNTIME)
public @interface InvokeLog {
}
給需要增強(qiáng)的方法增加注解
@Service
public class PhoneService {
@InvokeLog
public void deleteAll(){
System.out.println("PhoneService中deleteAll的核心代碼");
}
}切面類中使用@annotation來(lái)確定要增強(qiáng)的方法
@Component
@Aspect
public class MyAspect {
// 用Pointcut注解中的屬性來(lái)指定對(duì)哪些方法進(jìn)行增強(qiáng)
@Pointcut("@annotation(com.sangeng.aspect.InvokeLog)")
public void pt(){}
/*
用@Before注解來(lái)指定該方法中是增強(qiáng)的代碼,并且是在被增強(qiáng)方法執(zhí)行前執(zhí)行的
@Before的屬性寫(xiě)上加了@Pointcut注解的方法: 方法名()
*/
@Before("pt()")
public void methodbefore(){
System.out.println("方法被調(diào)用了");
}
}1.5 通知分類
- @Before:前置通知,在目標(biāo)方法執(zhí)行前執(zhí)行
- @AfterReturning: 返回后通知,在目標(biāo)方法執(zhí)行后執(zhí)行,如果出現(xiàn)異常不會(huì)執(zhí)行
- @After:后置通知,在目標(biāo)方法之后執(zhí)行,無(wú)論是否出現(xiàn)異常都會(huì)執(zhí)行
- @AfterThrowing:異常通知,在目標(biāo)方法拋出異常后執(zhí)行
- @Around:環(huán)繞通知,圍繞著目標(biāo)方法執(zhí)行
理解不同通知執(zhí)行時(shí)機(jī)。(下面的偽代碼是用來(lái)理解單個(gè)通知的執(zhí)行時(shí)機(jī)的,不能用來(lái)理解多個(gè)通知情況下的執(zhí)行順序。如果需要配置多個(gè)通知我們會(huì)選擇使用Around通知,更加的清晰并且好用)
public Object test() {
before();//@Before 前置通知
try {
Object ret = 目標(biāo)方法();//目標(biāo)方法調(diào)用
afterReturing();//@AfterReturning 返回后通知
} catch (Throwable throwable) {
throwable.printStackTrace();
afterThrowing();//@AfterThrowing 異常通知通知
}finally {
after();//@After 后置通知
}
return ret;
}
環(huán)繞通知非常特殊,它可以對(duì)目標(biāo)方法進(jìn)行全方位的增強(qiáng)。
例如:
@Around("pt()")
public void around(ProceedingJoinPoint pjp){
System.out.println("目標(biāo)方法前");
try {
pjp.proceed();//目標(biāo)方法執(zhí)行
System.out.println("目標(biāo)方法后");
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("目標(biāo)方法出現(xiàn)異常");
}finally {
System.out.println("finally中進(jìn)行增強(qiáng)");
}
}
1.6 獲取被增強(qiáng)方法相關(guān)信息
? 我們實(shí)際對(duì)方法進(jìn)行增強(qiáng)時(shí)往往還需要獲取到被增強(qiáng)代碼的相關(guān)信息,比如方法名,參數(shù),返回值,異常對(duì)象等。
? 我們可以在除了環(huán)繞通知外的所有通知方法中增加一個(gè)JoinPoint類型的參數(shù)。這個(gè)參數(shù)封裝了被增強(qiáng)方法的相關(guān)信息。我們可以通過(guò)這個(gè)參數(shù)獲取到除了異常對(duì)象和返回值之外的所有信息。
例如:
@Before("pt()")
public void methodbefore(JoinPoint jp){
Object[] args = jp.getArgs();//方法調(diào)用時(shí)傳入的參數(shù)
Object target = jp.getTarget();//被代理對(duì)象
MethodSignature signature = (MethodSignature) jp.getSignature();//獲取被被增強(qiáng)方法簽名封裝的對(duì)象
System.out.println("Before方法被調(diào)用了");
}
案例:
需求:要求讓所有service包下類的所有方法被調(diào)用前都輸出全類名,方法名,以及調(diào)用時(shí)傳入的參數(shù)
@Component
@Aspect
public class PrintLogAspect {
//對(duì)哪些方法增強(qiáng)
@Pointcut("execution(* com.sangeng.service..*.*(..))")
public void pt(){}
//怎么增強(qiáng)
@Before("pt()")
public void printLog(JoinPoint joinPoint){
//輸出 被增強(qiáng)的方法所在的類名 方法名 調(diào)用時(shí)傳入的參數(shù) joinPoint.getSignature().getName() joinPoint.getArgs()
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//類名
String className = signature.getDeclaringTypeName();
//方法名
String methodName = signature.getName();
//調(diào)用時(shí)傳入的參數(shù)
Object[] args = joinPoint.getArgs();
System.out.println(className+"=="+methodName+"======"+ Arrays.toString(args));
}
}? 如果需要獲取被增強(qiáng)方法中的異常對(duì)象或者返回值則需要在方法參數(shù)上增加一個(gè)對(duì)應(yīng)類型的參數(shù),并且使用注解的屬性進(jìn)行配置。這樣Spring會(huì)把你想獲取的數(shù)據(jù)賦值給對(duì)應(yīng)的方法參數(shù)。
例如:
@AfterReturning(value = "pt()",returning = "ret")//使用returning屬性指定了把目標(biāo)方法返回值賦值給下面方法的參數(shù)ret
public void AfterReturning(JoinPoint jp,Object ret){
System.out.println("AfterReturning方法被調(diào)用了");
}
@AfterThrowing(value = "pt()",throwing = "t")//使用throwing屬性指定了把出現(xiàn)的異常對(duì)象賦值給下面方法的參數(shù)t
public void AfterThrowing(JoinPoint jp,Throwable t){
System.out.println("AfterReturning方法被調(diào)用了");
}
? 相信你肯定覺(jué)得上面的獲取方式特別的麻煩難以理解。就可以使用下面這種萬(wàn)能的方法。
? 直接在環(huán)繞通知方法中增加一個(gè)ProceedingJoinPoint類型的參數(shù)。這個(gè)參數(shù)封裝了被增強(qiáng)方法的相關(guān)信息。
該參數(shù)的proceed()方法被調(diào)用相當(dāng)于被增強(qiáng)方法被執(zhí)行,調(diào)用后的返回值就相當(dāng)于被增強(qiáng)方法的返回值。
例如:
@Around(value = "pt()")
public Object around(ProceedingJoinPoint pjp) {
Object[] args = pjp.getArgs();//方法調(diào)用時(shí)傳入的參數(shù)
Object target = pjp.getTarget();//被代理對(duì)象
MethodSignature signature = (MethodSignature) pjp.getSignature();//獲取被被增強(qiáng)方法簽名封裝的對(duì)象
Object ret = null;
try {
ret = pjp.proceed();//ret就是目標(biāo)方法執(zhí)行后的返回值
} catch (Throwable throwable) {
throwable.printStackTrace();//throwable就是出現(xiàn)異常時(shí)的異常對(duì)象
}
return ret;
}
1.7 AOP應(yīng)用案例
1.7.1 需求
現(xiàn)有AI核心功能代碼如下:
public class AIController {
//AI自動(dòng)回答
public String getAnswer(String question){
//AI核心代碼 價(jià)值10個(gè)億
String str = question.replace("嗎", "");
str = str.replace("?","!");
return str;
}
//AI算
public String fortuneTelling(String name){
//AI算核心代碼
String[] strs = {"女犯?jìng)侔逊蚩耍档厣徎ㄔ圆换?,不是吃上兩家飯,也要刷上三家鍋?,"一朵鮮花頭上戴,一年四季也不開(kāi),一心想要花開(kāi)時(shí),采花之人沒(méi)到來(lái)。","此命生來(lái)脾氣暴,上來(lái)一陣雙腳跳,對(duì)你脾氣啥都好,經(jīng)常與人吵和鬧。"};
int index = name.hashCode() % 3;
return strs[index];
}
}? 現(xiàn)在為了保證數(shù)據(jù)的安全性,要求調(diào)用方法時(shí)fortuneTelling傳入的姓名是經(jīng)過(guò)加密的。我們需要對(duì)傳入的參數(shù)進(jìn)行解密后才能使用。并且要對(duì)該方法的返回值進(jìn)行加密后返回。
? PS:后期也可能讓其他方法進(jìn)行相應(yīng)的加密處理。
字符串加密解密直接使用下面的工具類即可:
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
public class CryptUtil {
private static final String AES = "AES";
private static int keysizeAES = 128;
private static String charset = "utf-8";
public static String parseByte2HexStr(final byte buf[]) {
final StringBuffer sb = new StringBuffer();
for (int i = 0; i < buf.length; i++) {
String hex = Integer.toHexString(buf[i] & 0xFF);
if (hex.length() == 1) {
hex = '0' + hex;
}
sb.append(hex.toUpperCase());
}
return sb.toString();
}
public static byte[] parseHexStr2Byte(final String hexStr) {
if (hexStr.length() < 1)
return null;
final byte[] result = new byte[hexStr.length() / 2];
for (int i = 0;i< hexStr.length()/2; i++) {
int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
result[i] = (byte) (high * 16 + low);
}
return result;
}
private static String keyGeneratorES(final String res, final String algorithm, final String key, final Integer keysize, final Boolean bEncode) {
try {
final KeyGenerator g = KeyGenerator.getInstance(algorithm);
if (keysize == 0) {
byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
g.init(new SecureRandom(keyBytes));
} else if (key == null) {
g.init(keysize);
} else {
byte[] keyBytes = charset == null ? key.getBytes() : key.getBytes(charset);
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
random.setSeed(keyBytes);
g.init(keysize, random);
}
final SecretKey sk = g.generateKey();
final SecretKeySpec sks = new SecretKeySpec(sk.getEncoded(), algorithm);
final Cipher cipher = Cipher.getInstance(algorithm);
if (bEncode) {
cipher.init(Cipher.ENCRYPT_MODE, sks);
final byte[] resBytes = charset == null? res.getBytes() : res.getBytes(charset);
return parseByte2HexStr(cipher.doFinal(resBytes));
} else {
cipher.init(Cipher.DECRYPT_MODE, sks);
return new String(cipher.doFinal(parseHexStr2Byte(res)));
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String AESencode(final String res) {
return keyGeneratorES(res, AES, "aA11*-%", keysizeAES, true);
}
public static String AESdecode(final String res) {
return keyGeneratorES(res, AES, "aA11*-%", keysizeAES, false);
}
public static void main(String[] args) {
System.out.println(
"加密后:" + AESencode("將要加密的明文")
);
System.out.println(
"解密后:" + AESdecode("730CAE52D85B372FB161B39D0A908B8CC6EF6DA2F7D4E595D35402134C3E18AB")
);
}
}1.7.2 實(shí)現(xiàn)
①導(dǎo)入依賴
<!--SpringIOC相關(guān)依賴-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<!--AOP相關(guān)依賴-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>②開(kāi)啟AOP注解支持
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置組件掃描-->
<context:component-scan base-package="com.sangeng"></context:component-scan>
<!--啟動(dòng)AOP注解支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>③自定義注解
package com.sangeng.aspect;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Crypt {
}④在目標(biāo)方法上增加注解
注意:目標(biāo)對(duì)象一定要記得注入Spring容器中
@Controller
public class AIController {
//....
//AI算命
@Crypt
public String fortuneTelling(String name){
System.out.println(name);
//AI算命核心代碼
String[] strs = {"女犯?jìng)侔逊蚩?,旱地蓮花栽不活,不是吃上兩家飯,也要刷上三家鍋?,"一朵鮮花頭上戴,一年四季也不開(kāi),一心想要花開(kāi)時(shí),采花之人沒(méi)到來(lái)。","此命生來(lái)脾氣暴,上來(lái)一陣雙腳跳,對(duì)你脾氣啥都好,經(jīng)常與人吵和鬧。"};
int index = name.hashCode() % 3;
return strs[index];
}
}⑤定義切面類
package com.sangeng.aspect;
import com.sangeng.util.CryptUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class CryptAspect {
//確定切點(diǎn)
@Pointcut("@annotation(com.sangeng.aspect.Crypt)")
public void pt(){
}
//定義通知
@Around("pt()")
public Object crypt(ProceedingJoinPoint pjp) {
//獲取去目標(biāo)方法調(diào)用時(shí)的參數(shù)
Object[] args = pjp.getArgs();
//對(duì)參數(shù)進(jìn)行解密 解密后傳入目標(biāo)方法執(zhí)行
String arg = (String) args[0];
String s = CryptUtil.AESdecode(arg);//解密
args[0] = s;
Object proceed = null;
String ret = null;
try {
proceed = pjp.proceed(args);//目標(biāo)方法調(diào)用
//目標(biāo)方法執(zhí)行后需要獲取到返回值
ret = (String) proceed;
//對(duì)返回值加密后進(jìn)行真正的返回
ret = CryptUtil.AESencode(ret);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return ret;
}
}1.8 xml配置AOP
①定義切面類
public class MyAspect {
public void before(JoinPoint joinPoint){
System.out.println("before");
}
// @AfterReturning(value = "pt()",returning = "ret")
public void afterReturning(JoinPoint joinPoint,Object ret){
System.out.println("afterReturning:"+ret);
}
// @After("pt()")
public void after(JoinPoint joinPoint){
System.out.println("after");
}
// @AfterThrowing(value = "pt()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Throwable e){
String message = e.getMessage();
System.out.println("afterThrowing:"+message);
}
public Object around(ProceedingJoinPoint pjp){
//獲取參數(shù)
Object[] args = pjp.getArgs();
MethodSignature signature = (MethodSignature) pjp.getSignature();
Object target = pjp.getTarget();
Object ret = null;
try {
ret = pjp.proceed();//目標(biāo)方法的執(zhí)行
//ret就是被增強(qiáng)方法的返回值
System.out.println(ret);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println(throwable.getMessage());
}
// System.out.println(pjp);
return ret;
}
}②目標(biāo)類和切面類注入容器
在切面類和目標(biāo)類上加是對(duì)應(yīng)的注解。注入如果是使用注解的方式注入容器要記得開(kāi)啟組件掃描。
當(dāng)然你也可以在xml中使用bean標(biāo)簽的方式注入容器。
@Component//把切面類注入容器
public class MyAspect {
//..。省略無(wú)關(guān)代碼
}
@Service//把目標(biāo)類注入容器
public class UserService {
//..。省略無(wú)關(guān)代碼
}
③配置AOP
<?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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--開(kāi)啟組件掃描-->
<context:component-scan base-package="com.sangeng"></context:component-scan>
<!--配置AOP-->
<aop:config>
<!--定義切點(diǎn)-->
<aop:pointcut id="pt1" expression="execution(* com.sangeng.service..*.*(..))"></aop:pointcut>
<aop:pointcut id="pt2" expression="@annotation(com.sangeng.aspect.InvokeLog)"></aop:pointcut>
<!--配置切面-->
<aop:aspect ref="myAspect">
<aop:before method="before" pointcut-ref="pt1"></aop:before>
<aop:after method="after" pointcut-ref="pt1"></aop:after>
<aop:after-returning method="afterReturning" pointcut-ref="pt1" returning="ret"></aop:after-returning>
<aop:after-throwing method="afterThrowing" pointcut-ref="pt2" throwing="e"></aop:after-throwing>
</aop:aspect>
</aop:config>
</beans>1.9 多切面順序問(wèn)題
? 在實(shí)際項(xiàng)目中我們可能會(huì)存在配置了多個(gè)切面的情況。這種情況下我們很可能需要控制切面的順序。
? 我們?cè)谀J(rèn)情況下Spring有它自己的排序規(guī)則。(按照類名排序)
? 默認(rèn)排序規(guī)則往往不符合我們的要求,我們需要進(jìn)行特殊控制。
? 如果是注解方式配置的AOP可以在切面類上加**@Order注解來(lái)控制順序。@Order中的屬性越小優(yōu)先級(jí)越高。**
? 如果是XML方式配置的AOP,可以通過(guò)調(diào)整配置順序來(lái)控制。
例如:
下面這種配置方式就會(huì)先使用CryptAspect里面的增強(qiáng),在使用APrintLogAspect里的增強(qiáng)
@Component
@Aspect
@Order(2)
public class APrintLogAspect {
//省略無(wú)關(guān)代碼
}
@Component
@Aspect
@Order(1)
public class CryptAspect {
//省略無(wú)關(guān)代碼
}1.10 AOP原理-動(dòng)態(tài)代理
? 實(shí)際上Spring的AOP其實(shí)底層就是使用動(dòng)態(tài)代理來(lái)完成的。并且使用了兩種動(dòng)態(tài)代理分別是JDK的動(dòng)態(tài)代理和Cglib動(dòng)態(tài)代理。
? 所以我們接下去來(lái)學(xué)習(xí)下這兩種動(dòng)態(tài)代理,理解下它們的不同點(diǎn)。
1.10.1 JDK動(dòng)態(tài)代理
? JDK的動(dòng)態(tài)代理使用的java.lang.reflect.Proxy這個(gè)類來(lái)進(jìn)行實(shí)現(xiàn)的。要求被代理(被增強(qiáng))的類需要實(shí)現(xiàn)了接口。并且JDK動(dòng)態(tài)代理也只能對(duì)接口中的方法進(jìn)行增強(qiáng)。
public static void main(String[] args) {
AIControllerImpl aiController = new AIControllerImpl();
//使用動(dòng)態(tài)代理增強(qiáng)getAnswer方法
//1.JDK動(dòng)態(tài)代理
//獲取類加載器
ClassLoader cl = Demo.class.getClassLoader();
//被代理類所實(shí)現(xiàn)接口的字節(jié)碼對(duì)象數(shù)組
Class<?>[] interfaces = AIControllerImpl.class.getInterfaces();
AIController proxy = (AIController) Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() {
//使用代理對(duì)象的方法時(shí) 會(huì)調(diào)用到invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy 是代理對(duì)象
//method 是當(dāng)前被調(diào)用的方法封裝的Method對(duì)象
//args 是調(diào)用方法時(shí)傳入的參數(shù)
//調(diào)用被代理對(duì)象的對(duì)應(yīng)方法
//判斷 當(dāng)前調(diào)用的是否是getAnswer方法
if(method.getName().equals("getAnswer")){
System.out.println("增強(qiáng)");
}
Object ret = method.invoke(aiController, args);
return ret;
}
});
String answer = proxy.getAnswer("三連了嗎?");
System.out.println(answer);
}
1.10.2 Cglib動(dòng)態(tài)代理
? 使用的是org.springframework.cglib.proxy.Enhancer類進(jìn)行實(shí)現(xiàn)的。
public class CglibDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//設(shè)置父類的字節(jié)碼對(duì)象
enhancer.setSuperclass(AIControllerImpl.class);
enhancer.setCallback(new MethodInterceptor() {
//使用代理對(duì)象執(zhí)行方法是都會(huì)調(diào)用到intercept方法
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//判斷當(dāng)前調(diào)用的方法是不是getAnswer方法 如果是進(jìn)行增強(qiáng)
if ("getAnswer".equals(method.getName())){
System.out.println("被增強(qiáng)了");
}
//調(diào)用父類中對(duì)應(yīng)的方法
Object ret = methodProxy.invokeSuper(o, objects);
return ret;
}
});
//生成代理對(duì)象
AIControllerImpl proxy = (AIControllerImpl) enhancer.create();
// System.out.println(proxy.getAnswer("你好嗎?"));
System.out.println(proxy.fortuneTelling("你好嗎?"));
}
}1.10.3 總結(jié)
? JDK動(dòng)態(tài)代理要求被代理(被增強(qiáng))的類必須要實(shí)現(xiàn)接口,生成的代理對(duì)象相當(dāng)于是被代理對(duì)象的兄弟。
? Cglib的動(dòng)態(tài)代理不要求被代理(被增強(qiáng))的類要實(shí)現(xiàn)接口,生成的代理對(duì)象相當(dāng)于被代理對(duì)象的子類對(duì)象。
? Spring的AOP默認(rèn)情況下優(yōu)先使用的是JDK的動(dòng)態(tài)代理,如果使用不了JDK的動(dòng)態(tài)代理才會(huì)使用Cglib的動(dòng)態(tài)代理。
1.11 切換默認(rèn)動(dòng)態(tài)代理方式
? 有的時(shí)候我們需要修改AOP的代理方式。
? 我們可以使用以下方式修改:
如果我們是采用注解方式配置AOP的話:
設(shè)置aop:aspectj-autoproxy標(biāo)簽的proxy-target-class屬性為true,代理方式就會(huì)修改成Cglib
<aop:aspectj-autoproxy proxy-target-class="true"/>
動(dòng)態(tài)代理和靜態(tài)代理詳解可以看我這一篇文章: SpringAop實(shí)現(xiàn)原理及代理模式詳解
如果我們是采用xml方式配置AOP的話:
設(shè)置aop:config標(biāo)簽的proxy-target-class屬性為true,代理方式就會(huì)修改成Cglib
<aop:config proxy-target-class="true"> </aop:config>
到此這篇關(guān)于Spring AOP注解配置與XML配置雙實(shí)戰(zhàn)教程的文章就介紹到這了,更多相關(guān)spring aop注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于springboot攔截器HandlerInterceptor的注入問(wèn)題
這篇文章主要介紹了springboot攔截器HandlerInterceptor的注入問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
IDEA maven項(xiàng)目中刷新依賴的兩種方法小結(jié)
這篇文章主要介紹了IDEA maven項(xiàng)目中刷新依賴的兩種方法小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
SpringBoot實(shí)現(xiàn)單點(diǎn)登錄(SSO)的四種方案
單點(diǎn)登錄(Single?Sign-On,SSO)是企業(yè)應(yīng)用系統(tǒng)中常見(jiàn)的用戶認(rèn)證方案,它允許用戶使用一組憑證訪問(wèn)多個(gè)相關(guān)但獨(dú)立的系統(tǒng),無(wú)需重復(fù)登錄,本文給大家介紹了SpringBoot實(shí)現(xiàn)單點(diǎn)登錄(SSO)的四種方案,需要的朋友可以參考下2025-04-04
struts1登錄示例代碼_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了struts1登錄示例代碼,需要的朋友可以參考下2017-08-08
Java自定義equals產(chǎn)生的問(wèn)題分析
這篇文章主要介紹了Java自定義equals時(shí)super.equals帶來(lái)的問(wèn)題分析,總的來(lái)說(shuō)這并不是一道難題,那為什么要拿出這道題介紹?拿出這道題真正想要傳達(dá)的是解題的思路,以及不斷優(yōu)化探尋最優(yōu)解的過(guò)程。希望通過(guò)這道題能給你帶來(lái)一種解題優(yōu)化的思路2023-01-01
springboot實(shí)現(xiàn)啟動(dòng)直接訪問(wèn)項(xiàng)目地址
這篇文章主要介紹了springboot實(shí)現(xiàn)啟動(dòng)直接訪問(wèn)項(xiàng)目地址,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
SpringBoot引入swagger報(bào)錯(cuò)處理的解決方法
這篇文章主要給大家介紹SpringBoot引入swagger是會(huì)出現(xiàn)報(bào)錯(cuò)的處理解決方法,文中有詳細(xì)的解決過(guò)程,感興趣的小伙伴可以跟著小編一起來(lái)學(xué)習(xí)吧2023-06-06
mybatis 忽略實(shí)體對(duì)象的某個(gè)屬性(2種方式)
這篇文章主要介紹了mybatis 忽略實(shí)體對(duì)象的某個(gè)屬性方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06

