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

1.1 概念
? AOP為Aspect Oriented Programming的縮寫,意為:面向切面編程。他是一種可以在不修改原來的核心代碼的情況下給程序動態(tài)統(tǒng)一進(jìn)行增強(qiáng)的一種技術(shù)。
? SpringAOP: 批量對Spring容器中bean的方法做增強(qiáng),并且這種增強(qiáng)不會與原來方法中的代碼耦合。
AOP:面向切面編程。簡單說,就是把一些業(yè)務(wù)邏輯中的相同的代碼抽取到一個獨立的模塊中,讓業(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要注入容器中
開啟組件掃描
<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 實現(xiàn)AOP
①開啟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">
<!--開啟組件掃描-->
<context:component-scan base-package="com.sangeng"></context:component-scan>
<!--開啟aop注解支持-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>②創(chuàng)建切面類
創(chuàng)建一個類,在類上加上@Component和@Aspect
使用@Pointcut注解來指定要被增強(qiáng)的方法
使用@Before注解來給我們的增強(qiáng)代碼所在的方法進(jìn)行標(biāo)識,并且指定了增強(qiáng)代碼是在被增強(qiáng)方法執(zhí)行之前執(zhí)行的。
@Component
@Aspect
public class MyAspect {
// 用Pointcut注解中的屬性來指定對哪些方法進(jìn)行增強(qiáng)
@Pointcut("execution(* com.sangeng.service.*.*(..))")
public void pt(){}
/*
用@Before注解來指定該方法中是增強(qiáng)的代碼,并且是在被增強(qiáng)方法執(zhí)行前執(zhí)行的
@Before的屬性寫上加了@Pointcut注解的方法: 方法名()
*/
@Before("pt()")
public void methodbefore(){
System.out.println("方法被調(diào)用了");
}
}1.2.4 測試
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(連接點):所謂連接點是指那些可以被增強(qiáng)到的點。在spring中,這些點指的是方法,因為spring只支持方法類型的連接點
- Pointcut(切入點):所謂切入點是指被增強(qiáng)的連接點(方法)
- Advice(通知/ 增強(qiáng)):所謂通知是指具體增強(qiáng)的代碼
- Target(目標(biāo)對象):被增強(qiáng)的對象就是目標(biāo)對象
- Aspect(切面):是切入點和通知(引介)的結(jié)合
- Proxy (代理):一個類被 AOP 增強(qiáng)后,就產(chǎn)生一個結(jié)果代理類
1.4 切點確定
1.4.1 切點表達(dá)式
? 可以使用切點表達(dá)式來表示要對哪些方法進(jìn)行增強(qiáng)。
寫法:execution([修飾符] 返回值類型 包名.類名.方法名(參數(shù)))
- 訪問修飾符可以省略,大部分情況下省略
- 返回值類型、包名、類名、方法名可以使用星號* 代表任意
- 包名與類名之間一個點 . 代表當(dāng)前包下的類,兩個點 … 表示當(dāng)前包及其子包下的類
- 參數(shù)列表可以使用兩個點 … 表示任意個數(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開頭
1.4.2 切點函數(shù)@annotation
? 我們也可以在要增強(qiáng)的方法上加上注解。然后使用@annotation來表示對加了什么注解的方法進(jìn)行增強(qiáng)。
寫法:@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來確定要增強(qiáng)的方法
@Component
@Aspect
public class MyAspect {
// 用Pointcut注解中的屬性來指定對哪些方法進(jìn)行增強(qiáng)
@Pointcut("@annotation(com.sangeng.aspect.InvokeLog)")
public void pt(){}
/*
用@Before注解來指定該方法中是增強(qiáng)的代碼,并且是在被增強(qiáng)方法執(zhí)行前執(zhí)行的
@Before的屬性寫上加了@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)異常不會執(zhí)行
- @After:后置通知,在目標(biāo)方法之后執(zhí)行,無論是否出現(xiàn)異常都會執(zhí)行
- @AfterThrowing:異常通知,在目標(biāo)方法拋出異常后執(zhí)行
- @Around:環(huán)繞通知,圍繞著目標(biāo)方法執(zhí)行
理解不同通知執(zhí)行時機(jī)。(下面的偽代碼是用來理解單個通知的執(zhí)行時機(jī)的,不能用來理解多個通知情況下的執(zhí)行順序。如果需要配置多個通知我們會選擇使用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)繞通知非常特殊,它可以對目標(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)信息
? 我們實際對方法進(jìn)行增強(qiáng)時往往還需要獲取到被增強(qiáng)代碼的相關(guān)信息,比如方法名,參數(shù),返回值,異常對象等。
? 我們可以在除了環(huán)繞通知外的所有通知方法中增加一個JoinPoint類型的參數(shù)。這個參數(shù)封裝了被增強(qiáng)方法的相關(guān)信息。我們可以通過這個參數(shù)獲取到除了異常對象和返回值之外的所有信息。
例如:
@Before("pt()")
public void methodbefore(JoinPoint jp){
Object[] args = jp.getArgs();//方法調(diào)用時傳入的參數(shù)
Object target = jp.getTarget();//被代理對象
MethodSignature signature = (MethodSignature) jp.getSignature();//獲取被被增強(qiáng)方法簽名封裝的對象
System.out.println("Before方法被調(diào)用了");
}
案例:
需求:要求讓所有service包下類的所有方法被調(diào)用前都輸出全類名,方法名,以及調(diào)用時傳入的參數(shù)
@Component
@Aspect
public class PrintLogAspect {
//對哪些方法增強(qiáng)
@Pointcut("execution(* com.sangeng.service..*.*(..))")
public void pt(){}
//怎么增強(qiáng)
@Before("pt()")
public void printLog(JoinPoint joinPoint){
//輸出 被增強(qiáng)的方法所在的類名 方法名 調(diào)用時傳入的參數(shù) joinPoint.getSignature().getName() joinPoint.getArgs()
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
//類名
String className = signature.getDeclaringTypeName();
//方法名
String methodName = signature.getName();
//調(diào)用時傳入的參數(shù)
Object[] args = joinPoint.getArgs();
System.out.println(className+"=="+methodName+"======"+ Arrays.toString(args));
}
}? 如果需要獲取被增強(qiáng)方法中的異常對象或者返回值則需要在方法參數(shù)上增加一個對應(yīng)類型的參數(shù),并且使用注解的屬性進(jìn)行配置。這樣Spring會把你想獲取的數(shù)據(jù)賦值給對應(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)的異常對象賦值給下面方法的參數(shù)t
public void AfterThrowing(JoinPoint jp,Throwable t){
System.out.println("AfterReturning方法被調(diào)用了");
}
? 相信你肯定覺得上面的獲取方式特別的麻煩難以理解。就可以使用下面這種萬能的方法。
? 直接在環(huán)繞通知方法中增加一個ProceedingJoinPoint類型的參數(shù)。這個參數(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ù)
Object target = pjp.getTarget();//被代理對象
MethodSignature signature = (MethodSignature) pjp.getSignature();//獲取被被增強(qiáng)方法簽名封裝的對象
Object ret = null;
try {
ret = pjp.proceed();//ret就是目標(biāo)方法執(zhí)行后的返回值
} catch (Throwable throwable) {
throwable.printStackTrace();//throwable就是出現(xiàn)異常時的異常對象
}
return ret;
}
1.7 AOP應(yīng)用案例
1.7.1 需求
現(xiàn)有AI核心功能代碼如下:
public class AIController {
//AI自動回答
public String getAnswer(String question){
//AI核心代碼 價值10個億
String str = question.replace("嗎", "");
str = str.replace("?","!");
return str;
}
//AI算
public String fortuneTelling(String name){
//AI算核心代碼
String[] strs = {"女犯傷官把夫克,旱地蓮花栽不活,不是吃上兩家飯,也要刷上三家鍋。","一朵鮮花頭上戴,一年四季也不開,一心想要花開時,采花之人沒到來。","此命生來脾氣暴,上來一陣雙腳跳,對你脾氣啥都好,經(jīng)常與人吵和鬧。"};
int index = name.hashCode() % 3;
return strs[index];
}
}? 現(xiàn)在為了保證數(shù)據(jù)的安全性,要求調(diào)用方法時fortuneTelling傳入的姓名是經(jīng)過加密的。我們需要對傳入的參數(shù)進(jìn)行解密后才能使用。并且要對該方法的返回值進(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 實現(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>②開啟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>
<!--啟動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)對象一定要記得注入Spring容器中
@Controller
public class AIController {
//....
//AI算命
@Crypt
public String fortuneTelling(String name){
System.out.println(name);
//AI算命核心代碼
String[] strs = {"女犯傷官把夫克,旱地蓮花栽不活,不是吃上兩家飯,也要刷上三家鍋。","一朵鮮花頭上戴,一年四季也不開,一心想要花開時,采花之人沒到來。","此命生來脾氣暴,上來一陣雙腳跳,對你脾氣啥都好,經(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 {
//確定切點
@Pointcut("@annotation(com.sangeng.aspect.Crypt)")
public void pt(){
}
//定義通知
@Around("pt()")
public Object crypt(ProceedingJoinPoint pjp) {
//獲取去目標(biāo)方法調(diào)用時的參數(shù)
Object[] args = pjp.getArgs();
//對參數(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;
//對返回值加密后進(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)類上加是對應(yīng)的注解。注入如果是使用注解的方式注入容器要記得開啟組件掃描。
當(dāng)然你也可以在xml中使用bean標(biāo)簽的方式注入容器。
@Component//把切面類注入容器
public class MyAspect {
//..。省略無關(guān)代碼
}
@Service//把目標(biāo)類注入容器
public class UserService {
//..。省略無關(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">
<!--開啟組件掃描-->
<context:component-scan base-package="com.sangeng"></context:component-scan>
<!--配置AOP-->
<aop:config>
<!--定義切點-->
<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 多切面順序問題
? 在實際項目中我們可能會存在配置了多個切面的情況。這種情況下我們很可能需要控制切面的順序。
? 我們在默認(rèn)情況下Spring有它自己的排序規(guī)則。(按照類名排序)
? 默認(rèn)排序規(guī)則往往不符合我們的要求,我們需要進(jìn)行特殊控制。
? 如果是注解方式配置的AOP可以在切面類上加**@Order注解來控制順序。@Order中的屬性越小優(yōu)先級越高。**
? 如果是XML方式配置的AOP,可以通過調(diào)整配置順序來控制。
例如:
下面這種配置方式就會先使用CryptAspect里面的增強(qiáng),在使用APrintLogAspect里的增強(qiáng)
@Component
@Aspect
@Order(2)
public class APrintLogAspect {
//省略無關(guān)代碼
}
@Component
@Aspect
@Order(1)
public class CryptAspect {
//省略無關(guān)代碼
}1.10 AOP原理-動態(tài)代理
? 實際上Spring的AOP其實底層就是使用動態(tài)代理來完成的。并且使用了兩種動態(tài)代理分別是JDK的動態(tài)代理和Cglib動態(tài)代理。
? 所以我們接下去來學(xué)習(xí)下這兩種動態(tài)代理,理解下它們的不同點。
1.10.1 JDK動態(tài)代理
? JDK的動態(tài)代理使用的java.lang.reflect.Proxy這個類來進(jìn)行實現(xiàn)的。要求被代理(被增強(qiáng))的類需要實現(xiàn)了接口。并且JDK動態(tài)代理也只能對接口中的方法進(jìn)行增強(qiáng)。
public static void main(String[] args) {
AIControllerImpl aiController = new AIControllerImpl();
//使用動態(tài)代理增強(qiáng)getAnswer方法
//1.JDK動態(tài)代理
//獲取類加載器
ClassLoader cl = Demo.class.getClassLoader();
//被代理類所實現(xiàn)接口的字節(jié)碼對象數(shù)組
Class<?>[] interfaces = AIControllerImpl.class.getInterfaces();
AIController proxy = (AIController) Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() {
//使用代理對象的方法時 會調(diào)用到invoke
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy 是代理對象
//method 是當(dāng)前被調(diào)用的方法封裝的Method對象
//args 是調(diào)用方法時傳入的參數(shù)
//調(diào)用被代理對象的對應(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動態(tài)代理
? 使用的是org.springframework.cglib.proxy.Enhancer類進(jìn)行實現(xiàn)的。
public class CglibDemo {
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
//設(shè)置父類的字節(jié)碼對象
enhancer.setSuperclass(AIControllerImpl.class);
enhancer.setCallback(new MethodInterceptor() {
//使用代理對象執(zhí)行方法是都會調(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)用父類中對應(yīng)的方法
Object ret = methodProxy.invokeSuper(o, objects);
return ret;
}
});
//生成代理對象
AIControllerImpl proxy = (AIControllerImpl) enhancer.create();
// System.out.println(proxy.getAnswer("你好嗎?"));
System.out.println(proxy.fortuneTelling("你好嗎?"));
}
}1.10.3 總結(jié)
? JDK動態(tài)代理要求被代理(被增強(qiáng))的類必須要實現(xiàn)接口,生成的代理對象相當(dāng)于是被代理對象的兄弟。
? Cglib的動態(tài)代理不要求被代理(被增強(qiáng))的類要實現(xiàn)接口,生成的代理對象相當(dāng)于被代理對象的子類對象。
? Spring的AOP默認(rèn)情況下優(yōu)先使用的是JDK的動態(tài)代理,如果使用不了JDK的動態(tài)代理才會使用Cglib的動態(tài)代理。
1.11 切換默認(rèn)動態(tài)代理方式
? 有的時候我們需要修改AOP的代理方式。
? 我們可以使用以下方式修改:
如果我們是采用注解方式配置AOP的話:
設(shè)置aop:aspectj-autoproxy標(biāo)簽的proxy-target-class屬性為true,代理方式就會修改成Cglib
<aop:aspectj-autoproxy proxy-target-class="true"/>
動態(tài)代理和靜態(tài)代理詳解可以看我這一篇文章: SpringAop實現(xiàn)原理及代理模式詳解
如果我們是采用xml方式配置AOP的話:
設(shè)置aop:config標(biāo)簽的proxy-target-class屬性為true,代理方式就會修改成Cglib
<aop:config proxy-target-class="true"> </aop:config>
到此這篇關(guān)于Spring AOP注解配置與XML配置雙實戰(zhàn)教程的文章就介紹到這了,更多相關(guān)spring aop注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Java基礎(chǔ)篇--面向?qū)ο?(構(gòu)造方法,static、this關(guān)鍵字)
這篇文章主要介紹了Java基礎(chǔ)篇--面向?qū)ο?(構(gòu)造方法,static、this關(guān)鍵字),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-04-04
解決Mybatis中mapper.xml文件update,delete及insert返回值問題
這篇文章主要介紹了解決Mybatis中mapper.xml文件update,delete及insert返回值問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11
spring boot切面execution表達(dá)式添加多個包路徑問題及解決方案
在Spring Boot中,如果你想為多個包中的方法創(chuàng)建一個切面,你可以在@Pointcut注解中使用||操作符來指定多個包,下面給大家分享spring boot切面execution表達(dá)式添加多個包路徑問題及解決方案,感興趣的朋友跟隨小編一起看看吧2024-03-03
SpringBoot?main方法結(jié)束程序不停止的原因分析及解決方法
Spring?Boot啟動內(nèi)嵌Tomcat時,main方法啟動非daemon線程執(zhí)行await()死循環(huán),使JVM保持運(yùn)行,通過發(fā)送關(guān)機(jī)指令可終止程序,下面通過本文給大家介紹SpringBoot?main方法結(jié)束程序不停止的原因分析及解決方法,感興趣的朋友一起看看吧2025-07-07
Spring?Boot?實現(xiàn)字段唯一校驗功能(實例代碼)
這篇文章主要介紹了Spring?Boot?實現(xiàn)字段唯一校驗,實現(xiàn)代碼很簡單,代碼簡單易懂,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-08-08

