• 设计模式1


    代理模式: 为其他对象提供一种代理以控制对这个对象的访问。

    1.为什么使用代理模式

    中介隔离:在某些情况下,一个客户类不想或者不能直接引用一个委托对象,而代理类对象可以在客户类和委托对象之间起到中介的作用,其特征是代理类和委托类实现相同的接口。

    开闭原则,增加功能代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则。代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后对返回结果的处理等。代理类本身并不真正实现服务,而是同过调用委托类的相关方法,来提供特定的服务。真正的业务功能还是由委托类来实现,但是可以在业务功能执行的前后加入一些公共的服务。例如我们想给项目加入缓存、日志这些功能,我们就可以使用代理类来完成,而没必要打开已经封装好的委托类。

    2.代理模式实现原理

    代理模式主要包含三个角色,即抽象主题角色(Subject)、委托类角色(被代理角色,Proxied)以及代理类角色(Proxy)

    抽象主题角色:可以是接口,也可以是抽象类;

    委托类角色:真实主题角色,业务逻辑的具体执行者;

    代理类角色:内部含有对真实对象RealSubject的引用,负责对真实主题角色的调用,并在真实主题角色处理前后做预处理和后处理。

    3.代理模式应用场景

    1. 自定义aop、事务aop
    2. 日志收集
    3. 权限控制
    4. 过滤器
    5. 全局异常捕获
    6. 自定义注解的实现 (apo+ 反射技术)
    7. Mybatis的Mapper接口,JPA的Respoitory接口
    8. 分布式事务lcn,seata,分库分表中间件shardingSphere 代理数据源(拦截劫持数据源实现功能增强)
    9. Rpc远程调用中间件,也是走代理

    4.代理模式创建的方式

    静态代理和动态代理

    4.1 静态代理

    静态代理是由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。一句话,自己手写代理类就是静态代理。

    基于接口实现方式

    public interface MessageService {
        void sendMessage();
    }
    
    public class MessageServiceImpl implements MessageService {
        public void sendMessage() {
            System.out.println("发送消息..");
        }
    }
    
    public class MessageServiceProxy implements MessageService {
        /**
         * 被代理对象
         */
        private MessageService proxiedMessageService;
    
        public MessageServiceProxy(MessageService messageService) {
          this.proxiedMessageService=messageService;
        }
    
        public void sendMessage() {
            System.out.println("日志收集开始..");
            proxiedMessageService.sendMessage();
            System.out.println("日志收集结束..");
        }
    }
    
    public class ClientTest {
        public static void main(String[] args) {
            MessageService messageService = new MessageServiceProxy(new MessageServiceImpl());
            messageService.sendMessage();
        }
    }

    基于继承方式实现 

    public class MessageServiceProxy extends MessageServiceImpl {
    
        public void sendMessage() {
            System.out.println("日志收集开始..");
            super.sendMessage();
            System.out.println("日志收集结束..");
        }
    }
    
    public class ClientTest {
        public static void main(String[] args) {
            new OrderServiceProxy().sendMessage();
        }
    }

    静态代理其实很好理解,就是要我们手动的去实现代理类,下面的动态代理才是我要讲的重点

    4.2 动态代理 

    动态代理是在实现阶段不用关心代理类,而在运行阶段才指定哪一个对象。动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成 。

    4.2.0 讲JDK动态代理前先讲讲反射技术

    java 反射技术:可以动态根据class文件获取类的信息(类的名称,属性,方法),帮助实例化对象,动态的调用某个方法,比如某个方法是私有,都可以通过反射技术实现调用.

    反射的缺点
    性能问题

    1.使用反射基本上是一种解释操作,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求很高的系统框架上,普通程序不建议使用。

    2.反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。

    使用反射会模糊程序内部逻辑

    程序人员希望在源代码中看到程序的逻辑,反射等绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂。

    安全限制

    使用反射技术要求程序必须在一个没有安全限制的环境中运行。如果一个程序必须在有安全限制的环境中运行,如Applet,那么这就是个问题了

    内部暴露

    由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

    4.2.1 JDK动态代理

    JDK动态代理的一般步骤如下:

    1.创建被代理的接口和类;

    2.实现InvocationHandler接口,对目标接口中声明的所有方法进行统一处理;

    3.调用Proxy的静态方法,创建代理类并生成相应的代理对象;

    public interface OrderService {
        void order();
    }
    
    public class OrderServiceImpl implements OrderService {
        public void order() {
            System.out.println("订单操作.....");
        }
    }
    
    public class JdkInvocationHandler implements InvocationHandler {
        /**
         * 目标对象 -- 即被代理对象
         */
        public Object target;
    
        public JdkInvocationHandler(Object target) {
            this.target = target;
        }
    
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println(">>>日志收集开始>>>>");
            // 执行代理对象方法
            Object reuslt = method.invoke(target, args);
            System.out.println(">>>日志收集结束>>>>");
            return reuslt;
        }
    
        /**
         * 获取代理对象接口
         *
         * @param <T>
         * @return
         */
        public <T> T getProxy() {
            return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
        }
    }
    

    // System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 可以获取动态代理生成的class文件 JdkInvocationHandler jdkInvocationHandler
    = new JdkInvocationHandler(new OrderServiceImpl()); OrderService proxy = jdkInvocationHandler.getProxy(); proxy.order();

    注意:继承了Proxy类,实现了代理的接口,由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理。

    JDK动态代理原理分析

    1.在使用jdk动态代理的时候,必须要实现InvocationHandler接口,invoke方法 

    invoke(Object proxy, Method method, Object[] args) , Invoke 方法中该三个参数分别表示为: 生成的代理对象、被代理执行的方法、参数

     通过设置System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true"); 获取动态代理的生成的class文件

    使用jdk动态代理获取代理类对象(JDK自动生成代理类) $Proxy0.class 如下(后面的0表示序号,如果有多个代理类这个序号会

     

    从反编译的代码可以看出代理类$Proxy0继承的Proxy类并实现了OrderService接口,order()方法,实际走的InvocationHandler.invoke()方法,m3就是接口的方法,反编译代码如下

     现在我们来debug下代码,走到$Proxy的order方法,看下 this, m3,h的debug信息

     

     

    纯手写动态代理

    1. 创建代理类$Proxy0源代码文件实现被代理的接口。

           public final class $Proxy0 extends Proxy implements OrderService { }

    1.  使用JavaCompiler技术编译该$Proxy0文件获取到$Proxy0.class
    2.  使用ClassLoader将该$Proxy0.class加入到当前JVM内存中

    ClassLoader 是类加载器,ClassLoader 作用:负责将 Class 加载到 JVM 中,审查每个类由谁加载(父优先的等级加载机制)将 Class 字节码重新解析成 JVM 统一要求的对象格式

    手写模拟Jdk动态代理

    package com.brian.proxy.service;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public interface BrianInvocationhandler {
        /**
         * 模拟jdk动态代理 InvocationHandler接口
    *
    @param proxy 代理对象 * @param method 被代理类执行的方法 * @param args 参数 * @return */ Object invoke(Object proxy, Method method,Object[] args) throws InvocationTargetException, IllegalAccessException; }
    /**
     * @program: architect
     * @author: Brian Huang
     * @create: 2019-05-20 20
     **/
    @Slf4j
    public class BrianJdkInvocationhandler implements BrianInvocationhandler {
    
        private Object target;
    
        public BrianJdkInvocationhandler(Object target) {
            this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
            log.info("方法执行前,,,");
            Object result = method.invoke(target, args);//使用Java反射技术执行
            log.info("方法执行后,,,");
            return result;
        }
    }
    package com.brian.proxy.service.impl;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    /**
     * java类加载器  将class字节码文件加载到内存中
     * @program: architect
     * @author: Brian Huang
     * @create: 2019-05-20 22
     **/
    public class JavaClassLoader extends ClassLoader {
    
        private File classPathFile;
    
        public JavaClassLoader(){
            String classPath=JavaClassLoader.class.getResource("").getPath();
            this.classPathFile=new File(classPath);
        }
    
        @Override
        public Class<?> findClass(String name) throws ClassNotFoundException {
            String className= JavaClassLoader.class.getPackage().getName()+"."+name;
            if(classPathFile!=null){
                File classFile=new File(classPathFile,name.replaceAll("\.","/")+".class");
                if(classFile.exists()){
                    FileInputStream in=null;
                    ByteArrayOutputStream out=null;
                    try {
                        in=new FileInputStream(classFile);
                        out=new ByteArrayOutputStream();
                        byte[] buff=new byte[1024];
                        int len;
                        while ((len=in.read(buff))!=-1){
                            out.write(buff,0,len);
                        }
                        return defineClass(className,out.toByteArray(),0,out.size());
                    }catch (Exception e){
                        e.printStackTrace();
                    }finally {
                        if(in!=null){
                            try {
                                in.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        if(out!=null){
                            try {
                                out.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
            return null;
        }
    
    }
    package com.brian.proxy.proxy;
    
    import com.brian.proxy.service.OrderService;
    import com.brian.proxy.service.impl.BrianJdkInvocationhandler;
    import com.brian.proxy.service.impl.JavaClassLoader;
    import com.brian.proxy.service.impl.OrderServiceImpl;
    
    import javax.tools.JavaCompiler;
    import javax.tools.StandardJavaFileManager;
    import javax.tools.ToolProvider;
    import java.io.File;
    import java.io.FileWriter;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    /**
     * @program: architect
     * @author: Brian Huang
     * @create: 2019-05-20 22
     **/
    public class MyProxy {
    
        static String rt = "
    	";
    
        /**
         * @param classInfo 被代理实现的接口信息 <br>
         * @param h
         * @return
         */
        public static Object newProxyInstance(JavaClassLoader javaClassLoader, Class classInfo, BrianJdkInvocationhandler h) {
            try {
                // 1.创建代理类java源码文件,写入到硬盘中..
                Method[] methods = classInfo.getMethods();
                String proxyClass = "package com.brian.proxy.service.impl;" + rt
                        + "import java.lang.reflect.Method;" + rt
                        + "import com.brian.proxy.service.impl.BrianJdkInvocationhandler;" + rt
                        + "import java.lang.reflect.InvocationTargetException;" + rt
                        + "public class $Proxy2 implements " + classInfo.getName() + "{" + rt
                        + "BrianJdkInvocationhandler h;" + rt
                        + "public $Proxy2(BrianJdkInvocationhandler h)" + "{" + rt
                        + "this.h= h;" + rt + "}"
                        + getMethodString(methods, classInfo) + rt + "}";
                // 2. 将代理类源码文件写入硬盘中
                //String filename = "C:\Users\LiangHuang\Desktopd\$Proxy2.java";
                String filename = JavaClassLoader.class.getResource("").getPath()+"$Proxy2.java";
                File f = new File(filename);
                FileWriter fw = new FileWriter(f);
                fw.write(proxyClass);
                fw.flush();
                fw.close();
    
                // 3.使用JavaCompiler 编译该$Proxy2源代码 获取class文件
                JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
                StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null);
                Iterable units = fileMgr.getJavaFileObjects(filename);
                JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units);
                t.call();
                fileMgr.close();
    
                //4.使用classClassLoader 将$Proxy2.class读取到内存中...
                Class proxy2Class = javaClassLoader.findClass("$Proxy2");
                //5.使用java反射机制给函数中赋值
                Constructor m = proxy2Class.getConstructor(BrianJdkInvocationhandler.class);
                Object o = m.newInstance(h);
                return o;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
        public static String getMethodString(Method[] methods, Class intf) {
            String proxyMe = "";
            for (Method method : methods) {
                proxyMe += "public void " + method.getName() + "() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {" + rt
                        + "Method md= " + intf.getName() + ".class.getMethod("" + method.getName()
                        + "",new Class[]{});" + rt
                        + "this.h.invoke(this,md,null);" + rt + "}" + rt;
    
            }
            return proxyMe;
        }
    
        public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
            OrderService o =
                    (OrderService) MyProxy.newProxyInstance(new JavaClassLoader(), OrderService.class, new BrianJdkInvocationhandler(new OrderServiceImpl()));
            o.order();
        }
    
    }

    4.2.2 CGLIB动态代理 

    Cglib是一个强大的,高性能,高质量的代码生成类库。它可以在运行期扩展JAVA类与实现JAVA接口。其底层实现是通过ASM字节码处理框架来转换字节码并生成新的类。大部分功能实际上是ASM所提供的,Cglib只是封装了ASM,简化了ASM操作,实现了运行期生成新的class。

    CGLIB原理

    运行时动态的生成一个被代理类的子类(通过ASM字节码处理框架实现),子类重写了被代理类中所有非final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势植入横切逻辑。

    Cglib优缺点

    优点:JDK动态代理要求被代理的类必须实现接口,当需要代理的类没有实现接口时Cglib代理是一个很好的选择。另一个优点是Cglib动态代理比使用java反射的JDK动态代理要快

    缺点:对于被代理类中的final方法,无法进行代理,因为子类中无法重写final函数

    CGLIB代理实现

    实现MethodInterceptor接口的intercept方法后,所有生成的代理方法都调用这个方法。

    intercept方法的具体参数有

    obj 目标类的实例

    1.     method 目标方法实例(通过反射获取的目标方法实例)

    2.     args 目标方法的参数

    3.     proxy 代理类的实例

    该方法的返回值就是目标方法的返回值。

    public interface MessageService {
    
        void sendMessage(Object object);
    }
    
    
    @Slf4j
    public class MessageServiceImpl implements MessageService {
    
        @Override
        public void sendMessage(Object object) {
            log.info("发送航班消息:" + object);
        }
    }
    
    @Slf4j
    public class BrianMethodInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            log.info("cglib执行前....");
            Object result = methodProxy.invokeSuper(o, objects);
            log.info("cglib执行后....");
            return result;
        }
    }
    
    
            System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"C:\Users\LiangHuang\Desktop\work");
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(MessageServiceImpl.class);
            enhancer.setCallback(new BrianMethodInterceptor());
            MessageServiceImpl messageService = (MessageServiceImpl) enhancer.create();
    
            messageService.sendMessage("CX233航班即将起飞...");

    其中通过设置 System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"C:\Users\LiangHuang\Desktop\work"),将动态生成的class文件生成到指定文件夹下

    可以看到生成了三个class文件

     其中 MessageServiceImpl$$EnhancerByCGLIB$$dc2beff3 就是我们到代理类,可以看到可以看到继承了我们的MessageServiceImpl类

     MessageServiceImpl$$EnhancerByCGLIB$$dc2beff3$$FastClassByCGLIB$$1d34c464  继承自FastClass类

     MessageServiceImpl$$FastClassByCGLIB$$98ff1a9f  也是继承自FastClass类,

    仔细看下两个类的invoke()方法发现两者区别

     

    ok,现在我们把MessageServiceImpl$$FastClassByCGLIB$$98ff1a9f 复制出来,命名为java文件Debug看一下

            MessageServiceImpl$$EnhancerByCGLIB$$dc2beff3 enhancerByCGLIB$$dc2beff3 = new MessageServiceImpl$$EnhancerByCGLIB$$dc2beff3();
            BrianMethodInterceptor[] brianMethodInterceptors = new BrianMethodInterceptor[1];
            brianMethodInterceptors[0] = new BrianMethodInterceptor();
    
            enhancerByCGLIB$$dc2beff3.setCallbacks(brianMethodInterceptors);
            enhancerByCGLIB$$dc2beff3.sendMessage("CX880航班30分钟后停靠E22...");

     

     

     

     下面的代码要开始翻看class编译代码

     var10000就是我们的动态生成的代理类,那这个索引值17在哪获取的呢?看下面 

     最后看下我们代理类的的CGLIB$sendMessage$0方法,实际走的supper.sendMessage()方法,即走我们目标的类MessageServiceImpl的sendMessage()方法

     

    Maven依赖

    <dependencies>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>
    </dependencies>  

    静态代理与动态代理区别

    静态代理需要自己写代理类,而动态代理不需要写代理类。

    JDK动态代理与CGLIB实现区别

    JDK动态代理底层实现:

    1. 基于实现接口的方式生成动态代理代理对象,通过java反射技术调用目标方法,相比直接调用方法慢

    2. 执行过程分4步

    2.1 拼接java源代码$Proxy0.java

    2.2 通过编译器编译为class文件

    2.3 通过类读取class文件到内存中

    2.4 采用java反射技术执行我们的目标方法

    CGLIB动态代理底层实现:

    1.通过继承类的方式生成我们的代理对象,使用ASM字节码技术实现,通过FastClass索引机制直接调用我们的目标方法。

    2.执行过程分3步

    2.1 采用ASM字节码技术生成class文件

    2.2 通过类加载器读取class文件到内存中

    2.3 采用FastClass索引技术直接调用我们的目标方法,比Java反射技术效率要高

  • 相关阅读:
    Windows 编程入门,了解什么是UWP应用。
    java getway springcloud 记录请求数据
    nginx服务器配置传递给下一层的信息的一些参数-设置哪些跨域的域名可访问
    e.printStackTrace() 原理的分析
    关于性能测试组出现的问题查询和优化
    springboot connecting to :mongodb://127.0..0.1:27017/test authentication failed
    redis 集群 slots are covered by nodes.
    @PostConstruct +getapplicationcontext.getbean springboot获取getBean
    idea 错误: 找不到或无法加载主类 xx.xxx.Application
    elastic-job和spring cloud版本冲突2
  • 原文地址:https://www.cnblogs.com/hlkawa/p/13263607.html
Copyright © 2020-2023  润新知