内省 动态代理 Lombok
内省
- 什么是内省?
内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也可以称作运行时类型检查。
不应该将内省和反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改它本身状态或行为的一种能力。
- 内省大多是对属性进行操作
- 他跟反射比的话,可以访问更高级的父类
- 直接访问的是他的get,set方法进行操作
- javaBean
JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。
- 获取BeanInfo
BeanInfo这个类就可以获取到类中的方法和属性。例如类 A 中有属性 name, 那我们可以通过 getName,setName 来得到其值或者设置新的值。通过 getName/setName 来访问 name 属性.
方法
- Introspector.getBeanInfo(Class A)---获取BeanInfo
- beaninfo.getPropertyDescriptors();
- getReadMethod
- getWriteMethod
代码:
1 package day15; 2 3 import java.beans.BeanInfo; 4 import java.beans.Introspector; 5 import java.beans.PropertyDescriptor; 6 import java.io.Serializable; 7 import java.lang.reflect.Method; 8 9 public class Bean_Info { 10 //内省大多是对属性进行操作 11 //他跟反射比的话,可以访问更高级的父类 12 //直接访问的是他的get,set方法进行操作 13 public static void main(String[] args) throws Exception { 14 Class clazz = User.class; 15 Object obj=clazz.newInstance(); 16 BeanInfo bi = Introspector.getBeanInfo(clazz); 17 PropertyDescriptor[] pds = bi.getPropertyDescriptors(); 18 // Method m = pds[0].getWriteMethod(); 19 Method wm = null; 20 for(PropertyDescriptor pd:pds) { 21 //System.out.println(pd.getName()); 22 //class 23 //name 24 //password 25 if("name".equals(pd.getName())) { 26 wm=pd.getWriteMethod(); 27 wm.invoke(obj, "张三"); 28 } 29 if("password".equals(pd.getName())) { 30 wm=pd.getWriteMethod(); 31 wm.invoke(obj, "123456789"); 32 } 33 } 34 System.out.println(obj); 35 } 36 } 37 38 class User implements Serializable{ 39 private String name; 40 private String password; 41 public User(){ 42 super(); 43 } 44 public String getName() { 45 return name; 46 } 47 public void setName(String name) { 48 this.name = name; 49 } 50 public String getPassword() { 51 return password; 52 } 53 public void setPassword(String password) { 54 this.password = password; 55 } 56 @Override 57 public String toString() { 58 return "User [name=" + name + ", password=" + password + "]"; 59 } 60 61 }
动态代理
- JDK动态代理
- CGLIB 代理(第三方)
JDK动态代理代码:
1 package day15; 2 import java.lang.reflect.InvocationHandler; 3 import java.lang.reflect.Method; 4 import java.lang.reflect.Proxy; 5 6 public class JDKProxy { 7 public static void main(String[] args) { 8 //实际业务逻辑实现类 9 TruckBean tb= new TruckBean(); 10 //代理类对象--实现在执行实际原业务前后要做的事情 11 InvocationHandler IH = new TruckProxy(tb); 12 Truck truck = (Truck) Proxy.newProxyInstance(TruckBean.class.getClassLoader(), TruckBean.class.getInterfaces(), IH); 13 truck.transform(); 14 Class c = truck.getClass().getSuperclass(); 15 System.out.println(c);//class java.lang.reflect.Proxy 16 System.out.println(truck.getClass());//class day15.$Proxy0 17 18 } 19 } 20 21 interface Truck { 22 public void transform(); 23 } 24 25 class TruckBean implements Truck { 26 @Override 27 public void transform() { 28 System.out.println("---------TruckBean中的代码"); 29 } 30 } 31 32 class TruckProxy implements InvocationHandler { 33 // 被代理的对象 34 private Object obj; 35 36 TruckProxy(Object obj) { 37 this.obj = obj; 38 } 39 40 @Override 41 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 42 // TODO Auto-generated method stub 43 System.out.println("------动态代理一"); 44 Object result = method.invoke(obj, args); 45 System.out.println("------动态代理二"); 46 return result; 47 } 48 49 }
底层实现:
- 通过实现 InvocationHandler 接口创建自己的调用处理器;
- 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
- 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
- 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。
CGLIB 代理(第三方)
cglib是一个java字节码的生成工具,它动态生成一个被代理类的子类,子类重写被代理的类的所有不是final的方法。在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑。
代码:
1 package day15; 2 import java.lang.reflect.Method; 3 4 import net.sf.cglib.proxy.Enhancer; 5 import net.sf.cglib.proxy.MethodInterceptor; 6 import net.sf.cglib.proxy.MethodProxy; 7 8 public class CGProxy { 9 public static void main(String[] args) { 10 Enhancer enhancer = new Enhancer(); 11 // 继承被代理类 12 enhancer.setSuperclass(HelloServiceImpl.class); 13 // 设置回调 14 enhancer.setCallback(new HelloMethodInterceptor()); 15 // 设置代理类对象 16 HelloServiceImpl helloService = (HelloServiceImpl) enhancer.create(); 17 // 在调用代理类中方法时会被我们实现的方法拦截器进行拦截 18 helloService.sayBey(); 19 } 20 } 21 22 class HelloServiceImpl { 23 public void sayHello() { 24 System.out.println("Hello Zhanghao"); 25 } 26 27 public void sayBey() { 28 System.out.println("Bye Zhanghao"); 29 } 30 } 31 32 class HelloMethodInterceptor implements MethodInterceptor { 33 @Override 34 public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { 35 System.out.println("Before: " + method.getName()); 36 Object object = methodProxy.invokeSuper(o, objects); 37 System.out.println("After: " + method.getName()); 38 return object; 39 } 40 }
总结:
- 静态代理比较容易理解, 需要被代理的类和代理类实现自同一个接口, 然后在代理类中调用真正实现类, 并且静态代理的关系在编译期间就已经确定了。而动态代理的关系是在运行期间确定的。静态代理实现简单,适合于代理类较少且确定的情况,而动态代理则给我们提供了更大的灵活性。
- JDK 动态代理所用到的代理类在程序调用到代理类对象时才由 JVM 真正创建,JVM 根据传进来的业务实现类对象以及方法名 ,动态地创建了一个代理类的 class 文件并被字节码引擎执行,然后通过该代理类对象进行方法调用。我们需要做的,只需指定代理类的预处理、调用后操作即可。
- 静态代理和动态代理都是基于接口实现的, 而对于那些没有提供接口只是提供了实现类的而言, 就只能选择 CGLIB 动态代理了
JDK 动态代理和 CGLIB 动态代理的区别
- JDK 动态代理基于 Java 反射机制实现, 必须要实现了接口的业务类才能用这种方法生成代理对象。
- CGLIB 动态代理基于 ASM 框架通过生成业务类的子类来实现。
- ASM是什么?
- asm是assembly的缩写,是汇编的称号,对于java而言,asm就是字节码级别的编程。 是一个 Java 字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM 可以直接产生二进制 class 文件,也可以在类被加载入 Java 虚拟机之前动态改变类行为。Java class 被存储在严格格式定义的 .class 文件里,这些类文件拥有足够的元数据来解析类中的所有元素:类名称、方法、属性以及 Java 字节码(指令)。ASM 从类文件中读入信息后,能够改变类行为,分析类信息,甚至能够根据用户要求生成新类。说白了ASM是直接通过字节码来修改class文件。
- ASM是什么?
- JDK 动态代理的优势是最小化依赖关系,减少依赖意味着简化开发和维护并且有 JDK 自身支持。还可以平滑进行 JDK 版本升级,代码实现简单。
- 基于 CGLIB 框架的优势是无须实现接口,达到代理类无侵入,我们只需操作我们关系的类,不必为其它相关类增加工作量,性能比较高。
描述代理的几种实现方式? 分别说出优缺点?
- 代理可以分为 "静态代理" 和 "动态代理",动态代理又分为 "JDK 动态代理" 和 "CGLIB 动态代理" 实现。
- 静态代理:代理对象和实际对象都继承了同一个接口,在代理对象中指向的是实际对象的实例,这样对外暴露的是代理对象而真正调用的是 Real Object.
优点:可以很好的保护实际对象的业务逻辑对外暴露,从而提高安全性。
缺点:不同的接口要有不同的代理类实现,会很冗余
- JDK 动态代理:
为了解决静态代理中,生成大量的代理类造成的冗余;
JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现.
- jdk 的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象
- jdk 动态代理之所以只能代理接口是因为代理类本身已经 extends 了 Proxy,而 java 是不允许多重继承的,但是允许实现多个接口
优点:解决了静态代理中冗余的代理实现类问题。
缺点:JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常。
- CGLIB 代理:
由于 JDK 动态代理限制了只能基于接口设计,而对于没有接口的情况,JDK 方式解决不了;
- CGLib 采用了非常底层的字节码技术,其原理是通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用,顺势织入横切逻辑,来完成动态代理的实现。实现方式实现 MethodInterceptor 接口,重写 intercept 方法,通过 Enhancer 类的回调方法来实现。
- CGLib 在创建代理对象时所花费的时间却比 JDK 多得多,所以对于单例的对象,因为无需频繁创建对象,用 CGLib 合适,反之,使用 JDK 方式要更为合适一些。
- 由于 CGLib 由于是采用动态创建子类的方法,对于 final 方法,无法进行代理。
优点:没有接口也能实现动态代理,而且采用字节码增强技术,性能也不错。
缺点:技术实现相对难理解些。
Lombok
作用:
它属于Java的一个热门工具类
使用它可以有效的解决代码工程中那些繁琐又重复的代码
Lombok的实现和反射没有任何关系
反射是程序在运行期的一种自省(introspect)能力Lombok的实现是在编译期就完成了
通过注解的方法可以直接调用