反射 类存储结构和反射 反射(Reflection) 是 Java 的高级特性之一,是框架实现的基础。 定义:Java 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 语言的反射机制。 反射是对类元属性的封装,支持运行时访问和修改类属性,包括从 Class 中获取信息、创建对象、调用方法、访问属性值等。 说到类就不得不提类的加载过程,类是方法区中的一个数据结构,创建类时首先由类加载器读入字节码,然后进行验证、分配内存和初始化等过程,每个类加载器都有其命名空间,用于保存类结构,反射就是对这个类的结构的访问接口 。 一般而言,当用户使用一个类的时候,应该获取这个类,而后通过这个类实例化对象,但是使用反射则可以相反的通过对象获取类中的信息。 通俗的讲反射就是可以在程序运行的时候动态装载类,查看类的信息,生成对象,或操作生成的对象。它允许运行中的 Java 程序获取自身的信息,自己能看到自己,就像照镜子一样。 另外,为什么要有反射这个“后门”呢?首先我们要区分静态和动态编译的概念:
静态编译:在编译时确定类型,绑定对象
动态编译:运行时确定类型,绑定对象 两者的区别在于,动态编译可以最大程度地支持多态,多态最大的意义在于降低类的耦合性 ,而 Java 语言确是静态编译(如:int a=3;要指定类型),Java 反射很大程度上弥补了这一不足:解耦以及提高代码的灵活性 ,所以 Java 也被称为是准动态语言 。 尽管反射有诸多优点,但是反射也有缺点:
由于反射会额外消耗一定的系统资源,因此,反射操作的效率要比那些非反射操作低得多 。
由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用 (代码有功能上的错误)。所以能用其它方式实现的就尽量不去用反射。
获取类信息 Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM 已经帮我们创建好了。 如果知道一个实例,那么可以通过实例的“getClass()”方法获得运行实例的 Class(该类型的字节码文件对象),如果你知道一个类型,那么你也可以使用“.class”的方法获得运行实例的 Class。 一般获取 Class 的方法有三种:
1 2 3 4 5 6 7 // 方式1:通过 Class 类的静态方法获取 Class 类对象 (推荐) Class c = Class.forName("java.util.List"); // 方式2:因为所有类都继承 Object 类。因而可通过调用 Object 类中的 getClass 方法来获取 Object o = new Object(); o.getClass(); // 方式3:任何数据类型(包括基本数据类型)都有一个“静态”的 class 属性 Class c = Object.class;
以下示例中,obj 表示要解析的对象实例,clazz 表示获取到的该对象对应的字节码,用于分析包、类等信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 1 通过对象得到java的字节码 Class clazz = obj.getClass(); // clazz对象是 java内存的对象的字节码 // 2通过字节码获取包名 Package pk = clazz.getPackage(); String pname = pk.getName(); // demo.neusoft.po System.out.println("该类包:" + pname); // 3通过字节码对象得到类的全路径 String fullpath = clazz.getName(); System.out.println("类全路径:" + fullpath); // 4通过字节码对象得到类的名字 String simpleName = clazz.getSimpleName(); System.out.println("类名:" + simpleName); // 5通过字节码对象得到类的父类 Class fatherClazz = clazz.getSuperclass(); System.out.println(simpleName + "类的父类:" + fatherClazz.getSimpleName()); // 6通过字节码对象得到类的接口 Class[] interfaces = clazz.getInterfaces(); for (Class iclazz : interfaces) { System.out.println(simpleName + "类的接口:" + iclazz.getName()); } // 7返回表示数组组件类型的 Class。如果此类是数组则返回表示此类组件类型的Class,如果此类不表示数组类,则此方法返回 null Object[] o = new Object[4]; Class<?> componentType = o.getClass().getComponentType();
获取类加载器 返回该类的类加载器。有些实现可能使用 null 来表示引导类加载器,如果该类由引导类加载器加载,则此方法在这类实现中将返回 null,比如 Object。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 //1、获取一个系统的类加载器 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); System.out.println("系统的类加载器-->" + classLoader); //2、获取系统类加载器的父类加载器(扩展类加载器(extensions classLoader)) classLoader = classLoader.getParent(); System.out.println("扩展类加载器-->" + classLoader); //3、获取扩展类加载器的父类加载器 //输出为Null,引导类加载器无法被Java程序直接引用 classLoader = classLoader.getParent(); System.out.println("启动类加载器-->" + classLoader); //4、测试当前类由哪个类加载器进行加载 ,结果就是系统的类加载器 classLoader = Class.forName("com.tallate.repackage_2018.reflect.ClassTest").getClassLoader(); System.out.println("当前类由哪个类加载器进行加载-->"+classLoader); //5、测试JDK提供的Object类由哪个类加载器负责加载的 classLoader = Class.forName("java.lang.Object").getClassLoader(); System.out.println("JDK提供的Object类由哪个类加载器加载-->" + classLoader);
如果存在安全管理器,并且调用者的类加载器不是 null,也不同于或是请求其类加载器的类的类加载器的祖先,则此方法通过 RuntimePermission(“getClassLoader”)权限调用此安全管理器 checkPermission 方法,以确保可以访问该类的类加载器。如果此对象表示一个基本类型或 void,则返回 null。
实例化对象 可能会抛出 InstantiationException 异常
1 2 3 4 5 6 7 Object newObj =null; // 7.通过字节码对象实例化一个对象 newObj = clazz.newInstance();//相当于按类实例化一个对象 System.out.println("比较参数对象:"+newObj==obj); // 8.通过全路径实例化对象 Class<?> newObj2 = Class.forName(fullpath); System.out.println("比较全路径对象:"+newObj2==obj);
分析属性 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 9.通过字节码对象 分析属性 Field[] fields = clazz.getFields();//得到非私有属性 fields=clazz.getDeclaredFields();//获取私有属性 for (Field f : fields) { //获取属性名 String fname = f.getName(); //属性的类型 Class type = f.getType(); //针对私有属性的访问,临时放开权限 f.setAccessible(true); //获取属性的值 Object fval = f.get(obj);//get方法获取内存中的值,参数要求 //针对属性设置 //set(当前对象本身,属性设置的值); //f.set(newObj, value); //将当前对象的内存传递过来 System.out.println("属性名称:"+fname+ ",属性类型"+type.getSimpleName()+ ",属性值:"+fval); }
分析方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 // 10.通过字节码文件得到当前类的方法 //clazz.getMethod(name, parameterTypes) Method[] methods = clazz.getMethods(); // 只获取public的 methods=clazz.getDeclaredMethods(); // 获取全部 for (Method method : methods) { //分析方法结构 //得到方法返回类型 Class<?> returnType = method.getReturnType(); //方法名称 String name = method.getName(); //方法中参数类型集合 Class<?>[] pts = method.getParameterTypes(); String s=""; for (Class<?> pclazz : pts) { String ptype = pclazz.getSimpleName(); s=s+ptype+","; } System.out.println("方法返回类型:"+returnType+ "方法名:"+name+ "方法参数类型:"+s); } // 11.方法反射执行 //A:得到指定名称的方法对象的声明 Method method = clazz.getMethod("getStuname"); //B: 通过方法对象反射执行 //invoke(方法的当前对象本身,方法执行需要的实际参数) Object val = method.invoke(obj); System.out.println("getStuname方法返回:"+val); // 12.通过字节码对象得到构造方法 Constructor[] constructors = clazz.getConstructors(); for (Constructor constructor : constructors) { //得到构造器的名称 String name = constructor.getName(); Class[] cps = constructor.getParameterTypes(); //构造器的反射执行 //constructor.newInstance(initargs) }
*** 例子
这个例子通过反射实例化一个 MyIOC 类,然后找到它类型为 AttributeType 的成员变量,然后调用对应的 setter 方法设置属性1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static String toUpperStr(String str) { return Character.toUpperCase(str.charAt(0)) + str.substring(1); } public static void main(String[] args) { try { Class c = Class.forName("MyIOC"); Object o = c.newInstance(); Field[] declaredFields = c.getDeclaredFields(); for (Field f : declaredFields) { if (f.getType() == AttributeType.class) { Method set = c.getDeclaredMethod("set" + toUpperStr(f.getName()), AttributeType.class); set.invoke(o, new AttributeType("Hello")); } } } catch(Exception e) { e.printStackTrace(); } }
绕过 private、final 限制 1 2 3 4 5 6 7 8 Field field = getField(target, fieldName); // 设置private可访问 field.setAccessible(true); // 设置final可修改 Field modifierField = Field.class.getDeclaredField("modifiers"); modifierField.setAccessible(true); modifierField.setInt(field, field.getModifiers() & ~Modifier.FINAL); field.set(target, value);
越过范型检查 泛型作用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的。
1 2 3 4 5 6 7 8 9 10 11 public static void test10() throws Exception{ ArrayList<Integer> list = new ArrayList<Integer>(); list.add(100); list.add(200); Method method = list.getClass().getMethod("add", Object.class); method.invoke(list, "Java反射机制实例。"); //遍历集合 for(Object obj : list){ System.out.println(obj); } }
通过反射机制获得数组信息并修改数组的大小和值 下面的方法可以扩展数组到新的长度。
1 2 3 4 5 6 7 public Object arrayInc(Object oldArr, int newLen) { Class<?> arr = oldArr.getClass().getComponentType(); Object newArr = Array.newInstance(arr, newLen); int originLength = Array.getLength(oldArr); System.arraycopy(oldArr, 0, newArr, 0, originLength); return newArr; }
下面的代码通过反射来修改数组某一位置的值。
反射应用于工厂模式 平时在使用数据库的时候我们一般都需要在配置文件中设置所需的驱动器类,实际上就是应用了工厂模式,比如如果需要使用 MySQL 数据库:
1 2 3 4 driver=com.mysql.jdbc.Driver ``` ### 使用反射实现简易ORM框架 #### 属性声明
public class BaseDao { private Connection con; private PreparedStatement pstm; private ResultSet rs; // 连接的一些属性 private String driver; private String url; private String uname; private String pwd; … }
static { Properties p = new Properties(); try { FileInputStream rd = new FileInputStream(“db.properties”); p.load(rd); } catch(IOException e) { e.printStackTrace(); } driver = p.getProperty(“driver”); url = p.getProperty(“url”); name = p.getProperty(“name”); pwd = p.getProperty(“pwd”); }
private Object run(IDao idao,String sql,Object…objs){ Object obj=null;//执行 CRUD 返回的具体值 try { // 1.提取 CRUD 中重复代码 Class.forName(driver); con = DriverManager.getConnection(url, uname, pwd); pstm = con.prepareStatement(sql); // 2.为 sql 语句中问号赋值 for (int i = 0; i < objs.length; i++) { pstm.setObject(i + 1, objs[i]); } // 3.利用第一个参数提供 crud 模板对象,执行具体的 crud 并返回结果 obj=idao.init(pstm); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); }finally{ try { if(rs!=null) rs.close(); if (pstm != null) pstm.close(); if (con != null) con.close(); } catch (SQLException e) { e.printStackTrace(); } } return obj; }
public interface IDao { public Object init(PreparedStatement pstm)throws SQLException; }
public int saveOrUpdate(String sql, Object… objs) { return (Integer) run(new IDao() { @Override public Object init(PreparedStatement pstm) throws SQLException { return pstm.executeUpdate(); } },sql,objs); }
1 2 #### 查询操作 首先定义我们的数据库表和字段注解,数据库中对应的名字由注解给出
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Xtable { String value(); } enum Genor { Auto } @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) public @interface Xid { String value(); //主键生成方式 Genor Gen(); }
List find(String sql, Class poclazz, Object… objs) { Object result = run(new IDao() { @Override public Object onExec(PreparedStatement pstm) throws SQLException { // 执行查询,返回结果集 rs = pstm.executeQuery(); //实例化一个集合对象,封装实体对象 List pos = new ArrayList(); try { //遍历结果集 while(rs.next()) { // 实例化每个查询到的结果 Object po = poclazz.newInstance(); Field[] fs = poclazz.getDeclaredFields(); // 使用查询结果填充实体类的属性 //通过实体类中的属性,属性与物理表中的列一致规则,得到对应列是值 for(Field f : fs) { // 列名 String fname = f.getName(); // 列值 Object fval = rs.getObject(fname); // 使用 set 方法 Method setMethod = poclazz.getDeclaredMethod( “set” + StringUtil.toUpper(fname), f.getType()); /** * 注意有些类型还未写全——比如 Double,有用到时记得补上 */ if(fval instanceof Boolean) { setMethod.invoke(po, (Boolean) fval); } else if(fval instanceof Integer) { setMethod.invoke(po, new Integer(fval.toString())); } else if(fval instanceof Long) { setMethod.invoke(po, new Long(fval.toString())); } else if(fval instanceof String) { setMethod.invoke(po, fval.toString()); } else { setMethod.invoke(po, fval.toString()); } } // 将构造的对象添加到集合中 pos.add(po); } } catch (Exception e) { e.printStackTrace(); } // 返回查询到的结果 return pos; } }, sql, objs); // 转换结果类型返回 return (List) result; }
Object findById(final Class poclazz, Serializable id) { // 1.生成 sql 语句 String sql = “select * from “; // 反射注解得到表的名称 Table xtable = (Table) poclazz.getAnnotation(Table.class); sql = sql + xtable.value(); // 2.获取主键名称 Field[] fs = poclazz.getDeclaredFields(); for (Field field : fs) { Id xid = field.getAnnotation(Id.class); if(xid!=null){ String sid = xid.value();//主键名称 sql = sql + “ where “ + sid + “=?”; break; } } // 3.执行操作,返回查询到的对象 List result = find(sql, poclazz, id); return result.get(0); }
List findAll(final Class poclazz) { // 1.生成 sql 语句 String sql = “select * from “; Table xtable = (Table) poclazz.getAnnotation(Table.class); sql += xtable.value(); return (List)(List)find(sql, poclazz); }
1 2 3 4 5 6 7 8 ## 动态代理实现方案 ### 代理实现方法 1. 静态代理 在编译时确定的称为静态代理(或者说委托,因为代理类的实际实现是调用了目标对象的相应方法),静态代理的实现参考代理模式。 1. 动态代理 Java中可以通过Proxy.newProxyInstance来创建代理对象,代理类和被代理类需要同一套接口,而且还需要实现一个InvocationHandler类作为中介。 ### 使用Jdk Proxy实现动态代理 #### InvocationHandler和newProxyInstance()的使用
public interface InvocationHandler { // proxy 被代理对象,method 该对象的方法,args 该方法的参数 Object invoke(Object proxy, Method method, Object[] args); }
1 当调用目标对象的方法时,该调用会转发到invoke方法中,因此可以在invoke方法中定义统一处理逻辑,在方法层面上来说也可以当成中介者。
// loader 代理类的类加载器,interfaces 代理类实现的接口列表,h 调用处理器 InvocationHandler public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
1 2 3 4 5 6 7 实例化代理类时需要使用Proxy.newProxyInstance静态方法创建,并不是直接实例化一个代理对象,它会根据代理接口的方法列表来反射定义方法体,对代理对象的方法调用都会被转发到中介对象的invoke方法。 newProxyInstance用于创建代理类实例,其执行过程大致如下: 1. getProxyClass,查看缓存中是否已经使用loader创建了和interfaces相关的代理类对象,Proxy类中使用一个静态Map对象保存类装载器对象到其对应代理对象的缓存 1. 调用ProxyGenerator的generateProxyClass产生代理类的**字节码** 1. 使用反射获取构造函数并生成代理类实例 #### 原理 1. 生成类的定义文件(字节码文件)
Proxy.newProxyInstance(loader, interfaces, this);
1 2 3 4 5 6 7 8 9 10 newProxyInstance完成了什么工作? 在运行时,jdk为根据目标接口(Service)信息,生成代理类的字节码文件,并通过制定的Classloader来完成字节码文件的加载和解析,并返回一个代理类的Class。最后通过该Class的构造函数完成代理类实例的创建。 java.lang.reflect.Proxy#getProxyClass0 -> proxyClassCache.get 第一次调用缓存不命中,调ProxyClassFactory生成实例 -> ProxyClassFactory.apply -> ProxyGenerator.generateProxyClass 生成字节码文件 -> java.lang.reflect.Proxy#defineClass0 加载字节码文件并解析为Class对象 1. 调用传递 从生成的代理类Class可以看出,在代理类内部完成了对InvocationHandler的绑定。那么调用就可以顺利成章的传递到相应的InvocationHandler中。 程序运行完后会在指定目录下生成Class文件
public class ReflectTests { public interface Service { } public static class ServiceImpl implements Service { } public static void main(String[] args) throws IOException { Service service = new ServiceImpl(); byte[] classFile = ProxyGenerator.generateProxyClass(“com.sun.proxy.$Proxy.1”, service.getClass().getInterfaces()); FileOutputStream out = new FileOutputStream(“/tmp/com.sun.proxy.$Proxy.1.class”); out.write(classFile); out.flush(); } }
1 对字节码文件反编译可以看一下生成的是一个什么样的Class:
public final class 1 extends Proxy implements Service { private static Method m1; private static Method m2; private static Method m0; public _/* $FF was: 1*/(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 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); } } static { try { m1 = Class.forName(“java.lang.Object”).getMethod(“equals”, Class.forName(“java.lang.Object”)); m2 = Class.forName(“java.lang.Object”).getMethod(“toString”); m0 = Class.forName(“java.lang.Object”).getMethod(“hashCode”); } catch (NoSuchMethodException var2) { throw new NoSuchMethodError(var2.getMessage()); } catch (ClassNotFoundException var3) { throw new NoClassDefFoundError(var3.getMessage()); } } }
1 2 3 4 5 6 7 8 9 1.代理类的名称为1; 2.代理类继承了Proxy类,并实现了Service(自定义)接口; 3.根据父类信息,代理类定义了四个成员变量,都是Method类型,其中m3为Service中的service方法; 4.在代理类内部依赖了InvocationHandler,也就是完成了代理类跟目标类的绑定; 5.在调用阶段直接调用了InvocationHandler的invoke方法; 6.在代理类内部依赖了InvocationHandler; 7.InvocationHandler后面的内容可以参考上面的解释。 #### JdkProxy例子 下面的代码使用Jdk进行动态代理:
// 实现一个被委托类,实现实际的处理逻辑 class Target implements Inter { public void print() { System.out.println(“target”); } } // 实现一个委托类,它的方法不会被实际调用 class Invoker implements Inter { public void print() { System.out.println(“invoker”); } } class DynamicProxy implements InvocationHandler { private Object obj; // 委托对象,即 invoker 对象 public DynamicProxy(Object o) { this.obj = o; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(“before”); Object result = method.invoke(obj, args); System.out.println(“after”); return result; } } class JdkProxyTest { public static void main(String[] args) { // 创建中介对象 DynamicProxy proxy = new DynamicProxy(new Target()); // 生成$Proxy0.class 文件,即代理类文件 System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”); Inter invoker = (Inter) Proxy.newProxyInstance( Inter.class.getClassLoader(), new Class[]{Inter.class}, proxy); // 通过代理对象调用被委托对象的方法 invoker.print(); } }
/*
实现 jdk 动态代理模式的代理对象
功能主要有两个:
被代理对象方法拦截,方法的执行在当前代理对象中完成;
生成代理对象,通过当前的代理方法返回一个代理对象,代码中
运行的是代理对象中的方法; / public class MyProxy implements InvocationHandler { // 1.被代理对象(当前对象实现指定接口) Object target; public MyProxy(Object t) { this.target = t; } // 2.返回代理后的对象 public Object getInstances() { //jdk 动态代理提供 Proxy 类 / newProxyInstance方法参数 * ① 当前代理类的类加载器 * ② 被代理类的接口的集合 * ③ 当前代理对象,作用当前外部执行代理对象中的方法, * 反向调用当前代理对象中 invoke 方法,完成执行方法的拦截 * / return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), this); } / *
功能:拦截被代理对象的中所有的执行方法
@param proxy 拦截方法的代理对象(向外输出)
@param method 所拦截的方法
@param args 拦截方法执行时的实际参数值
@return 方法执行后返回的结果 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 方法执行前的代码 Object v = null; Date d = new Date(); SimpleDateFormat sf = new SimpleDateFormat(“hh:mm:ss SSS”); String s = sf.format(d); System.out.println(s+”开始执行:” + method.getName() + “,传递的参数:” + Arrays.toString(args)); // 执行被拦截的方法 v = method.invoke(target, args); // 方法执行后的代码 d = new Date(); s = sf.format(d); System.out.println(s + “, “ + method.getName() + “方法执行结束”); return v; } }
Hello h = (Hello) new MyProxy(new HelloWorld()).getInstances(); h.doPrint();1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 ### 使用CGLIB实现动态代理 1. 动态生成一个要代理类的子类(使用字节码处理框架ASM,来转换字节码并生成新的类),子类重写要代理的类的所有不是final的方法 1. 在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑 下面的代码是使用CGLIB实现动态代理的示例: ``` // 需要CGlib动态代理的目标类 class TargetObject { public String method1(String paramName) { return paramName; } public int method2(int count) { return count; } public int method3(int count) { return count; } @Override public String toString() { return "TargetObject []" + getClass(); } } // 目标对象拦截器,实现MethodInterceptor class TargetInterceptor implements MethodInterceptor { /** * 重写方法拦截在方法前和方法后加入业务 * * @param obj obj为目标对象 * @param method method为目标方法 * @param params params 为参数, * @param proxy CGlib方法代理对象 */ @Override public Object intercept(Object obj, Method method, Object[] params, MethodProxy proxy) throws Throwable { System.out.println("调用前: " + method); Object result = proxy.invokeSuper(obj, params); System.out.println("调用result: " + result); System.out.println("调用后"); return result; } } public class CGLIBTest { public static void main(String[] args) { // 这里Enhancer类是CGLib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展 Enhancer enhancer = new Enhancer(); // 将被代理类TargetObject设置成父类 enhancer.setSuperclass(TargetObject.class); // 设置拦截器TargetInterceptor enhancer.setCallback(new TargetInterceptor()); // 动态生成代理类 TargetObject targetObject2 = (TargetObject) enhancer.create(); System.out.println(targetObject2); // 直接输出其实调用了toString()方法,也会触发intercept方法 targetObject2.method1("hello"); targetObject2.method2(100); targetObject2.method3(200); } } ``` #### 比较JdkProxy和CGLIB 1. 性能 CGLIB的性能比JDK自带的动态代理要高。 1. 功能 JDK动态代理只能接口代理,如果目标类没有实现接口是不能创建代理的,而CGLIB支持类代理。 1. 代理的实现原理 JdkProxy是通过实现接口来进行代理的,如果目标类没有实现接口或者想要拦截的方法不在接口声明中则不能被代理。CGLIB是通过继承子类来进行动态代理的,因此,CGLIB对final方法无法进行代理(final方法不能被继承)。 1. 多级代理 JdkProxy中可以很方便地实现多级代理:
DynamicProxy proxy = new DynamicProxy(new Target()); Inter invoker = (Inter) Proxy.newProxyInstance( Inter.class.getClassLoader(), new Class[]{Inter.class}, proxy); DynamicProxy proxy1 = new DynamicProxy(invoker); Inter invoker1 = (Inter) Proxy.newProxyInstance( Inter.class.getClassLoader(), new Class[]{Inter.class}, proxy1); invoker1.print();1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 但是在使用Spring框架的过程中发现多级代理后注册到Bean容器内会出现问题,类型会变成Proxy类,可以改为CGLIB实现,但是CGLIB在实现多级代理时必须在两个代理之间加入一层中间代理: ``` CglibMultiInterceptor proxy = new CglibMultiInterceptor(); Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(TargetObject.class); enhancer.setCallback(proxy); TargetObject target = (TargetObject) enhancer.create(); proxy.setTargetObj(new TargetObject()); // 第二级 CglibMultiInterceptor proxy1 = new CglibMultiInterceptor(); Enhancer enhancer1 = new Enhancer(); enhancer1.setSuperclass(TargetObject.class); enhancer1.setCallback(proxy1); proxy1.setTargetObj(target); TargetObject target1 = (TargetObject) enhancer1.create(); target1.method1("123"); ``` 其中使用到了一个中间代理CglibMultiInterceptor,如下所示: ``` public class CglibMultiInterceptor implements MethodInterceptor { private Object targetObj; private MethodInterceptor nextInterceptor; @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { // 出于需求考虑可以排除Object类中的toString等方法 // boolean belongs2Object = Object.class.getName().equals(method.getDeclaringClass().getName()); Object res; System.out.println("调用前"); if (nextInterceptor != null) { res = nextInterceptor.intercept(targetObj, method, args, methodProxy); } else { // 使用invokeSuper()会调用f2.invoke(),会调用SuperClass的对应方法 // 使用invoke()会调用f1.invoke(),会先调用Proxy的方法 // res = methodProxy.invokeSuper(targetObj, args); res = methodProxy.invoke(targetObj, args); } System.out.println("调用后"); return res; } public void setTargetObj(Object target) { this.targetObj = target; } public void setNextInterceptor(MethodInterceptor methodInterceptor) { this.nextInterceptor = methodInterceptor; } } ``` #### 应用 Hibernate 底层使用了 javassist(默认)和 cglib,Spring 使用了 cglib 和 jdk 动态代理。 #### 原理 cglib 底层实现依赖于 ASM。 ### javassist 动态代理 #### 示例 public interface BookApi { void sell(); } private static BookApi createJavassistBytecodeDynamicProxy() throws Exception { ClassPool mPool = new ClassPool(true); CtClass mCtc = mPool.makeClass(BookApi.class.getName() + "JavaassistProxy"); mCtc.addInterface(mPool.get(BookApi.class.getName())); mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc)); mCtc.addMethod(CtNewMethod.make( "public void sell() { System.out.print(\"\") ; }", mCtc)); Class<?> pc = mCtc.toClass(); BookApi bytecodeProxy = (BookApi) pc.newInstance(); return bytecodeProxy; } 需要引入 javassist 依赖: <dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.21.0-GA</version> #### 应用 除了Hibernate外,dubbo也使用javassist作为底层动态代理实现框架,其主要考虑因素是性能。 #### 原理 类似ASM,javassist同样要求直接操作字节码文件。 ## QA 1. 反射创建Class实例的三种方式是什么? 方式1:通过 Class 类的静态方法获取 Class 类对象 (推荐) 方式2:因为所有类都继承 Object 类。因而可通过调用 Object 类中的 getClass 方法来获取 方式3:任何数据类型(包括基本数据类型)都有一个“静态”的 class 属性 1. 反射中,Class.forName 和 ClassLoader 区别? Class.forName(className)方法,其实调用的方法是 Class.forName(className,true,classloader); 第 2 个参数表示在 loadClass 后必须初始化。根据 JVM 加载类的知识,我们知道在执行过此方法后,目标对象的 static 块代码已经被执行,static 参数也已经被初始化。 而 ClassLoader.loadClass(className) 方法,实际调用的方法是 ClassLoader.loadClass(className,false); 第 2 个参数表示目标对象被装载后不进行链接,这就意味着不会去执行该类静态块中间的内容。 例如,在 JDBC 使用中,常看到这样的用法,Class.forName("com.mysql.jdbc.Driver") 如果换成了 getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。根据 com.mysql.jdbc.Driver 的源代码,Driver 在 static 块中会注册自己到 java.sql.DriverManager。而 static 块就是在 Class 的初始化中被执行。所以这个地方就只能用 Class.forName(className)。 1. 描述动态代理的几种实现方式,分别说出相应的优缺点 JDK 动态代理和 CGLIB 动态代理。JDK 动态代理是由 Java 内部的反射机制来实现的,CGLIB 动态代理底层则是借助 ASM 框架来实现的,实现了无反射机制进行代理,利用空间来换取了时间,代理效率高于 JDK 。总的来说,**反射机制在生成类的过程中比较高效,而 ASM 在生成类之后的相关执行过程中比较高效**(可以通过将 ASM 生成的类进行缓存,这样解决 ASM 生成类过程低效问题)。还有一点必须注意:**JDK 动态代理的应用前提,必须是目标类基于统一的接口**。如果没有上述前提,JDK 动态代理不能应用。由此可以看出,JDK 动态代理有一定的局限性,**CGLIB 这种第三方类库实现的动态代理应用更加广泛**,且在效率上更有优势。 ## Spring AOP ### 面向切口 注意这样的情形,软件的多个模块都有部分相同的行为,AOP希望使用“横切”的方法来减少这种代码的冗余。 具体的,就是把这些与业务无关的、却被业务模块所共同调用的子程序封装起来。比较典型的应用比如事务和日志。 1. 横切关注点 希望被拦截的方法及拦截后怎么处理,比如调用后台管理接口前需要先做日志; 1. 切面(aspect) 类是对物体的抽象,而切面是横切关注点的抽象,比如所有需要做日志的接口; 1. 连接点(joinpoint) 可以被拦截到的方法,可以是方法、字段或构造器; 1. 切入点(pointcut) 对连接点的拦截的定义,对哪些方法进行拦截,主要用于筛选连接点; 1. 通知(advice) 拦截到连接点之后要执行的代码,通知分为前置、后置、异常、最终、环绕五种; 1. 目标对象 被代理的对象; 1. 织入(weave) 将切面应用到目标对象并导致代理对象创建的过程,也就是引入的具体过程; 1. 引入(introduction) 在不修改代码的前提下,引入可以在运行期为类动态地添加一些方法或字段; ### JavaWeb的Filter JavaWeb中的Filter就是aop的一个例子。 比如定义了一个过滤器LoginFilter用于过滤所有/*的请求,此时来了一个请求/user/login,本来应该触发UserController.login,但是被LoginFilter拦截了,拦截器方法是doFilter。 ``` public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { doBefore(); chain.doFilter(request, response); doAfter(); } ``` 其中,横切关注点是登录验证,切面是所有需要登录验证的接口,连接点是login,切入点是"/*",通知是doBefore和doAfter这些方法,目标对象是UserController,织入过程由web容器完成,即为请求创建FilterChain的过程,引入即为UserController添加登录验证功能的过程。 ### Spring-AOP Spring的核心模块Spring-AOP支持面向切面编程,配置又有配置文件(<aop:config>)和注解(@Aspect)两种方式,下面是配置文件的简单设置
aop:config
<aop:advisor advice-ref=”txAdvice” pointcut=”execution(* com.hgc..*.service.*.*(..))”/>
## 参考
1. [Java 使用JDK的动态代理如何实现多级代理](https://segmentfault.com/q/1010000004605731)
1. [Springboot与shiro整合遇到的坑(SpringBoot使用多级代理时必须强制使用CGLIB)](https://zhuanlan.zhihu.com/p/29161098)
1. [CGLIB原理和功能介绍](http://blog.csdn.net/zghwaicsdn/article/details/50957474)
1. [CGLib动态代理的介绍及用法(单回调、多回调、不处理、固定值、懒加载)](https://blog.csdn.net/difffate/article/details/70552056)
1. [cglib动态代理实现(多级代理)](https://chenfeifeng.github.io/2017/06/12/cglib%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E5%AE%9E%E7%8E%B0/)
0%
Theme NexT works best with JavaScript enabled