Java代理设计模式

2023年 9月 25日 80.5k 0

代理设计模式

  • 通过对目标对象(target)进行封装, 对程序进行加强处理。例如:获得目标对象方法的运行总时长、Spring的面向切面编程。
  • 实现原理:创建一个代理类(proxy),并持有一个目标对象,代理类不会自己实现真正服务,通过调用目标对象的相关方法来提供服务。类似于现实中介。
  • 通过一个代理类间接访问目标对象。
  • 20181228090858878.png

    静态代理

    直接编写一个代理类,把源码直接编写好,编译后生成一个.class文件

    简单实现

  • 创建一个Person接口
  • public interface Person{
        // 租房
        public void rentRoom();
    }
    
  • 创建租客类
  • public class Renter implements Person{
        @Override
        public void rentRoom(){
    		租房子
        }
    }
    
  • 创建代理类,即租客通过中间找房子
  • public class RenterProxy implements Person{
    	private Renter renter;
        @Override
        public void rentRoom(){
            中介找房子,转租给租客....
            租房子
            中介给租客钥匙,租客入住...
        }
    }
    

    租客想租房子,但是自己没有实力,就通过找一个代理(中介)来替他找房子,并租下。实际上来说最终还是租客租房子,只不过通过其他人来代替自己完成该任务,自己领包入住即可。

    弊端

    通过编写源码来实现代理模式,1000个类就会有一千个代理类,会产生类爆炸。

    动态代理

    在程序运行阶段,在内存中动态生成代理类,被称为动态代理,目的是为了减少代理类的数量。解决代码复用的问题

    • JDK动态代理技术:只能代理接口。
    • CGLIB动态代理技术:CGLIB(Code Generation Library)是一个开源项目。是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。它既可以代理接口,又可以代理类,底层是通过继承的方式实现的。性能比JDK动态代理要好。(底层有一个小而快的字节码处理框架ASM。)
    • Javassist动态代理技术:Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架

    JDK动态代理

    • 只能代理接口。

    • 在程序运行过程中创建代理类。

    • 在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象。

    代码实现

  • 创建一个Person接口,提供方法
  • /**
     * 被实现的接口
     * @author hly
     * @version 1.0
     */
    public interface Person {
        /**
         * 相亲方法
         *
         * */
         void blindDate();
    }
    
  • 创建Person的Impl实现类,并重写方法。
  • /**
     * 一位男士要去相亲
     * @author hly
     * @version 1.0
     */
    public class Man implements Person {
        /**
         * 相亲
         */
        @Override
        public void blindDate() {
            System.out.println("开始相亲.....");
        }
    }
    
  • 创建InvocationHandle实现类
    • 代理对象的所有方法调用都会被转发到 InvocationHandler 接口的 invoke() 方法中
  • /**
     * @author hly
     * @version 1.0
     */
    public class MyInvocationHandler implements InvocationHandler {
        // 持有目标对象(被代理的对象)
        private Object target;
        // 有参构造器,给target赋值
        public MyInvocationHandler(Object target){
            this.target = target;
        }
    
        /**
         * 代理类底层执行的方法。即当代理类调用目标方法时,jvm底层转发到InvocationHandle接口实现类的invoke方法
         * 再通过该方法,利用反射机制调用被代理对象的目标方法。
         * 当代理类调用该方法时就可以做一些增强操作。
         * 即代理对象就是媒婆,帮你约好人,相亲还是你自己去
         * @param proxy 代理对象,后期需要用时,直接用
         * @param method 被代理对象(目标对象)的目标方法
         * @param args 目标方法的参数列表
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            // 调用目标对象方法前的操作
            System.out.println("媒婆帮你找了一个相亲对象");
            // 将反射机制调用目标对象的目标方法(相亲方法)
            Object result = method.invoke(target, args);
            // 调用目标对象方法后的操作
            return result;
        }
    }
    
  • 通过java.lang.reflect.Proxy创建代理对象
  • /**
     * @author hly
     * @version 1.0
     */
    public class TestPerson {
        @Test
        public void test(){
            // 创建目标对象
            Person target = new Man();
            /*
             * 男人条件有点差,没能约到相亲对象
             * 他找了个媒婆帮他找到他的相亲对象
             * 媒婆就是那个所谓的代理类
             */
            /*
                Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),调用处理器对象)
                第一个参数:目标对象的类加载器,
                第二个参数:目标对象接口类型。代理类和目标类实现相同的接口,所以要通过这个参数告诉JDK动态代理生成的类要实现哪些接口
                第三个参数:调用处理器。这是一个JDK动态代理规定的接口,接口全名:java.lang.reflect.InvocationHandler
             */
            //强转接口,而不是实现对象。
            // 运行类型为class jdk.proxy2.$Proxy7
            Person manProxy = ((Person) Proxy.newProxyInstance((target.getClass().getClassLoader()), target.getClass().getInterfaces(), new MyInvocationHandler(target)));
            manProxy.blindDate();
        }
    }
    
  • 运行效果
  • image-20230919171558908.png

    • 注意:当调用其他方法时,也有可能会调用invoke方法。
      • .getClass() 方法是获取一个对象的运行时类信息,它不会触发 invoke() 方法的执行。
      • 调用代理对象的其他方法:当你调用代理对象的非目标方法时,例如 toString(), hashCode(), equals() 等方法,都会触发 invoke() 方法的执行。
      • Object 类中的方法:如果代理的目标实现了 Object 类中的方法,如 wait(), notify(), notifyAll() 等,调用这些方法也会触发 invoke() 方法的执行。
      • 默认方法(Default Method):如果代理目标实现了接口中的默认方法,并且你调用了这些默认方法,也会触发 invoke() 方法的执行。

    CGLIB代理

    • 如果被代理类没有实现接口,那么这么实现动态代理?这时候就需要用到CGLIB了。这种代理方式就叫做CGLIB代理。

    • CGLIB代理也叫作子类代理,他是通过在内存中构建一个子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,然后加入自己需要的操作。因为使用的是继承的方式,所以不能代理final 类

      代码实现

    • 先引入cglib依赖
    • 
        cglib
        cglib
        3.3.0
      
      
    • 准备一个没有实现类的接口
    
    /**
     * 没有实现接口的类
     * @author hly
     * @version 1.0
     */
    public class UserService {
        public void login(){
            System.out.println("用户正在登录系统....");
        }
    
        public void logout(){
            System.out.println("用户正在退出系统....");
        }
    }
    
  • 使用CGLIB在内存中为UserService类生成代理类,并创建对象
  •  public  void test01(){
            // 创建字节码增加器
            Enhancer enhancer = new Enhancer();
            // 告诉cglib要继承哪个类
            enhancer.setSuperclass(UserService.class);
            // 设置回调接口
            //和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,
            // 而是:net.sf.cglib.proxy.MethodInterceptor
            enhancer.setCallback(方法拦截器对象);
            // 生成源码,编译class,加载到JVM,并创建代理对象
            UserService userService = (UserService) enhancer.create();
            userService.login();
            userService.logout();
        }
    
  • 编写MethodInterceptor即方法拦截器对象,与JDK动态代理中的InvocationHandler有异曲同工之妙。
  • /**
     * @author hly
     * @version 1.0
     */
    public class MyInvocationHandler implements InvocationHandler {
        private Object target;
        public MyInvocationHandler(Object target){
            this.target =target;
        }
    
        /**
         * 当代理对象调用方法时,底层会走这个方法
         * @param proxy 代理对象,方便后期使用
         * @param method 被代理对象的方法
         * @param args 方法参数
         * @return
         * @throws Throwable
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("代理对象调用方法.......");
            //反射机制调用方法
            System.out.println("底层通过反射调用被代理对象的method:"+method);
            Object result = method.invoke(target, args);
            return result;
        }
    }
    
  • 调用
  •     @Test
        public  void test01(){
            // 创建字节码增加器
            Enhancer enhancer = new Enhancer();
            // 告诉cglib要继承哪个类
            enhancer.setSuperclass(UserService.class);
            // 设置回调接口
            //和JDK动态代理原理差不多,在CGLIB中需要提供的不是InvocationHandler,
            // 而是:net.sf.cglib.proxy.MethodInterceptor
            enhancer.setCallback(new MyMethodInterceptor());
            // 生成源码,编译class,加载到JVM,并创建代理对象
            UserService userService = (UserService) enhancer.create();
            userService.login();
            userService.logout();
        }
    

    结果

    image-20230919211702752.png

    JDK动态代理与CGLIB的区别

    • 两者都可以实现动态代理机制,在运行过程中生成代理类写入程序。
    • JDK动态代理只能代理接口类,底层通过java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现
    • CGLIB既可以代理类也可以代理接口,大部分情况下代理类,通过继承被代理类实现其子类,来完成代理模式。底层通过net.sf.cglib.proxy.Enhancer类,和net.sf.cglib.proxy.MethodInterceptor;接口实现
    • 在不破坏代理类源码的情况下,两者都起到了增加作用,遵循oop原则。解决静态代理带来的类爆炸问题
    • 当代理类调用目标方法时,两者底层都转发到各自特定的一个接口实现类通过反射机制,-->method.invoke方法实现,但具体实现又不一样。一个是被代理方法,另一个是代理方法
    //JDK动态代理
    InvocationHandle
        invoke()
        	// 被代理方法
        	method.invoke(target,args);
    // CGLIB
    MethodInterceptor
        intercept()
        	// 代理方法
        	methodProxy.invokeSuper(o,objects);
    

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论