Spring AOP源码分析(二)JDK动态代理和CGLIB介绍

简介:
本篇是介绍java实现代理对象的两种方法,JDK动态代理和CGLIB。 
JDK动态代理:针对你所调用的方法是接口所定义的方法。动态的创建一个类,通过实现目标类的接口来实现代理。 
CGLIB:没有限制。通过继承目标类来创建代理类,实现代理。 
下面看案例: 

案例一,JDK动态代理: 
Person和Animals都实现了Say接口sayHello方法。现在就需要对他们的sayHello方法进行拦截。 
Say接口如下:
 
?
1
2
3
4
public interface Say {
 
     public void sayHello();
}

Person类如下: 
?
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
package com.lg.aop.base;
 
public class Person implements Say{
     
     private String name;
     
     public Person() {
         super ();
     }
 
     public Person(String name) {
         super ();
         this .name = name;
     }
 
     @Override
     public void sayHello() {
         System.out.println( "My name is " +name+ "!" );
         throw new RuntimeException();
     }
 
     public String getName() {
         return name;
     }
 
     public void setName(String name) {
         this .name = name;
     }
 
     
}

Animal类如下: 
?
1
2
3
4
5
6
7
8
public class Animals implements Say{
 
     @Override
     public void sayHello() {
         System.out.println( "I am a animal" );
     }
 
}

使用JDK动态代理来创建代理对象的工具类JDKDynamicProxy如下: 
?
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
package com.lg.aop.base;
 
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
 
public class JDKDynamicProxy {
     
     public static  Object createProxy( final Object target){
         return Proxy.newProxyInstance(ProxyTest. class .getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler(){
 
             @Override
             public Object invoke(Object proxy, Method method, Object[] args)
                     throws Throwable {
                 if (method.getName().equals( "sayHello" )){
                     doBefore();
                     try {
                         method.invoke(target, args);
                     } catch (Exception e) {
                         doThrowing();
                     }
                     doAfter();
                 }
                 return null ;
             }
 
         });
     }
     
     private static void doThrowing() {
         System.out.println( "AOP say throw a exception" );
     }
     
     private static void doBefore() {
         System.out.println( "AOP before say" );
     }
 
     private static void doAfter() {
         System.out.println( "AOP after say" );
     }
 
}

JDK动态代理就是通过Proxy.newProxyInstance来创建代理对象的: 
第一个参数是ClassLoader:因为此次代理会创建一个Say接口的实现类,需要将这个类加载到jvm中,所以用到了ClassLoader。 
第二个参数是代理类要实现的所有接口:当你调用这些接口的方法时都会进行拦截。 
第三个参数是InvocationHandler,每次调用代理对象的方法时,都会先执行InvocationHandler的invoke方法,在该方法中实现我们的拦截逻辑。 
在本案例中,InvocationHandler的invoke方法中,我们的拦截逻辑是这样的,当调用sayHello方法时,进行doBefore、doAfter、doThrowing等拦截。 

测试类如下:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.lg.aop.base;
 
 
public class ProxyTest {
 
     public static void main(String[] args){
         Say say1= new Person( "lg" );
         say1=(Say)JDKDynamicProxy.createProxy(say1);
         say1.sayHello();
         
         System.out.println( "-------------------------------" );
         
         Say say2= new Animals();
         say2=(Say) JDKDynamicProxy.createProxy(say2);
         say2.sayHello();
     }
}

测试结果如下: 
?
1
2
3
4
5
6
7
8
AOP before say
My name is lg!
AOP say throw a exception
AOP after say
-------------------------------
AOP before say
I am a animal
AOP after say

我们可以看到实现了相应的拦截。 
这里说明下几个概念, 
(1)Proxy.newProxyInstance的返回结果和目标对象Person实现了同样的接口,但他们之间不能相互转化。即say1=(Person)JDKDynamicProxy.createProxy(say1);是错误的。 
(2)InvocationHandler的invoke(Object proxy, Method method, Object[] args)方法中的Object proxy是Proxy.newProxyInstance的返回的代理对象,不是目标对象Person,因此希望执行原目标对象的sayHello方法时,method.invoke(target, args);所传对象是原目标对象,而不是代理对象proxy。 

