手搓代码-从Java动态代理到AOP

关键字 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动态代理的特点。

  1. 被代理对象需要有接口实现。
    比如:worker实现了FixBug接口。

  2. 动态代理需要实现InvocationHandler接口,并可以获取到被代理的对象。
    比如:Boss实现了InvocationHandler接口,并在初始化时候传入了要被代理的对象worker。

  3. 通过Proxy.newProxyInstance获取到被代理的对象后,通过被代理的对象执行方法 会被代理对象拦截到。
    比如:

FixBug fixBug = (FixBug) Proxy.newProxyInstance(worker.getClass().getClassLoader(), worker.getClass().getInterfaces(), boss);
fixBug.fixBug();
输出:
worker开始修复问题了
worker 正在修复 问题 ....
worker修复问题完成了

当被添加了代理的对象 调用方法的时候,就会被Boss这个动态代理获拦截到方法的执行。

4.动态代理使用过程总结

  1. 想要添加动态代理的对象需至少要实现一个接口。

  2. 动态代理本身需要实现InvocationHandler,并可以接收到要被代理的对象,用于发起方法请求。

  3. 如果要使动态代理生效,需要使用Proxy.newProxyInstance方法来获取被添加代理的对象,并使用这个对象来发起方法调用。

5.封装动态代理的使用过程

  1. 第一步: 需要添加动态代理的对象 可以使用Object来代表。
  2. 第二步: 动态代理对象的封装
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;
    }
}
  1. 第三步:封装获取添加代理对象的方法
   public static <T> 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);
    }
  1. 第四步:使用过程
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>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> 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> 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> 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>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> 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> 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> 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.最后的最后

写了很多,希望大家能提出意见。指出我在思想上的不足。期待共同进步!!谢谢大家。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容