动态代理实例分析

代理是什么?
为某个对象提供一个代理,以控制对这个对象的访问。使用jdk代理那就必然要定义和实现接口
动态代理本质通过反射创建对象

一、静态代理实现

       静态代理可以由继承实现,或者是接口实现。明显接口实现要要优于继承实现。

       讲一下静态代理的实现,分为委托类和被代理的类,以及代理接口。其中委托类和被代理的类都要实现代理接口。
用代码来举例

1、单个代理

代理接口:

	package Proxy;
	
	public interface UserDao {
	    public void save();
	}

被代理的类:

	package Proxy;
	
	public class UserDaoImpl implements UserDao{
	
	    @Override
	    public void save() {
	        System.out.println("存钱");
	    }
	}

委托类:
委托类成员中需要被代理的类对象,所以要加上构造方法。

	package Proxy;
	
	public class Log1 implements UserDao {
	
	    private UserDao userDao;
	
	    public Log1(UserDao userDao) {
	        this.userDao = userDao;
	    }
	
	    @Override
	    public void save() {
	        System.out.println("log1 start ...");
	        userDao.save();
	    }
	}

下面进行简单的测试,

	public class StaticProxy {
	    public static void main(String[] args) {
	        UserDao userDao = new UserDaoImpl();
	        Log1 log1 = new Log1(userDao);
	        log1.save();
	//        Log2 log2 = new Log2(log1);
	//        log2.save();
	    }
	}

运行结果:
Alt
可以看到一个简单的代理生效了。

那怎么添加多个代理呢?很简单,一个代理增加一个类就可以了,同样也实现代理接口。

2、双代理

委托类2:

	package Proxy;
	
	public class Log2 implements UserDao {
	
	    private UserDao userDao;
	
	    public Log2(UserDao userDao) {
	        this.userDao = userDao;
	    }
	
	    @Override
	    public void save() {
	        System.out.println("log2 start ...");
	        userDao.save();
	    }
	}


将之前的代码里面的注释去掉,在将log1.save();也注释掉,可以看到下面的运行结果
Alt
可以看到两个代理也成功了。



二、手撸动态代理

       但是如果开发中用静态代理也太蠢了,每次要增加代理都要增加相对应的类,文件会非常多。因此动态代理出现了。
       跟着视频敲了手撸动态代理的代码,这个动态代理只用反射创建了一个委托对象,jdk1.7提供的是通过Class数组创建了委托对象的数组。看了下面的原理,相信会对后期框架AOP的学习很有帮助。

1、得到一个委托对象的java文件
	//编写java文件
	        String content = "";
	        Class targetInfo = target.getClass().getInterfaces()[0];   //java是多实现
	        String targetInfoName = targetInfo.getSimpleName();
	//        String packageContent = "package Proxy;";
	        String packageContent = "";
	        String importContent = "import " + targetInfo.getName() + ";";
	        String firstContent = "public class $Proxy implements " + targetInfoName + "{";
	        String fieldContent = "private " + targetInfoName + " target;";
	        String constructorContent = "public $Proxy(" + targetInfoName + " target){" +
	                "this.target=target;" + "}";
	
	        Method[] methods = targetInfo.getDeclaredMethods();
	        String methodContent = "";
	        for (Method method : methods) {
	            String returnTypeName = method.getReturnType().getSimpleName();
	            String methodName = method.getName();
	            Class[] parameterTypes = method.getParameterTypes();
	            String paramsContent = "";
	            String argsContent = "";
	            int i = 0;
	            for (Class parameterType : parameterTypes) {
	                String simpleName = parameterType.getSimpleName();
	                argsContent += simpleName + " p" + i + ",";
	                paramsContent += "p" + i + ",";
	                i++;
	            }
	            if (argsContent.length() > 0) {
	                argsContent = argsContent.substring(0, argsContent.length() - 1);
	                paramsContent = paramsContent.substring(0, paramsContent.length() - 1);
	            }
	            methodContent += "public " + returnTypeName + " " + methodName + "(" + argsContent + "){" +
	                    "System.out.println(\"log kkk proxy...\");" +
	                    "target." + methodName + "(" + paramsContent + ");" +
	                    "}";
	        }
	        content += packageContent + importContent + firstContent + fieldContent + constructorContent + methodContent + "}";
	
	        File file = new File("src/$Proxy.java");
	        FileWriter fw = null;
	        if (!file.exists()) {
	            try {
	                file.createNewFile();
	                fw = new FileWriter(file);
	                fw.write(content);
	                fw.flush();
	                fw.close();
	            } catch (IOException e) {
	                e.printStackTrace();
	            }
	        }

