内省 动态代理 Lombok

内省

  • 什么是内省?

内省是指计算机程序在运行时(Run time)检查对象(Object)类型的一种能力,通常也可以称作运行时类型检查

不应该将内省反射混淆。相对于内省,反射更进一步,是指计算机程序在运行时(Run time)可以访问、检测和修改本身状态或行为的一种能力。

  1. 内省大多是对属性进行操作
  2. 他跟反射比的话,可以访问更高级的父类
  3. 直接访问的是他的get,set方法进行操作
  • javaBean

JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。

  • 获取BeanInfo

BeanInfo这个类就可以获取到类中的方法和属性。例如类 A 中有属性 name, 那我们可以通过 getName,setName 来得到其值或者设置新的值。通过 getName/setName 来访问 name 属性.

方法

  1. Introspector.getBeanInfo(Class A)---获取BeanInfo
  2. beaninfo.getPropertyDescriptors();
  3. getReadMethod
  4. 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 }

底层实现:

  1. 通过实现 InvocationHandler 接口创建自己的调用处理器;
  2. 通过为 Proxy 类指定 ClassLoader 对象和一组 interface 来创建动态代理类;
  3. 通过反射机制获得动态代理类的构造函数,其唯一参数类型是调用处理器接口类型;
  4. 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数被传入。

 

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文件。
  • 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的实现是在编译期就完成

通过注解的方法可以直接调用

 

posted @ 2022-02-21 21:39  Nickeyhu  阅读(244)  评论(0编辑  收藏  举报