spring-aop筆記
2、Spring-AOP
2.1、代理【Proxy】
-
動態代理:
-
特點:位元組碼誰用誰建立,誰用誰載入
-
作用:不修改原始碼的基礎上對方法增強
-
分類:
-
基於介面的動態代理
-
基於子類的動態代理
-
-
基於介面的動態代理
-
涉及的類:Proxy
-
提供者:JDK官方
-
-
如何建立代理物件:
-
使用Proxy類中的newProxyInstance方法
-
-
建立代理物件的要求:
-
被代理類最少實現一個介面,如果沒有則不能使用
-
-
newProxyInstance方法的引數:
-
ClassLoader:類載入器
-
它是用於載入代理物件位元組碼的.和被代理物件使用相同的類載入器。固定寫法。
-
-
Class[]
-
它是用於讓代理物件和被代理物件有相同方法。固定寫法。
-
-
InvocationHandler
-
它是讓我們寫如何代理。我們一般都是寫一個該介面的實現類,通常情況下都是匿名內部類,但不是必須的。
-
此介面的實現類都是誰用誰寫
-
-
-
來測試一下:
2.1.1、未使用代理
1.定義一個介面,要被代理的介面:Producer【提供者】
//生產者
public interface Producer {
//銷售
public void saleProduct(float money);
//售後
public void afterService(float money);
}
2.定義一個類,ProducerImpl【提供者】(要被代理的角色)
//生產者
public class ProducerImpl implements Producer {
//銷售
public void saleProduct(float money){
System.out.println("銷售產品,拿到錢:" + money);
}
//售後
public void afterService(float money){
System.out.println("售後服務,拿到錢:" + money);
}
}
3.定義一個類,Client【消費者】(未使用代理,直接面向廠家)
//模擬一個消費者(未使用代理)
public class Client {
public static void main(String[] args){
IProducer producer = new Producer();
producer.saleProduct(1000f);
}
}
結果:
2.1.2、使用了代理
1.定義一個類,ProxyMan【代理人】(不收中介費的時候)
public class ProxyMan implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader() ,target.getClass().getInterfaces() ,this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target ,args);
}
}
2.定義一個類,Client【消費者】(使用了代理)
//模擬一個消費者
public class Client {
public static void main(String[] args){
IProducer producer = new Producer();
// producer.saleProduct(1000f);
//代理模式
ProxyMan pm = new ProxyMan();
pm.setTarget(producer);
IProducer producerPoxy = (IProducer)pm.getProxy();
producerPoxy.afterService(1000f);
}
}
結果:
3.當代理人收代理費時(抽取兩成):
public class ProxyMan implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader() ,target.getClass().getInterfaces() ,this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object value = null;
Float money = (Float) args[0];
if("saleProduct".equals(method.getName())) {
value = method.invoke(target, money * 0.8f);
}
if("afterService".equals(method.getName())){
value = method.invoke(target ,args);
}
return value;
}
}
結果:
被成功抽取兩成!
在不改變原有程式碼的時候,改變傳輸的資訊。不僅僅可以改變值,還可以增加其他東西,代理模式大多數情況用在增加日誌等功能。
2.2、Spring中的AOP
2.2.1、AOP相關術語:
-
Joinpoint(連線點):
-
所謂連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點。
-
-
Pointout(切入點):
-
所謂切入點是指我們要對那些
Joinpoint
進行攔截的定義。
-
-
Advice(通知/增強)
-
所謂通知是指攔截到Joinpoint之後所要做的事情就是通知。
-
通知型別:前置通知,後置通知,異常通知,環繞通知。
-
-
Introduction(引介):
-
引介是一種特殊的通知,在不修改類程式碼的前提下,Introduction可以在執行期為類動態的新增一些方法或Field。
-
-
Target(目標物件)
-
代理的目標物件(也就是說被代理的物件)
-
-
Weaving(織入):
-
是指把增強應用到目標物件來建立新的代理物件的過程。
-
spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。
-
-
Proxy(代理):
-
一個類被AOP織入增強後,就產生一個結果代理類。
-
-
Aspect(切面):
-
是切入點和通知(引介)的結合。
-
2.2.2、AOP的配置內容
spring中基於XML的AOP配置步驟:
-
把通知Bean也較給spring來管理
<!-- 配置Logger類 -->
<bean id="logger" class="com.gzk.utils.Logger"></bean> -
使用aop:config標籤表明開始AOP的配置
-
使用aop:aspect標籤表明配置切面
-
id屬性:是給切面提供一個唯一標識
-
ref屬性:是指定通知類bean的ID
-
-
在aop:aspect標籤的內部使用對應標籤來配置通知的型別
-
我們現在示例是讓pringLog方法在切入點方法執行之前:所以是前置通知
-
aop:before:表示配置前置通知
-
method屬性:用於指定Logger類中哪個方法是前置通知
-
pointcut屬性:用於指定切入點表示式,該表示式的含義指的是對業務層中哪些方法增強
-
-
aop:after-returning:表示配置後置通知
-
method屬性:用於指定Logger類中哪個方法是後置通知
-
pointcut屬性:用於指定切入點表示式,該表示式的含義指的是對業務層中哪些方法增強
-
-
aop:after-throwing:表示配置異常通知
-
method屬性:用於指定Logger類中哪個方法是異常通知
-
pointcut屬性:用於指定切入點表示式,該表示式的含義指的是對業務層中哪些方法增強
-
-
aop:after:表示配置最終通知
-
method屬性:用於指定Logger類中哪個方法是最終通知
-
pointcut屬性:用於指定切入點表示式,該表示式的含義指的是對業務層中哪些方法增強
-
-
切入點表示式的寫法:
-
關鍵字:execution(表示式)
-
表示式:
-
訪問修飾符 返回值 包名.包名...類名.方法名(引數列表)
-
-
標準表示式寫法(例):
-
public void com.gzk.servicce.UserServiceImpl.test()
-
-
訪問修飾符可以省略
-
void com.gzk.servicce.UserServiceImpl.test()
-
-
返回值可以使用萬用字元,表示任意返回值
-
* com.gzk.servicce.UserServiceImpl.test()
-
-
包名可以使用萬用字元,表示任意包。但是有幾級包,就需要寫幾個
*
。-
* *.*.*.UserServiceImpl.test()
-
-
包名可以使用
..
表示當前包及其子包-
* *..UserServiceImpl.test()
-
-
類名和方法名都可以使用
*
來實現通配-
* *..*.*()
-
-
引數列表:
-
可以直接寫資料型別:
-
基本型別直接寫名稱:int(例)
-
* *..*.*(int)
-
-
引用型別寫包名.類名的方式:java.lang.String(例)
-
* *..*.*(java.lang.String)
-
-
-
可以使用萬用字元
*
表示任意型別,但是必須有引數-
* *..*.*(*)
-
-
可以使用
..
表示有無引數均可,有引數可以使任意型別-
* *..*.*(..)
-
-
-
全通配寫法:
-
* *..*.*(..)
-
-
實際開發中,很少用全通配寫法,因為會全部被代理,切入點表示式的通常寫法:
-
切到業務層或者要被代理的層(或包)實現類下的所有方法:
-
* com.gzk.service.*.*(..)
-
-
-
-
2.2.3、測試(非註解)
測試四種常用通知型別【前置通知,後置通知,異常通知,最終通知】
1.隨意配置mapper和service包下的類,用作測試
public interface UserMapper {
void test();
}
public class UserMapperImpl implements UserMapper {
public void test() {
System.out.println("測試中。。。");
}
}
public interface UserService {
void test();
}
public class UserServiceImpl implements UserService {
private UserMapper userMapper;
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
public void test() {
int i = 1/0;
userMapper.test();
}
}
2.將UserServiceImpl設為被代理的類
3.新建一個utils包,建立一個Logger類【代理類】
public class Logger {
public void beforeLogger(){
System.out.println("前置通知");
}
public void afterReturningLogger(){
System.out.println("後置通知");
}
public void afterThrowingLogger(){
System.out.println("異常通知");
}
public void afterLogger(){
System.out.println("最終通知");
}
}
4.配置xml檔案
5.測試:
public class UserTest {
6.結果
當發生異常時:
2.2.4、通用化切入點配置
2.2.5、環繞通知
1.將其他通知註釋掉,寫一個環繞通知:
<aop:around method="aroundLogger" pointcut-ref="point-cut"/>
2.在Logger類中加入環繞方法:
public void aroundLogger(){
System.out.println("環繞通知");
}
3.結果:
很明顯出問題了,被代理類中的方法沒了,是什麼問題呢?
-
問題:
-
當我們配置了環繞通知之後,切入點方法沒有執行,而通知方法執行了。
-
-
分析:
-
通過對比動態代理中的環繞通知程式碼,發現動態代理的環繞通知有明確的切入點方法呼叫,而我們的程式碼沒有。
-
-
解決:
-
spring框架為我們提供了一個介面,ProceedingJoinPoint。該介面有一個方法proceed(),此方法就相當於明確呼叫切入點方法。
-
該介面可以作為環繞通知的方法引數,在程式執行時,spring框架會為我們提供介面的實現類供我們呼叫。
-
好了,問題找到且分析了,那我們開始寫程式碼吧
xml配置檔案不用變,但Logger代理類中的aroundLogger()方法就要改變。
//環繞通知
public Object aroundLogger(ProceedingJoinPoint point){
Object[] args = point.getArgs();
Object proceed;
try {
//前置通知
System.out.println("前置通知");
proceed = point.proceed(args);
//後置通知
System.out.println("後置通知");
return proceed;
}catch (Throwable e){
//異常通知
System.out.println("異常通知");
throw new RuntimeException(e);
}finally {
//最終通知
System.out.println("最終通知");
}
}
結果:
2.2.6、測試(註解)
1.通過註解方式隨意配置mapper和service包下的類,用作測試
public interface UserMapper {
void test();
}
public interface UserService {
void test();
}
2.將UserServiceImpl設為被代理的類
3.新建一個utils包,建立一個Logger類【代理類】(使用註解)
4.此時有兩種方式來配置 xml 檔案,一種是xml,一種是java
4.1、xml為配置檔案
4.2、java為配置檔案,建立一個config包,在下面新建一個類
5.測試
5.1、使用xml為配置類測試:
public class UserTest {
5.2、使用java為配置類測試
public class UserTest {
6.結果: