动态代理的实现方式?JDK Proxy和CGLib的区别?
动态代理的常用实现方式是反射。反射机制是指程序在运行期间可以访问、检测和修改其本身状态或行为的一种能力,使用反射我们可以调用任意一个类对象,以及类对象中包含的属性及方法。动态代理是一种行为方式,而反射或 ASM 只是它的一种实现手段而已。
实现方式:
1 2 3 4 | (1).ASM (JAVA字节码操作框架) (2).Javassit (字节码类库,应用在SpringBoot默认连接池HiKariCP中) (3).CGLib (基于ASM实现) (4).JDK Proxy(Spring默认的动态代理,基于反射实现) |
JDK Proxy和CGLib的区别和选择:
(1). jdk动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。只能对实现了接口的类生成代理只能对实现了接口的类生成代理。
(2). cglib:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,对于final类或方法,是无法继承的。
(3). Spring AOP中的动态代理选择:
a. 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
b. 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
c. 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
JDK Proxy 和 CGLib 的使用及代码分析
JDK Proxy 动态代理实现
JDK Proxy 动态代理的实现无需引用第三方类,只需要实现 InvocationHandler 接口,重写 invoke() 方法即可,整个实现代码如下所示:
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 | import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Test { static interface Car { void running(); } static class Bus implements Car { @Override public void running() { System.out.println("The bus is running."); } } static class Taxi implements Car { @Override public void running() { System.out.println("The taxi is running."); } } static class JDKProxy implements InvocationHandler { private Object target; // 代理对象 // 获取到代理对象 public Object getInstance(Object target) { this.target = target; // 取得代理对象 return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), this); } @Override public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { System.out.println("动态代理之前的业务处理."); Object result = method.invoke(target, args);// 执行调用方法(此方法执行前后,可以进行相关业务处理) return result; } } public static void main(String[] args) { // 执行 JDK Proxy JDKProxy jdkProxy = new JDKProxy(); Car carInstance = (Car) jdkProxy.getInstance(new Taxi()); carInstance.running(); } } 程序执行结果: 动态代理之前的业务处理. The taxi is running. |
可以看出 JDK Proxy 实现动态代理的核心是实现 Invocation 接口,我们查看 Invocation 的源码,会发现里面其实只有一个 invoke() 方法,源码如下:
1 2 3 4 | public interface InvocationHandler { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable; } |
这是因为在动态代理中有一个重要的角色也就是代理器,它用于统一管理被代理的对象,显然 InvocationHandler 就是这个代理器,而 invoke() 方法则是触发代理的执行方法,我们通过实现 Invocation 接口来拥有动态代理的能力。
CGLib的实现
首先在pom.xml中引入jar包
1 2 3 4 5 | <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> |
CGLib实现代码如下:
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 | import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class Demo { static class Car { public void running() { System.out.println("The car is running."); } } static class CGLibProxy implements MethodInterceptor { private Object target; // 代理对象 public Object getInstance(Object target) { this.target = target; Enhancer enhancer = new Enhancer(); // 设置父类为实例类 enhancer.setSuperclass(this.target.getClass()); // 回调方法 enhancer.setCallback(this); // 创建代理对象 return enhancer.create(); } @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { System.out.println("方法调用前业务处理."); Object result = methodProxy.invokeSuper(o, objects);// 执行方法调用 return result; } } // 执行 CGLib 的方法调用 public static void main(String[] args) { // 创建 CGLib 代理类 CGLibProxy proxy = new CGLibProxy(); // 初始化代理对象 Car car = (Car) proxy.getInstance(new Car()); // 执行方法 car.running(); } } 以上程序执行结果: 方法调用前业务处理. The car is running. |
可以看出 CGLib 和 JDK Proxy 的实现代码比较类似,都是通过实现代理器的接口,再调用某一个方法完成动态代理的,唯一不同的是,CGLib 在初始化被代理类时,是通过 Enhancer 对象把代理对象设置为被代理类的子类来实现动态代理的。因此被代理类不能被关键字 final 修饰,如果被 final 修饰,再使用 Enhancer 设置父类时会报错,动态代理的构建会失败。
动态代理和静态代理的区别
静态代理其实就是事先写好代理类,可以手工编写也可以使用工具生成,但它的缺点是每个业务类都要对应一个代理类,特别不灵活也不方便。
动态代理的常见使用场景有 RPC 框架的封装、AOP(面向切面编程)的实现、JDBC 的连接等。
Spring 框架中同时使用了两种动态代理 JDK Proxy 和 CGLib,当 Bean 实现了接口时,Spring 就会使用 JDK Proxy,在没有实现接口时就会使用 CGLib,我们也可以在配置中指定强制使用 CGLib,只需要在 Spring 配置中添加
Lombok原理
Lombok属于 Java 的一个热门工具类,使用它可以有效的解决代码工程中那些繁琐又重复的代码,如 Setter、Getter、toString、equals 和 hashCode 等等,这种方法都可以使用 Lombok 注解来完成。
常用注解
1 2 3 4 5 6 7 8 9 10 11 12 | @Data 注解在类上;提供类所有属性的 getting 和 setting 方法,此外还提供了equals、canEqual、hashCode、toString 方法 @Setter :注解在属性上;为属性提供 setting 方法 @Setter :注解在属性上;为属性提供 getting 方法 @Log4j :注解在类上;为类提供一个 属性名为log 的 log4j 日志对象 @NoArgsConstructor :注解在类上;为类提供一个无参的构造方法 @AllArgsConstructor :注解在类上;为类提供一个全参的构造方法 @Cleanup : 可以关闭流 @Builder : 被注解的类加个构造者模式 @Synchronized : 加个同步锁 @SneakyThrows : 等同于try/catch 捕获异常 @NonNull : 如果给参数加个这个注解 参数为null会抛出空指针异常 @Value : 注解和@Data类似,区别在于它会把所有成员变量默认定义为private final修饰,并且不会生成set方法。 |
如果在项目中使用了 Lombok 的 Getter 和 Setter 注解,那么想要在编码阶段成功调用对象的 set 或 get 方法,我们需要在 IDE 中安装 Lombok 插件。
原理:
Lombok 的实现和反射没有任何关系,反射是程序在运行期的一种自省(introspect)能力,而 Lombok 的实现是在编译期就完成了。Lombok是基于Java1.6实现的JSR269:PluggableAnnotationProcessingAPI来实现的,也就是通过编译期自定义注解处理器来实现的,Lombok是基于Java1.6实现的JSR269:PluggableAnnotationProcessingAPI来实现的,也就是通过编译期自定义注解处理器来实现的,它的执行步骤如下:
从流程图中可以看出,在编译期阶段,当 Java 源码被抽象成语法树(AST)之后,Lombok 会根据自己的注解处理器动态修改 AST,增加新的代码(节点),在这一切执行之后就生成了最终的字节码(.class)文件,这就是 Lombok 的执行原理。
待更新…