关键字 java 代理模式 动态代理 自定义注解 泛型使用 AOP springboot
1.java静态代理
遵循代理模式的思想,当一个对象需要完成某种操作,但是对象本身有不方便去完成的时候,对象可以委托给代理对象去完成相关操作。
示例代码:
假设:boss发现了系统的bug ,但是boss 修复不了,于是让可以修复问题的worker去修改。
第一步: 首先要有boss
public class Boss {
}
第二步:要有worker
public class Worker {
}
第三步:worker要有修复问题的能力,这里把这个修复问题的能力抽象成一个接口
public interface FixBug {
void fixBug();
}
第四步:那有能力修复问题的worker就要实现这个FixBug的接口,并实现fixBug方法。
public class Worker implements FixBug {
@Override
public void fixBug() {
System.out.println("worker 正在修复 问题 ....");
}
}
第五步:boss要拥有这个worker,这里把worker当做是boss的一个字段(属性)。
public class Boss {
// 这里要求的是一个有修复问题能力的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 已经修复了问题");
}
}
第六步:测试
public class Main {
public static void main(String[] args) {
FixBug worker = new Worker();
Boss boss = new Boss(worker);
boss.fixBug();
}
}
程序输出:
boss 要让worker 去修改问题
worker 正在修复 问题 ....
boss 的worker 已经修复了问题
以上就是个人理解的代理模式。
2.动态代理
动态代理的优势是不需要像静态代理一样硬编码。jdk支持在编译阶段动态去生成代理对象。
依然是上面的那个场景。
第一步:还是这个修改bug的能力。
public interface FixBug {
void fixBug();
}
第二步:还是这个拥有修改bug能力的worker
public class Worker implements FixBug {
@Override
public void fixBug() {
System.out.println("worker 正在修复 问题 ....");
}
}
第三步:还是一个需要代理的Boss
public class Boss {
}
第四步:确定动态代理
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开始修复问题了");
Object invoke = method.invoke(worker, args);
System.out.println("worker修复问题完成了");
return invoke;
}
}
到了这一步,其实是确定了代理关系。
Boss可以监控到worker的方法执行。也就是说这里boss变成了worker的代理了。
不过这个实例走到这里代理关系好像变成了Boss是worker的代理,不过也不是重点 ,重点是看动态代理的使用。
第五步:执行查看效果
public class Main {
public static void main(String[] args) {
// 要被代理的对象
FixBug worker = new Worker();
// 生成代理
Boss boss = new Boss(worker);
// 这一步是获取到一个被代理的对象 三个参数 第一个是被代理的对象ClassLoader ,第二个参数是 被代理的对象实现的接口,第三个参数是 代理对象
FixBug fixBug = (FixBug) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), boss);
// 被代理的对象执行方法
fixBug.fixBug();
}
}
输出:
worker开始修复问题了
worker 正在修复 问题 ....
worker修复问题完成了
动态代理的特点是在编译过程中 可以动态生成被代理的对象。
3.总结整理动态代理
总结下jdk动态代理的特点。
被代理对象需要有接口实现。
比如:worker实现了FixBug接口。动态代理需要实现InvocationHandler接口,并可以获取到被代理的对象。
比如:Boss实现了InvocationHandler接口,并在初始化时候传入了要被代理的对象worker。通过Proxy.newProxyInstance获取到被代理的对象后,通过被代理的对象执行方法 会被代理对象拦截到。
比如:
FixBug fixBug = (FixBug) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), boss);
fixBug.fixBug();
输出:
worker开始修复问题了
worker 正在修复 问题 ....
worker修复问题完成了
当被添加了代理的对象 调用方法的时候,就会被Boss这个动态代理获拦截到方法的执行。
4.动态代理使用过程总结
想要添加动态代理的对象需至少要实现一个接口。
动态代理本身需要实现InvocationHandler,并可以接收到要被代理的对象,用于发起方法请求。
如果要使动态代理生效,需要使用Proxy.newProxyInstance方法来获取被添加代理的对象,并使用这个对象来发起方法调用。
5.封装动态代理的使用过程
- 第一步: 需要添加动态代理的对象 可以使用Object来代表。
- 第二步: 动态代理对象的封装
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("进入方法");
Object invoke = method.invoke(target, args);
System.out.println("即将结束方法");
return invoke;
}
}
- 第三步:封装获取添加代理对象的方法
public static T getProxyObject(T obj) throws Exception {
Class>[] interfaces1 = obj.getClass().getInterfaces();
if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的对象要实现与其对应的接口");
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();
}
输出结果:
进入方法
worker 正在修复 问题 ....
即将结束方法
通过方法的封装。现在可以快速实现动态代理。唯一的问题就是动态代理拦截到方法之后,做的操作太单一。
目前的处理,拦截到调用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("进入方法");
Object invoke = method.invoke(target, args);
System.out.println("即将结束方法");
return invoke;
}
6.如何丰富拦截到方法之后的处理?
如果想要去丰富一类操作的处理。一定是通过抽象出这类操作的共同点,定义为接口,由接口的实现类去做具体的实现。在使用过程中通过选择不同的实现类来实现对于操作的灵活处理。
在我们现在的例子中,可以抽离出三个方法。
1.进入方法时
2.退出方法前
3.结果处理
抽离出接口则是
public interface MethodAroundHandler {
// 进入接口
void enterMethod();
// 退出方法前
void beforReturnMethod();
// 结果处理
T dealResult(T obj);
}
做一个简单的实现类
public class SimpleMethodAround implements MethodAroundHandler {
@Override
public void enterMethod() {
System.out.println("进入方法");
}
@Override
public void beforReturnMethod() {
System.out.println("方法退出前");
}
@Override
public T dealResult(T obj) {
System.out.println("拦截到方法的返回值:"+obj);
return obj;
}
}
修改之前的DynamicProxy 动态代理
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 getProxyObject(T obj) throws Exception {
Class>[] interfaces1 = obj.getClass().getInterfaces();
if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的对象要实现与其对应的接口");
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();
}
}
输出结果:
进入方法
worker 正在修复 问题 ....
方法退出前
总结这次的处理。
把原来写在DynamicProxy中的方法拦击成功的转移并抽象成了接口,这样可以很方便的根据不同的情况来切换接口实现类。
下面应该考虑的是如何去动态的切换接口实现类。
7.通过注解的方式动态切换接口实现类
现在需要动态切换接口实现类的地方是在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 {
// 初始化接口实现类
SimpleMethodAround simpleMethodAround = new SimpleMethodAround();
// 调用接口方法
simpleMethodAround.enterMethod();
// 调用原方法
Object invoke = method.invoke(target, args);
// 调用接口方法
simpleMethodAround.beforReturnMethod();
// 如果欧返回值 可以调用最后一个接口放方法
if (invoke != null) simpleMethodAround.dealResult(invoke);
// 最后返回结果
return invoke;
}
}
在invoke 可以拿到要调用的方法Method,这样就可以使用方法注解来指定使用哪个接口实现类,进而可以根据不同的注解来实例化不同的接口实现类。
这里修改invoke方法实现。主要是修改接口实现类的实例化过程。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果是被注解标注的方法 才会被拦截
if (method.isAnnotationPresent(DynamicProxyMethod.class)) {
// 获取到注解
DynamicProxyMethod annotation = method.getAnnotation(DynamicProxyMethod.class);
// 获取到接口实现类
MethodAroundHandler methodAroundHandler = (MethodAroundHandler) annotation.type().newInstance();
// 调用接口方法
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 // 默认是SimpleMethodAround.class
void fixBug();
}
还是原来的Main方法
public class Main {
public static T getProxyObject(T obj) throws Exception {
Class>[] interfaces1 = obj.getClass().getInterfaces();
if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的对象要实现与其对应的接口");
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();
}
}
输出:
进入方法
worker 正在修复 问题 ....
方法退出前
到此 基本实现了对于方法的拦截 实现了方法进入时 返回前 以及 对结果的处理
8.整理代码
注解定义
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DynamicProxyMethod {
Class type() default SimpleMethodAround.class;
}
方法拦截接口定义
public interface MethodAroundHandler {
void enterMethod();
void beforReturnMethod();
T dealResult(T obj);
}
两个MethodAroundHandler接口实现
public class SimpleMethodAround implements MethodAroundHandler {
@Override
public void enterMethod() {
System.out.println("进入方法");
}
@Override
public void beforReturnMethod() {
System.out.println("方法退出前");
}
@Override
public T dealResult(T obj) {
System.out.println("拦截到方法的返回值:"+obj);
return obj;
}
}
public class SimpleMethodAround2 implements MethodAroundHandler {
@Override
public void enterMethod() {
System.out.println("222 进入方法");
}
@Override
public void beforReturnMethod() {
System.out.println("222 方法退出前");
}
@Override
public T dealResult(T obj) {
System.out.println("222 拦截到方法的返回值:"+obj);
return obj;
}
}
要拦截的对象要实现一个接口
public interface FixBug {
@DynamicProxyMethod // 注解标注要使用哪个MethodAroundHandler接口实现类
void fixBug();
@DynamicProxyMethod(type = SimpleMethodAround2.class) // 注解标注要使用哪个MethodAroundHandler接口实现类
String coding();
}
要被动态代理的对象
public class Worker implements FixBug {
@Override
public void fixBug() {
System.out.println("worker 正在修复 问题 ....");
}
@Override
public String coding() {
return "worker 正在coding .....";
}
}
动态代理实现
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 {
// 如果是被注解标注的方法 才会被拦截
if (method.isAnnotationPresent(DynamicProxyMethod.class)) {
// 获取到注解
DynamicProxyMethod annotation = method.getAnnotation(DynamicProxyMethod.class);
// 获取到接口实现类
MethodAroundHandler methodAroundHandler = (MethodAroundHandler) annotation.type().newInstance();
// 调用接口方法
methodAroundHandler.enterMethod();
Object invoke = method.invoke(target, args);
methodAroundHandler.beforReturnMethod();
if (invoke != null) methodAroundHandler.dealResult(invoke);
return invoke;
}
return method.invoke(target, args);
}
}
Main函数
public class Main {
public static T getProxyObject(T obj) throws Exception {
Class>[] interfaces1 = obj.getClass().getInterfaces();
if (interfaces1 == null || interfaces1.length == 0) throw new Exception("要求代理的对象要实现与其对应的接口");
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();
}
}
最终输出:
进入方法
worker 正在修复 问题 ....
方法退出前
222 进入方法
222 方法退出前
222 拦截到方法的返回值:worker 正在coding .....
9. 最后MARK下
这个思路的实现,我封装了springboot-starter-dynamic-aop。
与文章中所讲的思路略微有些不同。
主要是利用了spring的Bean容器特性。来帮助创建动态代理。
githup地址: https://github.com/LH-0811/lh-springboot-starter-dynamic-aop
10.最后的最后
写了很多,希望大家能提出意见。指出我在思想上的不足。期待共同进步!!谢谢大家。