JavaEE程序设计【五】

2、原理了解:动态代理(JDK实现)

深入AOP开发的基石-java动态代理

动态代理利用了JDK API动态地在内存中构建代理对象,从而实现对目标对象的代理功能。动态代理又被称为JDK代理或接口代理。

静态代理与动态代理的区别主要在:

  • 静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
  • 动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中

Java字节码文件,不一定在本地硬盘上,可以是网络资源,可以是内存中的资源,只要能够获取到就行。而JDK动态生成的代理,命名为$Proxy0或者$Proxy1 即$Proxy+num。不会显示在我们的硬盘文件中,而是在内存中动态生成,我们在开发文件夹下找不到。

命名原码:

类生成的源头:byte数组内 就是代理类字节码文件了。再配合类加载器等其他内容通过方法生成类。但是如何生成字节码文件的方法无法继续向下查询了。因为JDK1.6开始,相关原码不再有了。想查看可以去下载openJDK

为了便于深入理解,经过使用特定方法,将其保存到硬盘后查看生成的代理:

h是InvocationHandler,来自父类Proxy构造生成

m3是反射生成的方法

特点:

  • 动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。

JDK中生成代理对象主要涉及的类有

1
2
3
4
5
static Object    newProxyInstance(ClassLoader loader,  //指定当前目标对象使用类加载器
Class<?>[] interfaces, //目标对象实现的接口的类型
InvocationHandler h //事件处理器
)
//返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。

关于为何需要类加载器,因为动态生成字节码,需要相应的加载器

Java反射

什么是反射

反射(Reflection)是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。

通过反射机制,可以在运行时访问 Java 对象的属性,方法,构造方法等。

类加载的完整过程如下:

  1. 在编译时,Java 编译器编译好 .java 文件之后,在磁盘中产生 .class 文件。.class 文件是二进制文件,内容是只有 JVM 能够识别的机器码。
  2. JVM 中的类加载器读取字节码文件,取出二进制数据,加载到内存中,解析.class 文件内的信息。类加载器会根据类的全限定名来获取此类的二进制字节流;然后,将字节流所代表的静态存储结构转化为方法区的运行时数据结构;接着,在内存中生成代表这个类的 java.lang.Class 对象。
  3. 加载结束后,JVM 开始进行连接阶段(包含验证、准备、初始化)。经过这一系列操作,类的变量会被初始化。
Class 对象

要想使用反射,首先需要获得待操作的类所对应的 Class 对象。Java 中,无论生成某个类的多少个对象,这些对象都会对应于同一个 Class 对象。这个 Class 对象是由 JVM 生成的,通过它能够获悉整个类的结构。所以,java.lang.Class 可以视为所有反射 API 的入口点。

反射的本质就是:在运行时,把 Java 类中的各种成分映射成一个个的 Java 对象。

举例来说,假如定义了以下代码:

1
2
> User user = new User(); 
>

步骤说明:

  1. JVM 加载方法的时候,遇到 new User(),JVM 会根据 User 的全限定名去加载 User.class 。
  2. JVM 会去本地磁盘查找 User.class 文件并加载 JVM 内存中。
  3. JVM 通过调用类加载器自动创建这个类对应的 Class 对象,并且存储在 JVM 的方法区。
  4. 注意:一个类有且只有一个 Class 对象。其它都是以它为根基创建的
1
2
3
4
 Object    invoke(Object proxy, //被代理后的对象
Method method, //使用的方法
Object[] args) //方法的参数
// 在代理实例上处理方法调用并返回结果。

​ 例:保存用户功能的动态代理实现

  • 接口类: IUserDao
1
2
3
4
5
package com.proxy;

public interface IUserDao {
public void save();
}
  • 目标对象:UserDao
1
2
3
4
5
6
7
8
9
package com.proxy;

public class UserDao implements IUserDao{

@Override
public void save() {
System.out.println("保存数据");
}
}
  • 动态代理对象:UserProxyFactory
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
package com.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyFactory {

private Object target;// 维护一个目标对象

public ProxyFactory(Object target) {
this.target = target;
}

// 为目标对象生成代理对象
public Object getProxyInstance() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开启事务");

// 执行目标对象方法
Object returnValue = method.invoke(target, args);

System.out.println("提交事务");
return null;
}
});
}
}
  • 测试类:TestProxy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.proxy;

import org.junit.Test;

public class TestProxy {

@Test
public void testDynamicProxy (){
IUserDao target = new UserDao();
System.out.println(target.getClass()); //输出目标对象信息
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass()); //输出代理对象信息
proxy.save(); //执行代理方法
}
}
  • 输出结果
