【设计模式】代理模式之JDK动态代理(案例详解)

简介: 案例讲解代理模式之JDK动态代理!
【辰兮要努力】:hello你好我是辰兮,很高兴你能来阅读,昵称是希望自己能不断精进,向着优秀程序员前行!

博客来源于项目以及编程中遇到的问题总结,偶尔会有读书分享,我会陆续更新Java前端、后台、数据库、项目案例等相关知识点总结,感谢你的阅读和关注,希望我的博客能帮助到更多的人,分享获取新知,大家一起进步!

吾等采石之人,应怀大教堂之心,愿大家奔赴在各自的热爱里…

一、初识代理模式

关于jdk的动态代理和cglib的动态代理,我会通过两篇文章案例,和大家一起学习

首先我们一起了解一下什么是代理模式

代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理访问目标对象。这样好处:可以在目标对象实现的基础上,增强额外的功能操作。(扩展目标对象的功能)。

在这里插入图片描述

CGLIB相关学习参考:【设计模式】代理模式之CGLIB动态代理(案例详解)


本期重点还是一起学习JDK动态代理

JDK动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。

JDK动态代理
1、实现InvocationHandler 
2、使用Proxy.newProxyInstance产生代理对象
3、被代理的对象必须要实现接口

JDK动态代理和CGLIB动态代理的区别?

1、JDK动态代理只能对实现了接口的类生成代理,而不能针对类。

2、CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法, 并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。

请添加图片描述

Spring如何选择用JDK还是CGLiB?

1、当Bean实现接口时,Spring就会用JDK的动态代理。

2、当Bean没有实现接口时,Spring使用CGlib是实现。

3、可以强制使用CGlib(在配置文件中配置)

在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>

二、代码案例学习

【模拟代码】JDK动态代理-相关实现代码

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

/**
 * JDK动态代理具体实现原理:
 *
 * 1、通过实现InvocationHandler接口创建自己的调用处理器;
 * 2、通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
 * 3、通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
 * 4、通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;
 */
public class JDKProxy implements InvocationHandler {

    private Object targetObject;

    /**
     * 将真实对象和代理对象建立联系,通过真实对象来返回一个代理对象(猜测该代理对象可能为真实对象的子类)
     * @param targetObject 真实对象
     * @return 代理对象
     */
    public Object newProxy(Object targetObject){
        this.targetObject = targetObject;

        /**
         * 参数1:类加载器,采用target本身的类加载器
         * 参数2:生成的动态代理对象挂在那个接口下,采用target实现的接口下,即Woker接口。
         * 参数3:定义实现方法逻辑的代理类,this表示当前对象,,它必须实现InvocationHandler的invoke方法。
         */
        return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);
    }

    /**
     * 代理方法逻辑
     * @param proxy 代理对象
     * @param method 当前调度方法
     * @param args 当前方法参数
     * @return  代理结果返回
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        handle();
        System.out.println("拦截方法:" + method);
        System.out.println("方法参数:" + Arrays.toString(args));
        Object obj = method.invoke(targetObject,args);
        return obj;
    }


    private void handle(){
        //处理一些业务逻辑
        System.out.println("JDKProxy处理业务逻辑");
    }
}

创建一个user对象

@Data
public class User {

    private String id;
    private String name;
    private Integer age;

}

创建一个接口

public interface UserManager {
    public void addUser(String id, String userName);
}

创建如上接口的实现类

public class UserManagerImpl implements UserManager {

    @Override
    public void addUser(String id, String password) {
        System.out.println("调用了UserManagerImpl.addUser()方法!"+id+"-"+password);
    }

}

创建一个测试类

/**
 * @program: demo
 * @description: ProxyTest 测试类
 * @author: 辰兮要努力
 * @create: 2021-10-25 21:08
 */
public class ProxyTest {

    public static void main(String[] args) {
        JDKProxy jdkProxy = new JDKProxy();
        UserManager userManagerJDK = (UserManager)jdkProxy.newProxy(new UserManagerImpl());
        userManagerJDK.addUser("1", "chenXI");
    }
}

UserManager userManagerJDK = (UserManager)jdkProxy.newProxy(new UserManagerImpl());

如上的代码逻辑我可以理解为一个帮助类,帮助我们获取了一个代理对象

userManagerJDK.addUser("1", "chenXI");

我们在通过代理对象调用我们对象本身的方法


执行如上程序,输出结果

JDKProxy处理业务逻辑
拦截方法:public abstract void com.chenxi.demo.dao.UserManager.addUser(java.lang.String,java.lang.String)
方法参数:[1, chenXI]
调用了UserManagerImpl.addUser()方法!1-chenXI

