Java代理简述
1.什么是代理?
对类或对象(目标对象)进行增强功能,最终形成一个新的代理对象,(Spring Framework中)当应用调用该对象(目标对象)的方法时,实际调用的是代理对象增强后的方法,比如对功能方法login实现日志记录,可以通过代理实现;
PS:目标对象--被增强的对象;代理对象--增强后的对象;
2.为什么需要代理?
一些类里面的方法有相同的代码或类中有相同的功能,可以将这些相同抽取出来形成一个公共的方法或功能,但Java有两个重要的原则:单一职责(对类来说的,即一个类应该只负责一项职责)和开闭原则(开放扩展,修改关闭),如果每个类的每个功能都调用了公共功能,就破坏了单一职责,如下图;如果这个类是别人已经写好的,你动了这个代码,同时也破坏了开闭原则(同时改动代码很麻烦,里面可能涉及其他很多的调用,可能带出无数的bug,改代码比新开发功能还难/(ㄒoㄒ)/~~);
由于有上面的问题存在,使用代理来实现是最好的解决办法;
3.Java实现代理有哪些?
(1)静态代理:通过对目标方法进行继承或聚合(接口)实现;(会产生类爆炸,因此在不确定的情况下,尽量不要使用静态代理,避免产生类爆炸)
1)继承:代理对象继承目标对象,重写需要增强的方法;
//业务类(目标对象) public class UserServiceImpl { public void query(){ System.out.println("业务操作查询数据库...."); } } //日志功能类 public class Log { public static void info(){ System.out.println("日志功能"); } } //继承实现代理(代理对象) public class UserServiceLogImpl extends UserServiceImpl { public void query(){ Log.info(); super.query(); } }
从上面代码可以看出,每种增强方法会产生一个代理类,如果现在增强方法有日志和权限,单个方法增强那需要两个代理类(日志代理类和权限代理类),如果代理类要同时拥有日志和权限功能,那又会产生一个代理类,同时由于顺序的不同,可能会产生多个类,比如先日志后权限是一个代理类,先权限后日志又是另外一个代理类。
由此可以看出代理使用继承的缺陷:产生的代理类过多(产生类爆炸),非常复杂,维护难;
2)聚合:代理对象和目标对象都实现同一接口,使用装饰者模式,提供一个代理类构造方法(代理对象当中要包含目标对象),参数是接口,重写目标方法;
//接口 public interface UserDao { void query(); } //接口实现类:目标对象 public class UserDaoImpl implements UserDao { @Override public void query() { System.out.println("query......"); } } //日志功能类 public class Log { public static void info(){ System.out.println("日志功能"); } } //聚合实现代理:同样实现接口,使用装饰者模式;(代理对象) public class UserDaoLogProxy implements UserDao { UserDao userDao; public UserDaoLogProxy(UserDao userDao){//代理对象包含目标对象 this.userDao = userDao; } @Override public void query() { Log.info(); userDao.query(); } } //测试 public static void main(String[] args) { UserDaoImpl target = new UserDaoImpl(); UserDaoLogProxy proxy = new UserDaoLogProxy(target); proxy.query(); }
聚合由于利用了面向接口编程的特性,产生的代理类相对继承要少一点(虽然也是会产生类爆炸,假设有多个Dao,每个Dao产生一个代理类,所以还是会产生类爆炸),如下案例:
//时间记录功能 public class Timer { public static void timer(){ System.out.println("时间记录功能"); } } //代理类:时间功能+业务 public class UserDaoTimerProxy implements UserDao { UserDao userDao; public UserDaoTimerProxy(UserDao userDao){ this.userDao = userDao; } @Override public void query() { Timer.timer(); userDao.query(); } } //测试 public static void main(String[] args) { //timer+query UserDao target = new UserDaoTimerProxy(new UserDaoImpl()); //log+timer+query UserDao proxy = new UserDaoLogProxy(target); proxy.query(); } public static void main(String[] args) { //log+query UserDao target = new UserDaoLogProxy(new UserDaoImpl()); //timer+log+query UserDao proxy = new UserDaoTimerProxy(target); proxy.query(); }
PS:
装饰和代理的区别:代理不需要指定目标对象,可以对任何对象进行代理;装饰需要指定目标对象,所以需要构造方法参数或set方法指定目标对象;
几个io的Buffer流(BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter)就是使用了装饰模式的静态代理;
public class BufferedReader extends Reader { private Reader in; ........ public BufferedReader(Reader in, int sz) { super(in); if (sz <= 0) throw new IllegalArgumentException("Buffer size <= 0"); this.in = in; cb = new char[sz]; nextChar = nChars = 0; } public BufferedReader(Reader in) { this(in, defaultCharBufferSize); } }
(2)动态代理:Java有JDK动态代理和CGLIB代理;(Spring Framework通过Spring AOP实现代理,底层还是使用JDK代理和CGLIB代理来实现)
模拟动态代理:不需要手动创建类文件(因为一旦手动创建类文件,会产生类爆炸),通过接口反射生成一个类文件,然后调用第三方的编译技术,动态编译这个产生的类文件成class文件,然后利用URLclassLoader把这个动态编译的类加载到jvm中,然后通过反射把这个类实例化。(通过字符串产生一个对象实现代理);
PS:Java文件 -> class文件 -> byte字节(JVM)-> class对象(类对象)-> new(对象);
所以步骤如下:
1)代码实现一个内容(完整的Java文件内容,包含包名、变量、构造方法、增强后的方法等),使其通过IO产生一个Java文件;
2)通过第三方编译技术产生一个class文件;
3)加载class文件利用反射实例化一个代理对象出来;
public class ProxyUtil { /** * content --->string * | * |生成 * v * .java <-----通过io产生 * .class <-----Java文件编程产生 * * .new <-----class文件反射产生实例对象 * @return */ public static Object newInstance(Object target){ Object proxy=null; //根据对象获取接口 Class targetInf =target.getClass().getInterfaces()[0]; //获取接口的所有方法 //getMethods(),该方法是获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的) //getDeclaredMethods(),该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法 Method[] declaredMethods = targetInf.getDeclaredMethods(); String line="\n"; String tab ="\t"; //接口名称 String targetInfName = targetInf.getSimpleName(); String content =""; //包位置 String packageContent = "package com;"+line; //接口 String importContent = "import "+targetInf.getName()+";"+line; String clazzFirstLineContent = "public class $Proxy implements "+targetInfName+"{"+line; //属性 String filedContent =tab+"private "+targetInfName+" target;"+line; //构造方法 String constructorContent =tab+"public $Proxy ("+targetInfName+" target){" +line +tab+tab+"this.target =target;" +line+tab+"}"+line; //方法 String methodContent = ""; for(Method method:declaredMethods){ //返回值 String returnTypeName = method.getReturnType().getSimpleName(); //方法名 String methodName = method.getName(); // Sting.class String.class 参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); String argsContent = ""; String paramsContent=""; int flag = 0; for(Class args :parameterTypes){ //String,参数类型 String simpleName = args.getSimpleName(); //String p0,Sting p1, argsContent+=simpleName+" p"+flag+","; paramsContent+="p"+flag+","; flag++; } if (argsContent.length()>0){ argsContent=argsContent.substring(0,argsContent.lastIndexOf(",")-1); paramsContent=paramsContent.substring(0,paramsContent.lastIndexOf(",")-1); } methodContent+=tab+"public "+returnTypeName+" "+methodName+"("+argsContent+") {"+line //增强方法先写死 +tab+tab+"System.out.println(\"log\");"+line; if(returnTypeName.equals("void")){ methodContent+=tab+tab+"target."+methodName+"("+paramsContent+");"+line +tab+"}"+line; }else{ methodContent+=tab+tab+"return target."+methodName+"("+paramsContent+");"+line +tab+"}"+line; } } content+=packageContent+importContent+clazzFirstLineContent+filedContent +constructorContent+methodContent+"}"; //生成Java文件 File file = new File("D:\\com\\$Proxy.java"); try{ if(!file.exists()){ file.createNewFile(); } //创建 FileWriter wr = new FileWriter(file); wr.write(content); wr.flush(); wr.close(); //编译Java文件 JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null, null, null); Iterable units = fileMgr.getJavaFileObjects(file); JavaCompiler.CompilationTask t = compiler.getTask(null, fileMgr, null, null, null, units); t.call(); fileMgr.close(); //new --> 反射 URL[] urls = new URL[]{new URL("file:D:\\\\")}; //加载外部文件 URLClassLoader classLoader = new URLClassLoader(urls); Class<?> proxyClass = classLoader.loadClass("com.$Proxy"); Constructor constructor = proxyClass.getConstructor(targetInf); //构造方法创建实例 proxy = constructor.newInstance(target); //clazz.newInstance();//根据默认构造方法创建对象 //Class.forName() }catch (Exception e){ e.printStackTrace(); } return proxy; } }
自定义代理的缺点:生成Java文件;动态编译文件;需要一个URLClassLoader;涉及到了IO操作,软件的最终性能体现到了IO操作,即IO操作影响到软件的性能;
案例:
1)接口:
public interface UserDao { void query(); void query(String name); String getName(String id); }
2)实现类:
public class UserService implements UserDao { @Override public void query() { System.out.println("query"); } @Override public void query(String name) { System.out.println(name); } @Override public String getName(String id) { System.out.println("id:"+id); return "李四"; } }
3)测试:
public static void main(String[] args) { UserDao dao = (UserDao) ProxyUtil.newInstance(new UserService()); dao.query(); dao.query("张三"); } --------结果-------- log query log 张三
package com; import com.hrh.dynamicProxy.dao.UserDao; public class $Proxy implements UserDao{//自定义动态代理生成的文件 private UserDao target; public $Proxy (UserDao target){ this.target =target; } public String getName(String p) { System.out.println("log"); return target.getName(p); } public void query(String p) { System.out.println("log"); target.query(p); } public void query() { System.out.println("log"); target.query(); } }
JDK动态代理:基于反射实现,通过接口反射得到字节码,然后将字节码转成class,通过一个native(JVM实现)方法来执行;
案例实现:实现 InvocationHandler 接口重写 invoke 方法,其中包含一个对象变量和提供一个包含对象的构造方法;
public class MyInvocationHandler implements InvocationHandler { Object target;//目标对象 public MyInvocationHandler(Object target){ this.target=target; } /** * @param proxy 代理对象 * @param method 目标对象的目标方法 * @param args 目标方法的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("log"); return method.invoke(target,args); } }
public class MyInvocationHandlerTest { public static void main(String[] args) { //参数: 当前类的classLoader(保证MyInvocationHandlerTest当前类可用) // 接口数组:通过接口反射得到接口里面的方法,对接口里面的所有方法都进行代理 // 实现的InvocationHandler:参数是目标对象 UserDao jdkproxy = (UserDao) Proxy.newProxyInstance(MyInvocationHandlerTest.class.getClassLoader(), new Class[]{UserDao.class},new MyInvocationHandler(new UserService())); jdkproxy.query("query"); //-----结果------ //log //query } }
下面查看底层JDK生成的代理类class:
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy18", new Class[]{UserDao.class}); try { FileOutputStream fileOutputStream = new FileOutputStream("xxx本地路径\\$Proxy18.class"); fileOutputStream.write(bytes); fileOutputStream.flush(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
或在代码的最前面添加下面代码:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "xxx本地路径");
代理类class反编译后的内容:下面的内容验证了JDK动态代理为什么基于聚合(接口)来的,而不能基于继承来的?因为JDK动态代理底层已经继承了Proxy,而Java是单继承,不支持多继承,所以以接口来实现;
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // import com.hrh.dao.UserDao; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; public final class $Proxy18 extends Proxy implements UserDao { private static Method m1; private static Method m4; private static Method m5; private static Method m2; private static Method m0; private static Method m3; public $Proxy18(InvocationHandler var1) throws { super(var1); } public final boolean equals(Object var1) throws { try { return (Boolean)super.h.invoke(this, m1, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void query(String var1) throws { try { super.h.invoke(this, m4, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } public final void query() throws { try { super.h.invoke(this, m5, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String toString() throws { try { return (String)super.h.invoke(this, m2, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final int hashCode() throws { try { return (Integer)super.h.invoke(this, m0, (Object[])null); } catch (RuntimeException | Error var2) { throw var2; } catch (Throwable var3) { throw new UndeclaredThrowableException(var3); } } public final String getName(String var1) throws { try { return (String)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } } static { try { m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object")); m4 = Class.forName("com.hrh.dao.UserDao").getMethod("query", Class.forName("java.lang.String")); m5 = Class.forName("com.hrh.dao.UserDao").getMethod("query"); m2 = Class.forName("java.lang.Object").getMethod("toString"); m0 = Class.forName("java.lang.Object").getMethod("hashCode"); m3 = Class.forName("com.hrh.dao.UserDao").getMethod("getName", Class.forName("java.lang.String")); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
CGLIB代理:借助asm(一个操作字节码的框架)实现代理操作;CGLIB基于继承来的(前文有CGLIB代理类class的反编译可以看出);
public class UserService { public void query(){ System.out.println("query"); } public static void main(String[] args) { // 通过CGLIB动态代理获取代理对象的过程 Enhancer enhancer = new Enhancer(); // 设置enhancer对象的父类 enhancer.setSuperclass(UserService.class); // 设置enhancer的回调对象 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("before method run..."); Object result = proxy.invokeSuper(obj, args); System.out.println("after method run..."); return result; } }); //创建代理对象 UserService bean = (UserService) enhancer.create(); bean.query(); } }
//-----------结果------
before method run...
query
after method run...
PS:如果只是对项目中一个类进行代理,可以使用静态代理,如果是多个则使用动态代理;
关于代理的其他相关知识介绍可参考前文:Spring(11) - Introductions进行类扩展方法、Spring笔记(3) - SpringAOP基础详解和源码探究
如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【码猿手】。