这就是JDK动态代理的原理,目前这些拦截都是硬编码写死的,如果我们继续进一步改造,便也可以实现更加灵活的代理,有兴趣的可以实现自己的AOP。 

案例二,CGLIB代理: 
首先在pom文件中引入cglib库。如下:
 
?
1
2
3
4
5
<dependency>
         <groupId>cglib</groupId>
         <artifactId>cglib</artifactId>
         <version> 3.1 </version>
     </dependency>

还是针对同样的Person、Animals、Say接口。只是这次创建代理对象的方式变了,如下: 
?
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
package com.lg.aop.base;
 
import java.lang.reflect.Method;
 
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.InvocationHandler;
 
public class CglibProxy {
 
     @SuppressWarnings ( "unchecked" )
     public static <T> T createProxy( final T t){
         Enhancer enhancer= new Enhancer();
         enhancer.setClassLoader(CglibProxy. class .getClassLoader());
         enhancer.setSuperclass(t.getClass());
         enhancer.setCallback( new InvocationHandler(){
 
             @Override
             public Object invoke(Object proxy, Method method, Object[] args)
                     throws Throwable {
                 Object ret= null ;
                 if (method.getName().equals( "sayHello" )){
                     doBefore();
                     try {
                         ret=method.invoke(t, args);
                     } catch (Exception e) {
                         doThrowing();
                     }
                     doAfter();
                 }
                 return ret;
             }
             
         });
         return (T)enhancer.create();
     }
     
     private static void doThrowing() {
         System.out.println( "AOP say throw a exception" );
     }
     
     private static void doBefore() {
         System.out.println( "AOP before say" );
     }
 
     private static void doAfter() {
         System.out.println( "AOP after say" );
     }
}

使用cglib的Enhancer来创建,创建出的代理对象继承了指定的class。 
(1)enhancer.setClassLoader:也是指定类加载器,将创建出来的新类加载到jvm中。 
(2)enhancer.setSuperclass(t.getClass()):设置目标类作为代理对象的父类。 
(3)enhancer.setCallback:设置一个回调函数,每次调用代理类的方法时,先执行该回调函数。 
然后就是测试:
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class ProxyTest {
 
     public static void main(String[] args){
         cglibTest();
     }
     
     public static void cglibTest(){
         Person p= new Person( "lg" );
         p=CglibProxy.createProxy(p);
         p.sayHello();
         
         System.out.println( "-------------------------------" );
         
         Animals animals= new Animals();
         animals=CglibProxy.createProxy(animals);
         animals.sayHello();
     }
}

p=CglibProxy.createProxy(p):由于创建出来的代理对象就是目标对象的子类,所以不用像JDK动态代理那样创建出的代理类只能是Say类型。 
运行效果如下: 
?
1
2
3
4
5
6
7
8
AOP before say
My name is lg!
AOP say throw a exception
AOP after say
-------------------------------
AOP before say
I am a animal
AOP after say

和JDK动态代理一样的效果,实现了拦截。 

总结一下: 
(1)当你所调用的目标对象的方法是接口所定义的方法时,可以使用JDK动态代理或者Cglib。即当你的目标类虽然实现了接口,但是所调用的方法却不是接口方法时,就无法使用JDK动态代理,因为JDK动态代理的原理就是实现和目标对象同样的接口,因此只能调用那些接口方法。 
(2)Cglib则没有此限制,因为它所创建出来的代理对象就是目标类的子类,因此可以调用目标类的任何方法(除去final方法,final方法不可继承),都会进行拦截。 

所以SpringAOP会优先选择JDK动态代理,当调用方法不是接口方法时,只能选择Cglib了。