Process finished with exit code 0

ps:我把代码都粘贴下来是希望如果你看不太懂可以一起本地实践


三、断点深入探究

我们一起debug执行一下对应的流程

1、首先是一个帮助类传入我们需要的对象后帮助我们生成一个代理对象

2、代理对象执行原对象的方法(功能)

在这里插入图片描述

真正执行方法的代码 Object obj = method.invoke(targetObject,args);

在这里插入图片描述

同理我们可以在此方法 Object obj = method.invoke(targetObject,args);前后新增方法可以增强原方法


JDK动态代理是java.lang.reflect.*包所提供的方式,它所代理的真实对象必须实现一个接口,依据该接口才能生成真实对象的代理

在这里插入图片描述

更详细的学习可以学习一下对应生成代理对象的源码newProxyInstance

 public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        //如果h为空将抛出异常
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();//拷贝被代理类实现的一些接口,用于后面权限方面的一些检查
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            //在这里对某些安全权限进行检查,确保我们有权限对预期的被代理类进行代理
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * 下面这个方法将产生代理类
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * 使用指定的调用处理程序获取代理类的构造函数对象
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            //假如代理类的构造函数是private的,就使用反射来set accessible
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            //根据代理类的构造函数来生成代理类的对象并返回
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

小结

AOP面向切面编程,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。

思考:有了动态代理,如果我们想在原有方法前后新增逻辑,如权限,日志等,我们就可以通过如上方式实现。


四、拓展面试相关

如何强制使用CGLIB实现AOP?

1、添加CGLIB库

(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)

2、在Spring配置文件中加入

<aop:aspectj-autoproxy proxy-target-class="true"/>

那到底何时使用JDK何时使用CGLIB?

1、如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。

2、如果目标对象实现了接口,可以强制使用CGLIB实现AOP。

3、如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

请添加图片描述


非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤️ 分享👥 留言💬thanks!!!

愿你们奔赴在自己的热爱里!

目录
相关文章
|
6天前
|
Java API 开发者
Jdk动态代理为啥不能代理Class?
该文章主要介绍了JDK动态代理的原理以及为何JDK动态代理不能代理Class。
Jdk动态代理为啥不能代理Class?
|
4天前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
1天前
|
设计模式 缓存 Java
【十一】设计模式~~~结构型模式~~~代理模式(Java)
文章详细介绍了代理模式(Proxy Pattern),这是一种对象结构型模式,用于给对象提供一个代理以控制对它的访问。文中阐述了代理模式的动机、定义、结构、优点、缺点和适用环境,并探讨了远程代理、虚拟代理、保护代理等不同代理形式。通过一个商务信息查询系统的实例,展示了如何使用代理模式来增加身份验证和日志记录功能,同时保持客户端代码的无差别对待。此外,还讨论了代理模式在分布式技术和Spring AOP中的应用,以及动态代理的概念。
【十一】设计模式~~~结构型模式~~~代理模式(Java)
|
1天前
|
设计模式 算法 安全
jdk8中都使用了哪些设计模式?
【8月更文挑战第18天】jdk8中都使用了哪些设计模式?
7 0
|
5天前
|
设计模式 算法 Java
面试官:JDK中都用了哪些设计模式?
面试官:JDK中都用了哪些设计模式?
16 0
|
5天前
|
设计模式 缓存 监控
设计模式之代理模式
代理模式(Proxy Pattern)是软件工程中的一种设计模式,它属于结构型模式,用于在不直接访问实际对象的情况下,通过一个或多个代理对象来间接访问某个对象或执行某些操作。
|
5天前
|
设计模式
设计模式的基础问题之代理模式在工作中的问题如何解决
设计模式的基础问题之代理模式在工作中的问题如何解决
|
30天前
|
设计模式 算法 Go
iLogtail设计模式问题之代理模式在iLogtail中是如何应用的
iLogtail设计模式问题之代理模式在iLogtail中是如何应用的
|
1天前
|
设计模式
设计模式-单一职责模式
设计模式-单一职责模式
|
1天前
|
设计模式 XML 存储
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)
文章详细介绍了工厂方法模式(Factory Method Pattern),这是一种创建型设计模式,用于将对象的创建过程委托给多个工厂子类中的某一个,以实现对象创建的封装和扩展性。文章通过日志记录器的实例,展示了工厂方法模式的结构、角色、时序图、代码实现、优点、缺点以及适用环境,并探讨了如何通过配置文件和Java反射机制实现工厂的动态创建。
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)