關(guān)鍵字 java 代理模式 動態(tài)代理 自定義注解 泛型使用 AOP springboot
1.java靜態(tài)代理
遵循代理模式的思想沮稚,當(dāng)一個對象需要完成某種操作封拧,但是對象本身有不方便去完成的時候眨八,對象可以委托給代理對象去完成相關(guān)操作拴曲。
示例代碼:
假設(shè):boss發(fā)現(xiàn)了系統(tǒng)的bug ,但是boss 修復(fù)不了坑鱼,于是讓可以修復(fù)問題的worker去修改智什。
第一步: 首先要有boss
public class Boss {
}
第二步:要有worker
public class Worker {
}
第三步:worker要有修復(fù)問題的能力蛾茉,這里把這個修復(fù)問題的能力抽象成一個接口
public interface FixBug {
void fixBug();
}
第四步:那有能力修復(fù)問題的worker就要實現(xiàn)這個FixBug的接口,并實現(xiàn)fixBug方法撩鹿。
public class Worker implements FixBug {
@Override
public void fixBug() {
System.out.println("worker 正在修復(fù) 問題 ....");
}
}
第五步:boss要擁有這個worker谦炬,這里把worker當(dāng)做是boss的一個字段(屬性)。
public class Boss {
// 這里要求的是一個有修復(fù)問題能力的worker所以就直接是定義FixBug的字段
private FixBug worker;
public Boss(FixBug fixBug) {
this.worker = fixBug;
}
public void fixBug(){
System.out.println("boss 要讓worker 去修改問題");
this.worker.fixBug();
System.out.println("boss 的worker 已經(jīng)修復(fù)了問題");
}
}
第六步:測試
public class Main {
public static void main(String[] args) {
FixBug worker = new Worker();
Boss boss = new Boss(worker);
boss.fixBug();
}
}
程序輸出:
boss 要讓worker 去修改問題
worker 正在修復(fù) 問題 ....
boss 的worker 已經(jīng)修復(fù)了問題
以上就是個人理解的代理模式节沦。
2.動態(tài)代理
動態(tài)代理的優(yōu)勢是不需要像靜態(tài)代理一樣硬編碼键思。jdk支持在編譯階段動態(tài)去生成代理對象。
依然是上面的那個場景甫贯。
第一步:還是這個修改bug的能力吼鳞。
public interface FixBug {
void fixBug();
}
第二步:還是這個擁有修改bug能力的worker
public class Worker implements FixBug {
@Override
public void fixBug() {
System.out.println("worker 正在修復(fù) 問題 ....");
}
}
第三步:還是一個需要代理的Boss
public class Boss {
}
第四步:確定動態(tài)代理
public class Boss implements InvocationHandler {
private FixBug worker;
public Boss(FixBug fixBug) {
this.worker = fixBug;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("worker開始修復(fù)問題了");
Object invoke = method.invoke(worker, args);
System.out.println("worker修復(fù)問題完成了");
return invoke;
}
}
到了這一步,其實是確定了代理關(guān)系叫搁。
Boss可以監(jiān)控到worker的方法執(zhí)行赔桌。也就是說這里boss變成了worker的代理了。
不過這個實例走到這里代理關(guān)系好像變成了Boss是worker的代理渴逻,不過也不是重點 疾党,重點是看動態(tài)代理的使用。
第五步:執(zhí)行查看效果
public class Main {
public static void main(String[] args) {
// 要被代理的對象
FixBug worker = new Worker();
// 生成代理
Boss boss = new Boss(worker);
// 這一步是獲取到一個被代理的對象 三個參數(shù) 第一個是被代理的對象ClassLoader 惨奕,第二個參數(shù)是 被代理的對象實現(xiàn)的接口雪位,第三個參數(shù)是 代理對象
FixBug fixBug = (FixBug) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), boss);
// 被代理的對象執(zhí)行方法
fixBug.fixBug();
}
}
輸出:
worker開始修復(fù)問題了
worker 正在修復(fù) 問題 ....
worker修復(fù)問題完成了
動態(tài)代理的特點是在編譯過程中 可以動態(tài)生成被代理的對象。
3.總結(jié)整理動態(tài)代理
總結(jié)下jdk動態(tài)代理的特點梨撞。
被代理對象需要有接口實現(xiàn)雹洗。
比如:worker實現(xiàn)了FixBug接口香罐。動態(tài)代理需要實現(xiàn)InvocationHandler接口,并可以獲取到被代理的對象时肿。
比如:Boss實現(xiàn)了InvocationHandler接口庇茫,并在初始化時候傳入了要被代理的對象worker。通過Proxy.newProxyInstance獲取到被代理的對象后螃成,通過被代理的對象執(zhí)行方法 會被代理對象攔截到港令。
比如:
FixBug fixBug = (FixBug) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), boss);
fixBug.fixBug();
輸出:
worker開始修復(fù)問題了
worker 正在修復(fù) 問題 ....
worker修復(fù)問題完成了
當(dāng)被添加了代理的對象 調(diào)用方法的時候,就會被Boss這個動態(tài)代理獲攔截到方法的執(zhí)行锈颗。
4.動態(tài)代理使用過程總結(jié)
想要添加動態(tài)代理的對象需至少要實現(xiàn)一個接口。
動態(tài)代理本身需要實現(xiàn)InvocationHandler咪惠,并可以接收到要被代理的對象,用于發(fā)起方法請求击吱。
如果要使動態(tài)代理生效,需要使用Proxy.newProxyInstance方法來獲取被添加代理的對象遥昧,并使用這個對象來發(fā)起方法調(diào)用覆醇。
5.封裝動態(tài)代理的使用過程
- 第一步: 需要添加動態(tài)代理的對象 可以使用Object來代表。
- 第二步: 動態(tài)代理對象的封裝
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("進(jìn)入方法");
Object invoke = method.invoke(target, args);
System.out.println("即將結(jié)束方法");
return invoke;
}
}
- 第三步:封裝獲取添加代理對象的方法
public static <T> T getProxyObject(T obj) throws Exception {
Class<?>[] interfaces1 = obj.getClass().getInterfaces();
if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的對象要實現(xiàn)與其對應(yīng)的接口");
DynamicProxy dynamicProxy = new DynamicProxy(obj);
ClassLoader classLoader = obj.getClass().getClassLoader();
Class<?>[] interfaces = obj.getClass().getInterfaces();
return (T) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
}
- 第四步:使用過程
public static void main(String[] args) throws Exception{
FixBug fixBug = getProxyObject((FixBug) (new Worker()));
fixBug.fixBug();
}
輸出結(jié)果:
進(jìn)入方法
worker 正在修復(fù) 問題 ....
即將結(jié)束方法
通過方法的封裝√砍簦現(xiàn)在可以快速實現(xiàn)動態(tài)代理永脓。唯一的問題就是動態(tài)代理攔截到方法之后,做的操作太單一鞋仍。
目前的處理,攔截到調(diào)用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("進(jìn)入方法");
Object invoke = method.invoke(target, args);
System.out.println("即將結(jié)束方法");
return invoke;
}
6.如何豐富攔截到方法之后的處理常摧?
如果想要去豐富一類操作的處理。一定是通過抽象出這類操作的共同點威创,定義為接口落午,由接口的實現(xiàn)類去做具體的實現(xiàn)。在使用過程中通過選擇不同的實現(xiàn)類來實現(xiàn)對于操作的靈活處理肚豺。
在我們現(xiàn)在的例子中溃斋,可以抽離出三個方法。
1.進(jìn)入方法時
2.退出方法前
3.結(jié)果處理
抽離出接口則是
public interface MethodAroundHandler {
// 進(jìn)入接口
void enterMethod();
// 退出方法前
void beforReturnMethod();
// 結(jié)果處理
<T>T dealResult(T obj);
}
做一個簡單的實現(xiàn)類
public class SimpleMethodAround implements MethodAroundHandler {
@Override
public void enterMethod() {
System.out.println("進(jìn)入方法");
}
@Override
public void beforReturnMethod() {
System.out.println("方法退出前");
}
@Override
public <T> T dealResult(T obj) {
System.out.println("攔截到方法的返回值:"+obj);
return obj;
}
}
修改之前的DynamicProxy 動態(tài)代理
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SimpleMethodAround simpleMethodAround = new SimpleMethodAround();
simpleMethodAround.enterMethod();
Object invoke = method.invoke(target, args);
simpleMethodAround.beforReturnMethod();
if (invoke != null) simpleMethodAround.dealResult(invoke);
return invoke;
}
}
依然還是原來的main方法
public class Main {
public static <T> T getProxyObject(T obj) throws Exception {
Class<?>[] interfaces1 = obj.getClass().getInterfaces();
if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的對象要實現(xiàn)與其對應(yīng)的接口");
DynamicProxy dynamicProxy = new DynamicProxy(obj);
ClassLoader classLoader = obj.getClass().getClassLoader();
Class<?>[] interfaces = obj.getClass().getInterfaces();
return (T) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
}
public static void main(String[] args) throws Exception{
FixBug fixBug = getProxyObject((FixBug) (new Worker()));
fixBug.fixBug();
}
}
輸出結(jié)果:
進(jìn)入方法
worker 正在修復(fù) 問題 ....
方法退出前
總結(jié)這次的處理吸申。
把原來寫在DynamicProxy中的方法攔擊成功的轉(zhuǎn)移并抽象成了接口梗劫,這樣可以很方便的根據(jù)不同的情況來切換接口實現(xiàn)類。
下面應(yīng)該考慮的是如何去動態(tài)的切換接口實現(xiàn)類截碴。
7.通過注解的方式動態(tài)切換接口實現(xiàn)類
現(xiàn)在需要動態(tài)切換接口實現(xiàn)類的地方是在DynamicProxy 的invoke方法中梳侨。
先回回顧下這個方法
public class DynamicProxy implements InvocationHandler {
// 被代理的對象
private Object target;
// 初始化時傳入對象
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 初始化接口實現(xiàn)類
SimpleMethodAround simpleMethodAround = new SimpleMethodAround();
// 調(diào)用接口方法
simpleMethodAround.enterMethod();
// 調(diào)用原方法
Object invoke = method.invoke(target, args);
// 調(diào)用接口方法
simpleMethodAround.beforReturnMethod();
// 如果歐返回值 可以調(diào)用最后一個接口放方法
if (invoke != null) simpleMethodAround.dealResult(invoke);
// 最后返回結(jié)果
return invoke;
}
}
在invoke 可以拿到要調(diào)用的方法Method,這樣就可以使用方法注解來指定使用哪個接口實現(xiàn)類日丹,進(jìn)而可以根據(jù)不同的注解來實例化不同的接口實現(xiàn)類猫妙。
這里修改invoke方法實現(xiàn)。主要是修改接口實現(xiàn)類的實例化過程聚凹。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果是被注解標(biāo)注的方法 才會被攔截
if (method.isAnnotationPresent(DynamicProxyMethod.class)) {
// 獲取到注解
DynamicProxyMethod annotation = method.getAnnotation(DynamicProxyMethod.class);
// 獲取到接口實現(xiàn)類
MethodAroundHandler methodAroundHandler = (MethodAroundHandler) annotation.type().newInstance();
// 調(diào)用接口方法
methodAroundHandler.enterMethod();
Object invoke = method.invoke(target, args);
methodAroundHandler.beforReturnMethod();
if (invoke != null) methodAroundHandler.dealResult(invoke);
return invoke;
}
return method.invoke(target, args);
}
定義方法注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicProxyMethod {
Class type() default SimpleMethodAround.class;
}
給原來的fixbug方法添加注解
public interface FixBug {
@DynamicProxyMethod // 默認(rèn)是SimpleMethodAround.class
void fixBug();
}
還是原來的Main方法
public class Main {
public static <T> T getProxyObject(T obj) throws Exception {
Class<?>[] interfaces1 = obj.getClass().getInterfaces();
if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的對象要實現(xiàn)與其對應(yīng)的接口");
DynamicProxy dynamicProxy = new DynamicProxy(obj);
ClassLoader classLoader = obj.getClass().getClassLoader();
Class<?>[] interfaces = obj.getClass().getInterfaces();
return (T) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
}
public static void main(String[] args) throws Exception{
FixBug fixBug = getProxyObject((FixBug) (new Worker()));
fixBug.fixBug();
}
}
輸出:
進(jìn)入方法
worker 正在修復(fù) 問題 ....
方法退出前
到此 基本實現(xiàn)了對于方法的攔截 實現(xiàn)了方法進(jìn)入時 返回前 以及 對結(jié)果的處理
8.整理代碼
注解定義
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicProxyMethod {
Class type() default SimpleMethodAround.class;
}
方法攔截接口定義
public interface MethodAroundHandler {
void enterMethod();
void beforReturnMethod();
<T>T dealResult(T obj);
}
兩個MethodAroundHandler接口實現(xiàn)
public class SimpleMethodAround implements MethodAroundHandler {
@Override
public void enterMethod() {
System.out.println("進(jìn)入方法");
}
@Override
public void beforReturnMethod() {
System.out.println("方法退出前");
}
@Override
public <T> T dealResult(T obj) {
System.out.println("攔截到方法的返回值:"+obj);
return obj;
}
}
public class SimpleMethodAround2 implements MethodAroundHandler {
@Override
public void enterMethod() {
System.out.println("222 進(jìn)入方法");
}
@Override
public void beforReturnMethod() {
System.out.println("222 方法退出前");
}
@Override
public <T> T dealResult(T obj) {
System.out.println("222 攔截到方法的返回值:"+obj);
return obj;
}
}
要攔截的對象要實現(xiàn)一個接口
public interface FixBug {
@DynamicProxyMethod // 注解標(biāo)注要使用哪個MethodAroundHandler接口實現(xiàn)類
void fixBug();
@DynamicProxyMethod(type = SimpleMethodAround2.class) // 注解標(biāo)注要使用哪個MethodAroundHandler接口實現(xiàn)類
String coding();
}
要被動態(tài)代理的對象
public class Worker implements FixBug {
@Override
public void fixBug() {
System.out.println("worker 正在修復(fù) 問題 ....");
}
@Override
public String coding() {
return "worker 正在coding .....";
}
}
動態(tài)代理實現(xiàn)
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxy implements InvocationHandler {
private Object target;
public DynamicProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果是被注解標(biāo)注的方法 才會被攔截
if (method.isAnnotationPresent(DynamicProxyMethod.class)) {
// 獲取到注解
DynamicProxyMethod annotation = method.getAnnotation(DynamicProxyMethod.class);
// 獲取到接口實現(xiàn)類
MethodAroundHandler methodAroundHandler = (MethodAroundHandler) annotation.type().newInstance();
// 調(diào)用接口方法
methodAroundHandler.enterMethod();
Object invoke = method.invoke(target, args);
methodAroundHandler.beforReturnMethod();
if (invoke != null) methodAroundHandler.dealResult(invoke);
return invoke;
}
return method.invoke(target, args);
}
}
Main函數(shù)
public class Main {
public static <T> T getProxyObject(T obj) throws Exception {
Class<?>[] interfaces1 = obj.getClass().getInterfaces();
if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的對象要實現(xiàn)與其對應(yīng)的接口");
DynamicProxy dynamicProxy = new DynamicProxy(obj);
ClassLoader classLoader = obj.getClass().getClassLoader();
Class<?>[] interfaces = obj.getClass().getInterfaces();
return (T) Proxy.newProxyInstance(classLoader, interfaces, dynamicProxy);
}
public static void main(String[] args) throws Exception{
FixBug fixBug = getProxyObject((FixBug) (new Worker()));
fixBug.fixBug();
fixBug.coding();
}
}
最終輸出:
進(jìn)入方法
worker 正在修復(fù) 問題 ....
方法退出前
222 進(jìn)入方法
222 方法退出前
222 攔截到方法的返回值:worker 正在coding .....
9. 最后MARK下
這個思路的實現(xiàn)割坠,我封裝了springboot-starter-dynamic-aop齐帚。
與文章中所講的思路略微有些不同。
主要是利用了spring的Bean容器特性彼哼。來幫助創(chuàng)建動態(tài)代理对妄。
githup地址: https://github.com/LH-0811/lh-springboot-starter-dynamic-aop
10.最后的最后
寫了很多,希望大家能提出意見敢朱。指出我在思想上的不足剪菱。期待共同進(jìn)步!拴签!謝謝大家孝常。