demo
在本 demo 中我們將創(chuàng)建一個 door
接口,實(shí)現(xiàn)對接口中所有方法的代理。
door.java
public interface Door {
void enter();
void out();
}
HomeDoorImpl.java
public class HomeDoorImpl implements Door{
@Override
public void enter() {
System.out.println("enter the home door");
}
@Override
public void out() {
System.out.println("out the home door");
}
}
main
and proxy
public class Proxy implements InvocationHandler {
private Object target;
public Proxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start proxy");
Object res = method.invoke(target, args);
System.out.println("end proxy");
return res;
}
public static void main(String[] args) {
Door door;
Proxy proxy = new Proxy(door = new HomeDoorImpl());
door = (Door) java.lang.reflect.Proxy.newProxyInstance(door.getClass().getClassLoader(),
door.getClass().getInterfaces(), proxy);
door.enter();
door.out();
}
}
運(yùn)行
輸出
start proxy
enter the home door
end proxy
start proxy
out the home door
end proxy
僅代理某個方法
可以看到我們將 Door
接口中的所有方法都代理躲查,但在實(shí)際業(yè)務(wù)中可能僅代理某些方法上陕。
接下來做一些小改動來支持這個功能。我們決定通過在方法上添加注解決定是否真正代理(PS:本質(zhì)上實(shí)現(xiàn)類的所有方法都會被代理)
NeedProxy.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedProxy {}
HomeDoorImpl.java
為 enter
方法添加注解
public class HomeDoorImpl implements Door{
@NeedProxy
@Override
public void enter() {
System.out.println("enter the home door");
}
@Override
public void out() {
System.out.println("out the home door");
}
}
Proxy.java
使用 Map 集合保存了需要代理的方法球匕,在 invoke function 時判斷是否執(zhí)行代理邏輯嘹悼。
public class Proxy implements InvocationHandler {
private HashMap<String, Object> proxyMethods = new HashMap<>();
private Object target;
public Proxy(Object target) {
this.target = target;
Method[] methods = target.getClass().getMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(NeedProxy.class)) {
proxyMethods.put(method.getName(), Void.TYPE);
break;
}
}
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (proxyMethods.containsKey(method.getName())) {
return invoke0(proxy, method, args);
}
return method.invoke(target, args);
}
private Object invoke0(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
System.out.println("start proxy");
Object res = method.invoke(target, args);
System.out.println("end proxy");
return res;
}
public static void main(String[] args) {
Door door;
Proxy proxy = new Proxy(door = new HomeDoorImpl());
door = (Door) java.lang.reflect.Proxy.newProxyInstance(door.getClass().getClassLoader(),
door.getClass().getInterfaces(), proxy);
door.enter();
door.out();
}
}
輸出
------start proxy-----
enter the home door
------end proxy-------
out the home door
實(shí)現(xiàn)一個 before-after
真實(shí)業(yè)務(wù)上不可能所有方法都是一樣的代理邏輯(PS:logger 是可以使用同一個邏輯的)如何模擬實(shí)現(xiàn)一個類似真實(shí)業(yè)務(wù)的 before-after叛甫,我們決定在 NeedProxy
和 Proxy
上下工夫。
BeforeAfter.java
定義一個 before-after 接口杨伙,這里可以根據(jù)不同的業(yè)務(wù)邏輯實(shí)現(xiàn),after
方法頁可以包裝以下 result
再吐出去萌腿。
public interface BeforeAfter{
void before(Object[] args);
void after(Object result);
}
NeedProxy.java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface NeedProxy {
Class<? extends BeforeAfter> beforeAfter();
}
HomeDoorImpl.java
public class HomeDoorImpl implements Door{
@NeedProxy(beforeAfter = HomeDoorEnterBeforeAfter.class)
@Override
public void enter() {
System.out.println("enter the home door");
}
@Override
public void out() {
System.out.println("out the home door");
}
}
HomeDoorEnterBeforeAfter.java
public class HomeDoorEnterBeforeAfter implements BeforeAfter {
@Override
public void before(Object[] args) {
System.out.println("打開室外燈");
}
@Override
public void after(Object result) {
System.out.println("關(guān)閉室外燈");
}
}
Door.java
目前代理邏輯中還沒有一個 HomeDoorEnterBeforeAfter
實(shí)例限匣,可能 BeforeAfter
構(gòu)造方法需要依賴。下方采用反射構(gòu)造實(shí)例。
public class Proxy implements InvocationHandler {
private HashMap<String, BeforeAfter> proxyMethods = new HashMap<>();
private Object target;
public Proxy(Object target) throws IllegalAccessException, InstantiationException {
this.target = target;
Method[] methods = target.getClass().getMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType().equals(NeedProxy.class)) {
NeedProxy needProxy = (NeedProxy) annotation;
proxyMethods.put(method.getName(), needProxy.beforeAfter().newInstance());
break;
}
}
}
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
BeforeAfter beforeAfter;
if ((beforeAfter = proxyMethods.get(method.getName())) != null) {
return invoke0(beforeAfter, proxy, method, args);
}
return method.invoke(target, args);
}
private Object invoke0(BeforeAfter beforeAfter, Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
beforeAfter.before(args);
Object res = method.invoke(target, args);
beforeAfter.after(res);
return res;
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
Door door;
Proxy proxy = new Proxy(door = new HomeDoorImpl());
door = (Door) java.lang.reflect.Proxy.newProxyInstance(door.getClass().getClassLoader(),
door.getClass().getInterfaces(), proxy);
door.enter();
door.out();
}
}
輸出
打開室外燈
enter the home door
關(guān)閉室外燈
out the home door
小結(jié)
可以看到 JDK 的動態(tài)代理實(shí)現(xiàn)代理顆粒度較為粗糙米死,并且需要入侵所有方法锌历,性能上可能有所下降。讀者如果對 Proxy
有興趣的話峦筒,可以進(jìn)一步了解 cglib
是如何實(shí)現(xiàn)代理的究西。