相关文章
|
26天前
|
Java
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
这篇文章是Spring5框架的实战教程,深入讲解了AOP的基本概念、如何利用动态代理实现AOP,特别是通过JDK动态代理机制在不修改源代码的情况下为业务逻辑添加新功能,降低代码耦合度,并通过具体代码示例演示了JDK动态代理的实现过程。
Spring5入门到实战------9、AOP基本概念、底层原理、JDK动态代理实现
|
26天前
|
XML Java 数据格式
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
这篇文章是Spring5框架的AOP切面编程教程,通过XML配置方式,详细讲解了如何创建被增强类和增强类,如何在Spring配置文件中定义切入点和切面,以及如何将增强逻辑应用到具体方法上。文章通过具体的代码示例和测试结果,展示了使用XML配置实现AOP的过程,并强调了虽然注解开发更为便捷,但掌握XML配置也是非常重要的。
Spring5入门到实战------11、使用XML方式实现AOP切面编程。具体代码+讲解
|
11天前
|
缓存 Java 开发者
Spring高手之路22——AOP切面类的封装与解析
本篇文章深入解析了Spring AOP的工作机制,包括Advisor和TargetSource的构建与作用。通过详尽的源码分析和实际案例,帮助开发者全面理解AOP的核心技术,提升在实际项目中的应用能力。
9 0
Spring高手之路22——AOP切面类的封装与解析
|
11天前
|
开发者 C# 容器
【独家揭秘】当WPF邂逅DirectX:看这两个技术如何联手打造令人惊艳的高性能图形渲染体验,从环境搭建到代码实践,一步步教你成为图形编程高手
【8月更文挑战第31天】本文通过代码示例详细介绍了如何在WPF应用中集成DirectX以实现高性能图形渲染。首先创建WPF项目并使用SharpDX作为桥梁,然后在XAML中定义承载DirectX内容的容器。接着,通过C#代码初始化DirectX环境,设置渲染逻辑,并在WPF窗口中绘制图形。此方法适用于从简单2D到复杂3D场景的各种图形处理需求,为WPF开发者提供了高性能图形渲染的技术支持和实践指导。
27 0
|
11天前
|
Java Spring XML
掌握面向切面编程的秘密武器:Spring AOP 让你的代码优雅转身,横切关注点再也不是难题!
【8月更文挑战第31天】面向切面编程(AOP)通过切面封装横切关注点,如日志记录、事务管理等,使业务逻辑更清晰。Spring AOP提供强大工具,无需在业务代码中硬编码这些功能。本文将深入探讨Spring AOP的概念、工作原理及实际应用,展示如何通过基于注解的配置创建切面,优化代码结构并提高可维护性。通过示例说明如何定义切面类、通知方法及其应用时机,实现方法调用前后的日志记录,展示AOP在分离关注点和添加新功能方面的优势。
24 0
|
18天前
|
设计模式 Java C++
揭秘!JDK动态代理VS CGLIB:一场关于Java代理界的‘宫心计’,你站哪队?
【8月更文挑战第24天】Java 动态代理是一种设计模式,允许在不改动原类的基础上通过代理类扩展功能。主要实现方式包括 JDK 动态代理和 CGLIB。前者基于接口,利用反射机制在运行时创建代理类;后者采用继承方式并通过字节码技术生成子类实现类的代理。两者在实现机制、性能及适用场景上有明显差异。JDK 动态代理适用于有接口的场景,而 CGLIB 更适合代理未实现接口的类,尽管性能更优但存在一些限制。开发者可根据需求选择合适的代理方式。
50 0
|
20天前
|
缓存 安全 Java
Spring AOP 中两种代理类型的限制
【8月更文挑战第22天】
13 0
|
20天前
|
Java Spring
|
26天前
|
XML Java 数据库
Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
这篇文章是Spring5框架的实战教程,详细解释了AOP的关键术语,包括连接点、切入点、通知、切面,并展示了如何使用AspectJ注解来开发AOP实例,包括切入点表达式的编写、增强方法的配置、代理对象的创建和优先级设置,以及如何通过注解方式实现完全的AOP配置。
|
23天前
|
Java 关系型数据库 MySQL
"解锁Java Web传奇之旅:从JDK1.8到Tomcat,再到MariaDB,一场跨越数据库的冒险安装盛宴,挑战你的技术极限!"
【8月更文挑战第19天】在Linux上搭建Java Web应用环境,需安装JDK 1.8、Tomcat及MariaDB。本指南详述了使用apt-get安装OpenJDK 1.8的方法,并验证其版本。接着下载与解压Tomcat至`/usr/local/`目录,并启动服务。最后,通过apt-get安装MariaDB,设置基本安全配置。完成这些步骤后,即可验证各组件的状态,为部署Java Web应用打下基础。
36 1
下一篇
DDNS