1
2
3
4
5
class com.proxy.UserDao
class com.sun.proxy.$Proxy4
开启事务
保存数据
提交事务
  • 1、创建接口

  • 2、实现接口来创建目标类

  • 3、创建切面类

  • 4、实现InvocationHandler接口

    • import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      import java.lang.reflect.Proxy;

    • (1)声明目标类作为私有成员,并且实现构造方法

      1
      2
      3
      4
      5
      >     	private IUserDao userDao;
      > public JdkProxy(IUserDao userDao) {
      > this.userDao = userDao;
      > }
      >
  • (2)实现其中的invoke方法(调用切面类中的方法增强)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    >     	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    > // TODO Auto-generated method stub
    > // 声明切面
    > MyAspect myAspect = new MyAspect();
    > // 前增强
    > myAspect.check_Permissions();
    > // 在目标类上调用方法,并传入参数
    > Object obj = method.invoke(userDao, args);
    > // 后增强
    > myAspect.log();
    > return obj;
    > }
    >
  • (3)创建获取增强代理实例的方法[也可以在使用中获取但是麻烦。]

    1
    2
    3
    4
    5
    6
    7
    >     	public Object createProxy() {
    > // 参数1.类加载器
    > // 参数2.被代理对象实现的所有接口
    > // 参数3.使用代理类,进行增强,返回的是代理后的对象
    > return Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), userDao.getClass().getInterfaces(), this);
    > }
    >
<font size=4 color=red>**PS:可以调整构造函数的功能到createProxy中,减少工作量**</font>
  • 5、使用

    • (1)、获取目标类实例对象
    • (2)、获取实现增强接口的代理实例对象
    • (3)、通过代理实例对象的方法获取增强后目标类的对象

目标对象——>代理获取目标对象——>产生增强目标对象——>投入使用

4、原理了解:动态代理(CGLib实现)

源码详解系列(一)——cglib动态代理的使用和分析

Cglib及其基本使用

cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。

cglib特点

  • JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。
    如果想代理没有实现接口的类,就可以使用CGLIB实现。
  • CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。
    它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
  • CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。
    不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。

cglib与动态代理最大的区别就是

  • 使用动态代理的对象必须实现一个或多个接口
  • 使用cglib代理的对象则无需实现接口,达到代理类无侵入。
  • CGLIB动态代理能够实现基于方法级别的拦截处理
  • CGLib动态代理执行方法,不是使用反射执行 Method.invoke()

使用cglib需要引入cglib的jar包,如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。

  • cglib的Maven坐标
1
2
3
4
5
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>

主要步骤

1、创建目标类

2、创建切面类

3、实现MethodInterceptor接口,创建代理类

  • import java.lang.reflect.Method;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
  • (1)实现intercept方法,调用切面类方法进行增强

4、通过使用Enhancer对象获取增强代理对象

  • 创建Enhancer对象:Enhancer是cglib代理的对外接口,以下操作都是调用这个类的方法
  • setSuperclass(Class superclass):代理谁?
  • setCallback(final Callback callback):怎么代理?(我们需要实现Callback的子接口MethodInterceptor重写其中的intercept方法,该方法定义了代理规则)
  • create():获得代理类
  • 使用代理类
  • 目标对象:UserDao
1
2
3
4
5
6
7
8
package com.cglib;

public class UserDao{

public void save() {
System.out.println("保存数据");
}
}
  • 代理对象:ProxyFactory
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
package com.cglib;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class ProxyFactory implements MethodInterceptor{

private Object target;//维护一个目标对象
public ProxyFactory(Object target) {
this.target = target;
}

//为目标对象生成代理对象
public Object getProxyInstance() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(target.getClass());
//设置回调函数
en.setCallback(this);
//创建子类对象代理
return en.create();
}

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("开启事务");
// 执行目标对象的方法
Object returnValue = method.invoke(target, args);
System.out.println("关闭事务");
return null;
}
}

函数参数信息

  • Object表示要进行增强的对象
  • Method表示拦截的方法
  • Object[]数组表示参数列表,基本数据类型需要传入其包装类型,如int–>Integer、long-Long、double–>Double
  • MethodProxy表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
  • 测试类:TestProxy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.cglib;

import org.junit.Test;

public class TestProxy {

@Test
public void testCglibProxy(){
//目标对象
UserDao target = new UserDao();
System.out.println(target.getClass());
//代理对象
UserDao proxy = (UserDao) new ProxyFactory(target).getProxyInstance();
System.out.println(proxy.getClass());
//执行代理对象方法
proxy.save();
}
}
  • 输出结果
1
2
3
4
5
class com.cglib.UserDao
class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6
开启事务
保存数据
关闭事务

关于为什么要用.getClass:

java中getClass()方法简介

CGLIB动态代理

getClass()方法的作用

  • 获得了Person这个(类)Class,进而通过返回的Class对象获取Person的相关信息,比如:获取Person的构造方法,方法,属性有哪些等等信息。

创建对象: Person p = new Person(1,”刘德华”);

返回Class类型的对象: Class c = p.getClass();

此时c是Class类型,Class提供了一系列的方法来获取类的相关信息,可以通过对象c来获取Person的信

息。比如,获取Person这个类的类名:

String perName = c.getName();

本质还是反射

例2:

  1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
  2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// CGLIB动态代理
