參考示例源代碼:
https://github.com/huangzhenshi/SpringFreamworkDemo/tree/master
概述
Spring代理模式的運用場景: 操作日志改橘、事務(wù)控制识啦、異常處理(AOP ThrowsAdvice)、權(quán)限認(rèn)證恍涂、緩存
Spring AOP實現(xiàn)的默認(rèn)兩種方式:DK動態(tài)代理右蕊、CGLIB
- JDK動態(tài)代理 反射原理的類依賴其接口砰奕,代理接口的實現(xiàn)方法,普通方法無法代理燎含。
- CGLIB宾濒,字節(jié)碼增強(qiáng)技術(shù),根據(jù)代理類實現(xiàn)一個增強(qiáng)的子類,采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用屏箍,并順勢織入橫切邏輯
設(shè)置proxy-target-class=true绘梦,全部都用CGLIB橘忱,false則接口類用反射,非接口類用CGLIB
Spring實現(xiàn)代理有4種方式:子類繼承卸奉、@Aspect注解钝诚、JDK動態(tài)代理、CGLIB榄棵,各有各適用的場景
代理模式分析
繼承的方法凝颇,最靈活,可以精準(zhǔn)實現(xiàn)一個被代理類的一個方法疹鳄。
@Aspect 先定義一個自定義注解拧略,再定義一個@Aspect類,并且把切點指定為自定義注解瘪弓,編寫不同業(yè)務(wù)場景下的代理邏輯垫蛆,最后直接在需要代理的方法上使用。優(yōu)點是非常容易復(fù)用腺怯,而且封裝了復(fù)雜性袱饭。編譯期生成代碼。
JDK動態(tài)代理瓢喉,半靈活宁赤,把需要代理的方法寫在繼承接口里面舀透,不希望被代理的方法栓票,寫在普通方法里面。
CGLIG最方便愕够,不依賴接口走贪,全部方法都用相同的代理邏輯
可以通過繼承的方式,然后重寫父類的方法惑芭,這樣實現(xiàn)代理模式坠狡。
優(yōu)勢:特別靈活,可以根據(jù)業(yè)務(wù)需求遂跟,為某個代理類的每個方法實現(xiàn)不同的代理邏輯
缺點:代碼量太大逃沿。如果一個類的多個方法代理邏輯一樣,或者多各類的代理邏輯一樣幻锁,無法復(fù)用凯亮。@Aspect是針對某一類方法而創(chuàng)造的,比如對service層的所有新增的方法哄尔,添加日志的功能假消。
優(yōu)勢: 編碼簡潔(寫一個工具注解既可以,然后在需要代理的類上做注解即可)岭接,可以實現(xiàn)精準(zhǔn)代理富拗,是針對方法的代理模式
JDK動態(tài)代理臼予,實體類所有的接口方法,都會執(zhí)行相同的代理邏輯
優(yōu)點:可以批量代理一個代理類里面所有接口中的方法啃沪;不同的代理類繼承相同的接口粘拾,可以復(fù)用代理工具類,只要代理邏輯一樣
缺點:完全依賴接口谅阿,所有要代理的方法必須要在接口中存在半哟,否則無法代理CGLIG代理模式:
優(yōu)點:沒有束縛,通過特殊的處理签餐,通過繼承和織入寓涨,對該類的所有的方法,都織入相同的代理邏輯
缺點:缺乏靈活性氯檐,無法為代理類的某一個方法定制特殊的代理邏輯
JDK動態(tài)代理實現(xiàn)
實現(xiàn)步驟:
- 寫一個接口Subject戒良,把所有要代理的方法都要寫在接口里面
- 被代理類RealSubject實現(xiàn)接口,實現(xiàn)自己本身的業(yè)務(wù)邏輯冠摄,當(dāng)然也可以包含一些不想被代理的方法糯崎,只要接口里沒有,都不會被代理
- 寫一個代理工具類Dynamicproxy河泳,實現(xiàn)InvocationHandler接口沃呢,實現(xiàn)invoke方法,編寫代理邏輯
- 創(chuàng)建代理類拆挥,調(diào)用代理方法
注意事項:被代理類里面可以包含接口里面沒有的方法薄霜,實現(xiàn)保護(hù)某些方法被代理,這些未暴露的方法纸兔,在代理類中是不可見的惰瓜,也不能被調(diào)用。
代碼
public interface Subject {
public void rent();
public void hello(String str);
}
public class RealSubject implements Subject{
@Override
public void rent() {
System.out.println("I want rent my house");
}
@Override
public void hello(String str) {
System.out.println("hello:"+str);
}
public void test(){
System.out.println("Test");
}
}
代理工具類
public class Dynamicproxy implements InvocationHandler{
//這就是我們代理的真實對象
private Object subject;
//構(gòu)造方法汉矿,給我們要代理的真實對象賦值
public Dynamicproxy(Object subject){
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在代理真實對象前我們可以添加一些自己的操作
System.out.println("代理開始");
System.out.println("代理Method:"+method);
//當(dāng)代理對象調(diào)用真實對象的方法時崎坊,其會自動的跳轉(zhuǎn)到代理對象關(guān)聯(lián)的handler對象的invoke方法來進(jìn)行調(diào)用
method.invoke(subject, args);
//在代理真實對象后我們可以添加一些自己的操作
System.out.println("代理結(jié)束");
return null;
}
}
代理
public class JDK動態(tài)代理 {
public static void main(String[] args) {
//我們要代理的真實對象
RealSubject realSubject = new RealSubject();
//我們要代理哪個真實對象,就將該對象傳進(jìn)去洲拇,最后通過該真實的對象調(diào)用該方法
InvocationHandler handler = new Dynamicproxy(realSubject);
Subject subject = (Subject)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), handler);
System.out.println(subject.getClass().getName());
subject.rent();
subject.hello("world");
}
}
CGLib實現(xiàn)
- 通過實現(xiàn)MethodInterceptor的intercept方法奈揍,來對所有的代理類進(jìn)行代理操作。
- 具體涉及到繼承的增強(qiáng)類子類Enhancer類赋续。并設(shè)置子類的方法調(diào)用觸發(fā)器
public class CglibProxy implements MethodInterceptor {
// 被代理對象
Object targetObject;
//參數(shù)為被代理類男翰,返回結(jié)果為代理類
public Object getProxyObject(Object object) {
this.targetObject = object;
//增強(qiáng)器,動態(tài)代碼生成器
Enhancer enhancer=new Enhancer();
//回調(diào)方法
enhancer.setCallback(this);
//設(shè)置生成類的父類類型
enhancer.setSuperclass(targetObject.getClass());
//動態(tài)生成字節(jié)碼并返回代理對象
return enhancer.create();
}
// 攔截方法
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("方法調(diào)用前");
// 調(diào)用方法
Object result = methodProxy.invoke(targetObject, args);
System.out.println("方法調(diào)用后");
return result;
}
}
Spring中的配置
- Pointcut(切點):指定在哪些類的哪些方法上織入橫切邏輯
- Advice(增強(qiáng)):描述橫切邏輯和方法的具體織入點(方法前蚕捉、方法后奏篙、方法的兩端等)
- Advisor(切面):將Pointcut和Advice兩者組裝起來。有了Advisor的信息,Spring就可以利用JDK或CGLib的動態(tài)代理技術(shù)采用統(tǒng)一的方式為目標(biāo)Bean創(chuàng)建織入切面的代理對象了
<aop:config expose-proxy="true" proxy-target-class="true">
<aop:pointcut id="txPointcut" expression="execution(* com.test.service..*.*(..))"/>
<aop:advisor pointcut-ref="txPointcut" advice-ref="txAdvice"/>
</aop:config>
自定義注解的使用
http://www.reibang.com/p/7dcd59bdbb0a
- 通過元注解給注解做修飾
- 在對應(yīng)的地方使用注解(可以是類上加注解秘通、方法上加注解为严、屬性上加注解)
- 通過反射獲取到目標(biāo)(類、方法肺稀、屬性)第股,并調(diào)用 ***.getAnnotation(),從而獲取注解類
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyMessage {
String name() default "sam";
int num() default 0;
String desc();
}
public class AnnotationTest {
@MyMessage(num = 10, desc = "參數(shù)a")
private static int a;
@MyMessage(name = "Sam test", desc = "測試方法test")
public void test() {
System.out.println("test");
}
}
public static void main(String[] args) throws Throwable{
//加載annotationTest.class類
Class clazz = Class.forName("com.test.aop.自定義注解.AnnotationTest");
//獲取屬性
Field[] fields = clazz.getDeclaredFields();
//遍歷屬性
for (Field field : fields) {
MyMessage myMessage = field.getAnnotation(MyMessage.class);
System.out.println("name:" + myMessage.name() + " num:" + myMessage.num() + " desc:" + myMessage.desc());
}
//獲取類中的方法
Method[] methods = clazz.getMethods();
//遍歷方法
for (Method method : methods) {
//判斷方法是否帶有MyMessage注解
if (method.isAnnotationPresent(MyMessage.class)) {
// 獲取所有注解 method.getDeclaredAnnotations();
// 獲取MyMessage注解
MyMessage myMessage = method.getAnnotation(MyMessage.class);
System.out.println("name:" + myMessage.name() + " num:" + myMessage.num() + " desc:" + myMessage.desc());
}
}
}
Aspect
https://www.cnblogs.com/magicalSam/p/7161942.html
原理:通過自定義注解+Aspect技術(shù)實現(xiàn)AOP代理
- 通過@Aspect注解使該類成為切面類
- 通過@Pointcut 指定切入點 话原,這里指定的切入點為MyLog注解類型夕吻,也就是被@MyLog注解修飾的方法,進(jìn)入該切入點繁仁。
- @Before 前置通知:在某連接點之前執(zhí)行的通知涉馅,但這個通知不能阻止連接點之前的執(zhí)行流程(除非它拋出一個異常)。
- @Around 環(huán)繞通知:可以實現(xiàn)方法執(zhí)行前后操作黄虱,需要在方法內(nèi)執(zhí)行point.proceed(); 并返回結(jié)果稚矿。
- @AfterReturning 后置通知:在某連接點正常完成后執(zhí)行的通知:例如,一個方法沒有拋出任何異常捻浦,正常返回晤揣。
- @AfterThrowing 異常通知:在方法拋出異常退出時執(zhí)行的通知。
- @After 后置通知:在某連接點正常完成后執(zhí)行的通知:例如朱灿,一個方法沒有拋出任何異常昧识,正常返回
@Aspect
@Component
public class MyLogAspect {
//切入點
@Pointcut(value = "@annotation(com.test.aop.aspectj.MyLog)")
private void pointcut() {
}
//pointcut() 設(shè)置切點,也就是aspect綁定Mylog注解的一個紐帶
//@annotation(myLog) 注入當(dāng)前的mylog盗扒,這樣才可以正常的mylog.requestUrl了
@Around(value = "pointcut()&& @annotation(myLog)")
public Object around(ProceedingJoinPoint point, MyLog myLog) {
System.out.println("++++執(zhí)行了around方法++++");
//攔截的類名
Class clazz = point.getTarget().getClass();
//攔截的方法
Method method = ((MethodSignature) point.getSignature()).getMethod();
System.out.println("執(zhí)行了 類:" + clazz + " 方法:" + method + " 自定義請求地址:"+myLog.requestUrl());
try {
Object result=point.proceed();
System.out.println("++++執(zhí)行了around方法結(jié)束++++");
return result;//執(zhí)行程序
} catch (Throwable throwable) {
throwable.printStackTrace();
return throwable.getMessage();
}
}
}