上面的代码就是简单的拼接一下委托对象,拼接完后结果如下图:
Alt

2、将委托对象的java文件编译程字节码文件
			//jdk1.6把文件编译成class文件
	        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
	        StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
	        Iterable units = fileManager.getJavaFileObjects(file);
	        JavaCompiler.CompilationTask t = compiler.getTask(null, fileManager, null, null, null, units);
	        t.call();
	        try {
	            fileManager.close();
	        } catch (IOException e) {
	            e.printStackTrace();
	        }

3、通过反射创建对象(显示的用URLClassLoader)

       jdk1.7封装好的是直接使用ClassLoader类加载器,将字节码文件加载到内存。而且还能一次性加载多个字节码文件。

 		URL url = null;
        URL[] urls = null;
        try {
            url = file.toURI().toURL();
            String temp = url.toString();
            String finalPath = temp.substring(0, temp.lastIndexOf("/"));
            System.out.println(finalPath);
            urls = new URL[]{new URL(finalPath)};
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        URLClassLoader urlClassLoader = new URLClassLoader(urls);
        Class cla = null;
        Constructor constructor = null;
        Object proxy = null;
        try {
            //通过反射实例化对象
            cla = urlClassLoader.loadClass("$Proxy");	//只加载了一个class文件
            constructor = cla.getConstructor(targetInfo);
            proxy = constructor.newInstance(target);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

4、返回委托对象
 	return proxy;

运行结果
Alt
踩的坑:URLClassLoader这个类加载器的loadClass方法,应该是在当前url下从前往后寻找匹配的Class文件,找到就返回。

手写单层的完整代码以及测试代码:click



三、使用jdk1.7封装好的动态代理

test文件:
       其中Proxy.newProxyInstance函数的参数分别是被代理对象的类加载器被代理对象的接口方法执行器。通过前面的手写动态代理也知道了前两个参数的意义,前两个参数就是为第三个参数创建委托对象服务的。

	package Proxy;
	
	
	import java.lang.reflect.Proxy;
	
	public class TestDemoFZ {
	    public static void main(String[] args) {
	        UserDao userDao = new UserDaoImpl();
	        MyHandle myHandle = new MyHandle(userDao);    //委托对象
	        UserDao userProxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), myHandle);
	        userProxy.save();	//代理对象调用自己的方法
	    }
	}

委托对象代码:

	package Proxy;

	import java.lang.reflect.InvocationHandler;
	import java.lang.reflect.Method;
	
	public class MyHandle implements InvocationHandler {
	
	    private UserDao userDao;
	
	    public MyHandle(UserDao userDao) {
	        this.userDao = userDao;
	    }
	
	    /**
	     *
	     * @param proxy     代理对象, 不要去动它
	     * @param method    当前要执行的被代理对象的方法
	     * @param args      这个方法调用时外界传入的参数值
	     * @return			返回的代理对象
	     * @throws Throwable
	     */
	    @Override
	    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
	        System.out.println("before log...");
	        method.invoke(userDao);   //反射调用被代理对象的方法(方法.对象)
	        System.out.println("after log...");
	        return proxy;
	    }
	}


运行结果
Alt
java jdk1.7动态代理,其中方法执行器中的invoke方法返回的是代理对象,其实感觉像也就是某个接口的对象
搞明白了动态代理,希望接下去看AOP的时候能明白一点!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值