// 1. 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
class MyMethodInterceptor implements MethodInterceptor{
...
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
logger.info("You said: " + Arrays.toString(args));
return proxy.invokeSuper(obj, args);
}
}
// 2. 然后在需要使用HelloConcrete的时候,通过CGLIB动态代理获取代理对象。
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(HelloConcrete.class);
enhancer.setCallback(new MyMethodInterceptor());

HelloConcrete hello = (HelloConcrete)enhancer.create();
System.out.println(hello.sayHello("I love you!"));

运行上述代码输出结果:

1
2
日志信息: You said: [I love you!]
HelloConcrete: I love you!

上述代码中,我们通过CGLIB的Enhancer来指定要代理的目标对象、实际处理代理逻辑的对象,最终通过调用create()方法得到代理对象,对这个对象所有非final方法的调用都会转发给MethodInterceptor.intercept()方法,在intercept()方法里我们可以加入任何逻辑,比如修改方法参数,加入日志功能、安全检查功能等;通过调用MethodProxy.invokeSuper()方法,我们将调用转发给原始对象,具体到本例,就是HelloConcrete的具体方法。

CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很类似,都是方法调用的中转站。

注意:对于从Object中继承的方法,CGLIB代理也会进行代理,如hashCode()equals()toString()等,但是getClass()wait()等方法不会,因为它是final方法,CGLIB无法代理。

如果对CGLIB代理之后的对象类型进行深挖,可以看到如下信息:

1
2
3
4
5
6
# HelloConcrete代理对象的类型信息
class=class cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52
superClass=class lh.HelloConcrete
interfaces:
interface net.sf.cglib.proxy.Factory
invocationHandler=not java proxy class

我们看到使用CGLIB代理之后的对象类型是cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52,这是CGLIB动态生成的类型;父类是HelloConcrete

印证了CGLIB是通过继承实现代理;同时实现了net.sf.cglib.proxy.Factory接口,这个接口是CGLIB自己加入的,包含一些工具方法。

注意,既然是继承就不得不考虑final的问题。我们知道final类型不能有子类,所以CGLIB不能代理final类型,遇到这种情况会抛出类似如下异常:

1
2
> java.lang.IllegalArgumentException: Cannot subclass final class cglib.HelloConcrete
>

同样的,final方法是不能重载的,所以也不能通过CGLIB代理,遇到这种情况不会抛异常,而是会跳过final方法只代理其他方法。

如果你还对代理类cglib.HelloConcrete$$EnhancerByCGLIB$$e3734e52具体实现感兴趣,它大致长这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// CGLIB代理类具体实现
public class HelloConcrete$$EnhancerByCGLIB$$e3734e52
extends HelloConcrete
implements Factory
{
...
private MethodInterceptor CGLIB$CALLBACK_0; // ~~
...

public final String sayHello(String paramString)
{
...
MethodInterceptor tmp17_14 = CGLIB$CALLBACK_0;
if (tmp17_14 != null) {
// 将请求转发给MethodInterceptor.intercept()方法。
return (String)tmp17_14.intercept(this,
CGLIB$sayHello$0$Method,
new Object[] { paramString },
CGLIB$sayHello$0$Proxy);
}
return super.sayHello(paramString);
}
...
}

上述代码我们看到,当调用代理对象的sayHello()方法时,首先会尝试转发给MethodInterceptor.intercept()方法,如果没有MethodInterceptor就执行父类的sayHello()。这些逻辑没什么复杂之处,但是他们是在运行时动态产生的,无需我们手动编写。

5、JDK和CGLib动态代理对比

JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑贬值在一起。

JDK动态代理的话,他有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例哪?答案就是CGLib。

CGLib采用底层的字节码技术,全称是:Code Generation Library,CGLib可以为一个类创建一个子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。

1、JDK动态代理具体实现原理:

  • 通过实现InvocationHandlet接口创建自己的调用处理器;
  • 通过为Proxy类指定ClassLoader对象和一组interface来创建动态代理;
  • 通过反射机制获取动态代理类的构造函数,其唯一参数类型就是调用处理器接口类型;
  • 通过构造函数创建动态代理类实例,构造时调用处理器对象作为参数参入;

JDK动态代理是面向接口的代理模式,如果被代理目标没有接口那么Spring也无能为力,Spring通过Java的反射机制生产被代理接口的新的匿名实现类,重写了其中AOP的增强方法。

2、CGLib动态代理:

CGLib是一个强大、高性能的Code生产类库,可以实现运行期动态扩展java类,Spring在运行期间通过 CGlib继承要被动态代理的类,重写父类的方法,实现AOP面向切面编程呢。

3、两者对比:

  • JDK动态代理是面向接口的。
  • CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么抱歉会失败)。

4、使用注意:

  • 如果要被代理的对象是个实现类,那么Spring会使用JDK动态代理来完成操作(Spirng默认采用JDK动态代理实现机制);
  • 如果要被代理的对象不是个实现类那么,Spring会强制使用CGLib来实现动态代理。
-----------------------本文结束 感谢阅读-----------------------
坚持原创技术分享,您的支持将鼓励我继续创作!恰饭^.^~