美文网首页
java中代理及实现

java中代理及实现

作者: cuzz_ | 来源:发表于2018-07-05 14:21 被阅读0次

    基本概念

    代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.、
    这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法

    静态代理

    静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类,如下面例子

    • 接口
    public class UserDaoImpl implements UserDao {
    
        public void save() {
            System.out.println("保存数据");
        }
    }
    
    • 实现类
    public class UserDaoImpl implements UserDao {
    
        public void save() {
            System.out.println("保存数据");
        }
    }
    
    • 代理类
    public class StaticUserDaoProxy implements UserDao{
        
        // 接受保存目标对象
        private UserDao target;
        
        public StaticUserDaoProxy(UserDao target) {
            this.target = target;
        }
    
        public void save() {
            System.out.println("before StaticUserDaoProxy...");
            // 执行目标对象的方法
            target.save();      
            System.out.println("after StaticUserDaoProxy...");
        }
    }
    
    • 测试类
    public class TestStaticProxy {
        public static void main(String[] args) {
            UserDao user = new UserDaoImpl();
            StaticUserDaoProxy userDaoProxy = new StaticUserDaoProxy(user);
            userDaoProxy.save();
        }
    }
    
    • 测试结果
    before StaticUserDaoProxy...
    保存数据
    after StaticUserDaoProxy...
    
    • 缺点
      因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多。同时,一旦接口增加方法,目标对象与代理对象都要维护

    jdk动态代理

    特点

    • 代理对象,不需要实现接口
    • 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

    实现方式

    JDK实现代理只需要使用newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:
    newProxyInstance(ClassLoader loader, Class<>[] interfaces,InvocationHandler h )

    newProxyInstance方法用来返回一个代理对象,这个方法总共有3个参数,ClassLoader loader用来指明生成代理对象使用哪个类装载器,Class<?>[] interfaces用来指明生成哪个对象的代理对象,通过接口指定,InvocationHandler h用来指明产生的这个代理对象要做什么事情。所以我们只需要调用newProxyInstance方法就可以得到某一个对象的代理对象了。

    • jdk代理类
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class JDKProxy implements InvocationHandler{
    
        // 维护一个目标对象
        private Object target;
        
        // 获取代理对象
        public Object getInstance(Object target) {
            this.target = target;
            Class clazz = target.getClass();
            return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
    
            System.out.println("before JDKProxy...");
            UserDao user = (UserDao) this.target;
            // 执行目标对象的方法
            user.save();
            System.out.println("after JDKProxy...");
            return null;
        }
    }
    
    • 测试类
    package com.cuzz.proxy;
    
    public class TestJDKProxy {
        public static void main(String[] args) {
            UserDao user = new UserDaoImpl();
            System.out.println("原始类:" + user.getClass());
            JDKProxy jdkProxy = new JDKProxy();
            UserDao proxyUser = (UserDao)jdkProxy.getInstance(user);
            System.out.println("代理类:" + proxyUser.getClass());
            proxyUser.save();
        }
    }
    
    • 测试结果
    原始类:class com.cuzz.proxy.UserDaoImpl
    代理类:class com.sun.proxy.$Proxy0
    before JDKProxy...
    保存数据
    after JDKProxy...
    

    局限性

    通过反射类Proxy和InvocationHandler回调接口实现的jdk动态代理,要求委托类必须实现一个接口,但事实上并不是所有类都有接口,对于没有实现接口的类,便无法使用该方方式实现动态代理

    实现自己的动态代理

    • Proxy类 -> MyProxy类
      主要的方法newProxyInstance
    package com.cuzz.custom;
    
    import java.io.File;
    import java.io.FileWriter;
    import java.io.IOException;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    // 生成代理对象的代码
    public class MyProxy {
    
        private Object target;
    
        // 换行
        private static String ln = "\r\n";
        
        public static Object newProxyInstance(MyClassLoader classLoader, Class<?>[] interfaces, MyInvocationHandler h) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            
            try {
                // 1. 生成员代码
                String proxySrc = generateSrc(interfaces);
                // System.out.println(proxySrc);
                
                // 2. 将生成的源代码输出到磁盘  保存为.java文件
                String filePath = MyProxy.class.getResource("").getPath();
                
                File f = new File(filePath + "$Proxy0.java");
                System.out.println(f);
            
                FileWriter fw = new FileWriter(f);
                fw.write(proxySrc);
                fw.flush();
                fw.close();
    
                // 3. 编译源代码 并且生成.class文件
    //          JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
    //          StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
    //          Iterable iterable = manager.getJavaFileObjects(f);
    //          
    //          CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
    //          task.call();
    //          manager.close();
                
                // 4. 将class文件中的内容 动态加载到JVM中
                
                // 5. 返回被代理后的对象
                Class proxyClass = classLoader.findClass("$Proxy0"); // 找到这个类
                Constructor c = proxyClass.getConstructor(MyInvocationHandler.class);
                return c.newInstance(h);
    
            } catch (IOException e) {
                e.printStackTrace();
            }
            return null; 
        }
    
    
        
        private static String generateSrc(Class<?>[] interfaces){
            StringBuffer src = new StringBuffer();
            src.append("package com.cuzz.custom;" + ln);
            src.append("import java.lang.reflect.Method;" + ln);
            src.append("public class $Proxy0 implements " + interfaces[0].getName() + " {" + ln);
    
            src.append("MyInvocationHandler h;" + ln);
    
            src.append("public $Proxy0(MyInvocationHandler h) {" + ln);
            src.append("this.h = h;" + ln);
            src.append("}" + ln);
    
            for (Method m : interfaces[0].getMethods()) {
                src.append("public " + m.getReturnType() + " " + m.getName() + "() {" + ln); 
    
                src.append("try{" + ln);    
                src.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\", new Class[]{});" + ln);
                src.append("this.h.invoke(this, m, null);" + ln);
                src.append("}catch(Throwable e){e.printStackTrace();}" + ln);
                src.append("}" + ln);
            }
            
            
            src.append("}" + ln);
            return src.toString();
        }
    }
    
    • InvocationHandler -> MyInvocationHandler
    package com.cuzz.custom;
    
    import java.lang.reflect.Method;
    
    public interface MyInvocationHandler {
    
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable;
    }
    
    
    • ClassLoader -> MyClassLoader
    package com.cuzz.custom;
    
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    
    public class MyClassLoader extends ClassLoader{
        
        private File baseDir;
    
        public MyClassLoader() {
            String basePath = MyClassLoader.class.getResource("").getPath();
            this.baseDir = new File(basePath);
        }
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            String className = MyClassLoader.class.getPackage().getName() + "." + name;
            if (baseDir != null) {
                File classFile = new File(baseDir, name.replace(".", "/") + ".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);
                        }
                        System.out.println(out.size());
                        return defineClass(className, out.toByteArray(),0 ,out.size());
    
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        if(null != in){
                            try {
                                in.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        if(null != out){
                            try {
                                out.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            }
            return null;
        }
    }
    

    编译源代码需要导入一个rt.jar包

    • MyUserDaoProxy
    package com.cuzz.custom;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    
    public class MyUserDaoProxy implements MyInvocationHandler{
        
        private Object target;
    
        public Object getInstance(Object target) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            this.target = target;
            Class clazz = target.getClass();
            MyClassLoader classLoader = new MyClassLoader();
            return MyProxy.newProxyInstance(classLoader, clazz.getInterfaces(), this);
        }
        
        @Override
        public Object invoke(Object proxy, Method method, Object[] args)
                throws Throwable {
            System.out.println("before MyProxy...");
            UserDao user = (UserDao) this.target;
            // 执行目标对象的方法
            user.save();
            System.out.println("after MyProxy...");
            return null;
        }
    }
    
    • 测试类
    package com.cuzz.custom;
    
    import java.lang.reflect.InvocationTargetException;
    
    public class TestMyProxy {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
            UserDao user = new UserDaoImpl();
            System.out.println("原始类:" + user.getClass());
            MyUserDaoProxy myProxy = new MyUserDaoProxy();
            // myProxy.getInstance(user);
            UserDao proxyUser = (UserDao) myProxy.getInstance(user);
            proxyUser.save();
            System.out.println("代理类:" + proxyUser.getClass());
        }
    }
    
    • 测试结果
    原始类:class com.cuzz.custom.UserDaoImpl
    before MyProxy...
    保存数据
    after MyProxy...
    代理类:class com.cuzz.custom.$Proxy0
    
    • 最后把生成的.class 文件delete()掉,就神不知鬼不觉了。

    Cglib代理

    上面的静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

    Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。

    • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现。
    • Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
    • Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

    Cglib子类代理实现方法:

    • 需要引入cglib的jar文件,但是Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可。
    • 引入功能包后,就可以在内存中动态构建子类
    • 代理的类不能为final,否则报错
    • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

    实现方式

    • UserDao
      不是接口没有实现类
    public class UserDao {
        public void save() {
            System.out.println("保存数据");
        }
    }
    
    
    • 代理类
    public class ProxyFactory implements MethodInterceptor {
        
        // 维护目标对象
        private Object target;
        
        // 给目标对象创建一个代理对象
        public Object getInstane(Object target) {
            this.target = target;
            // 生成工具类
            Enhancer en = new Enhancer();
            // 设置父类
            en.setSuperclass(target.getClass());
            // 设置回调函数
            en.setCallback(this);
            // 创建子类(代理对象)
            return en.create();
        }
    
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("before CglibProxy...");
            // 执行目标对象方法
            method.invoke(target, args);
            System.out.println("after CglibProxy...");
            return null;
        }
    }
    
    • 测试类
    public class TestProxy {
        public static void main(String[] args) {
            UserDao user = new UserDao();
            System.out.println("原始类:" + user.getClass());
            UserDao proxyUser = (UserDao) new ProxyFactory().getInstane(user);
            System.out.println("代理类:" + proxyUser.getClass());
            proxyUser.save();
        }
    }
    
    • 测试结果
    原始类:class com.cuzz.cglib.UserDao
    代理类:class com.cuzz.cglib.UserDao$$EnhancerByCGLIB$$48ae455e
    before CglibProxy...
    保存数据
    after CglibProxy...
    

    总结

    • 动态代理的本质字节码的重组

    相关文章

      网友评论

          本文标题:java中代理及实现

          本文链接:https://www.haomeiwen.com/subject/zpueuftx.html