代理模式是一种设计模式,能够使得在不修改源目标的前提下,额外扩展源目标的功能。即通过访问源目标的代理类,再由代理类去访问源目标。这样一来,要扩展功能,就无需修改源目标的代码了。只需要在代理类上增加就可以了。
使用代理的好处有很多,比如可以在原来的功能上增加功能,或者是控制访问,代理类帮忙去访问我访问不到的类等等。
其中,动态代理有着广泛的应用,也是动态代理让我们使用很多工具时能更简洁地编写代码,比如Mybatis不用写实现类只写接口就可以执行SQL,或者AOP,所以希望能动态代理学好来,以后更加深入理解框架的工作原理。
反射基础
动态代理的基础使反射,所以可以再简单巩固一下反射的内容。
在Java中,反射允许我们在运行时
检查和操作类、接口、构造函数、方法和字段。Java中有一个名为Class的类,它在运行时保存关于对象和类的所有信息。Class的对象可以用于执行反射。创建Class对象有3种方法:
- Class.forName(“ClassName”)
- 类的实例对象.getClass()
- 直接用类名.class的形式
获得了Class对象后,就可以调用很多方法来获取类的信息了:
- getName()
- getModifiers()
- getSuperclass()
- getDeclareMethods()
- getFiled()
- getDeclaredField()
- getDeclaredConstructors()
- ……
很多的内容和方法都定义在 java.lang.reflect
包中。我们学习这些方法后,发现通过代理,我们可以在运行时,得到类的相关信息,又可以在运行时去修改类的方法或是属性等等……
除此之外,还可以运行时创建对象。先使用 Class 对象获取指定的 Constructor 对象,再调用 Constructor 对象的 newInstance()方法来创建 Class 对象对应类的实例,这种方式可以选定构造方法创建实例。
静态代理
代理模式分为静态代理和动态代理。静态代理就是声明一个明确的代理类来访问对象,可以简单地用继承和重写方法来实现,不过就是会存在大量的冗余的代理类,而且代理类和目标类耦合度特别高,不易维护。在静态代理中的目标拓展类太多了,可以使用JDK动态代理,一个代理类可以被很多类拿来使用,增强功能。而且你修改了除代理类外的其他接口或者类,也不影响动态代理的功能。
JDK动态代理(接口代理)
JDK动态代理是通过实现InvocationHandler接口来实现的,也是利用反射机制在运行时动态的指定要代理目标类。 换句话说,动态代理是一种创建java对象的能力,让你不用特定创建某个需要被代理的类的代理类,就能创建代理类对象。
以下根据创建JDK动态代理类的步骤编写了一段代码,仅需要一个AnimalProxy代理类就可以拓展Dog和Cat的eat()功能,当然,她们的拓展内容是一样的。
- 创建Animal接口,定义目标类要完成的功能
- 创建目标类实现接口,Dog、Cat
- 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能:调用目标方法+增强功能
- 使用Proxy类的静态方法,创建代理对象。 并把返回值转为接口类型。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
interface Animal {
void eat();
}
class Dog implements Animal {
@Override
public void eat() {
System.out.println("The dog is eating");
}
}
class Cat implements Animal {
@Override
public void eat() {
System.out.println("The cat is eating");
}
}
// JDK 动态代理
class AnimalProxy 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 Throwable {
System.out.println("调用前");
Object result = method.invoke(target, args); // invoke方法来执行某个对象的方法
System.out.println("调用后");
return result;
}
public static void main(String[] args) {
//new一个JDK动态代理对象出来
AnimalProxy proxy = new AnimalProxy();
Animal dogProxy = (Animal) proxy.getInstance(new Dog());
dogProxy.eat();
Animal catProxy = (Animal) proxy.getInstance(new Cat());
catProxy.eat();
}
}
可以看看代码引入一块,一个最简单的动态代理类需要引入反射包 java.lang.reflect 里面的三个类 : InvocationHandler , Method, Proxy.
- InvocationHandler 接口(调用处理器),表示你的代理要干什么。需要重写一个方法invoke(),是Method类中的一个方法,表示执行方法的调用,也就是 把原来静态代理中代理类要完成的功能,写在这个方法里。 (详细可以参考代码体悟)
- Method类:表示方法的, 确切的说就是目标类中的方法。作用是通过Method可以执行某个目标类的方法。其中有一个invoke()方法。
- Proxy类:核心的对象,创建代理对象。之前静态代理创建对象都是 new 类的构造方法, 现在我们是使用Proxy类的方法,代替new的使用。 其中,它有一个静态方法newProxyInstance()方法,作用是创建代理对象,等同于静态代理的new 代理类的构造方法的作用。
public Object invoke(Object proxy, Method method, Object[] args)
// 方法原型:
// 参数: Object proxy:jdk创建的代理对象,无需赋值。
// Method method:目标类中的方法,jdk提供method对象的
// Object[] args:目标类中方法的参数, jdk提供的。
//如果不传原有的method,只传两个参数就会陷入一个死循环,因为你看你后
//面还要在处理附加功能中执行原有bean的方法,以完成代理的职责。
method.invoke(目标对象,方法的参数)
//很多类都继承这个动态代理类,可能都有同名方法,所以就要根据传入的对象 //和参数来调用这个对象的方法
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
// 参数:
// 1. ClassLoader loader 类加载器,负责向内存中加载对象的。 使用反射获取对象的ClassLoader
// 如Dog类 , Dog.getCalss().getClassLoader(), 目标对象的类加载器(所以复习一下反射是很有必要的!!)
// 2. Class<?>[] interfaces: 接口,目标对象实现的接口,也是反射获取的。 如代码中就是获取了Animal接口
// 3. InvocationHandler h : 我们自己写的,刚好又在这个类里定义
//的方法,所以传个this,代理类要完成的功能。
// 返回值就是代理对象,所以需要自己转化一下
总结来说,JDK动态代理必须至少实现一个InvocationHandler 接口,因为JDK是根据这个接口“凭空”来生成类的,具体的功能增强的执行都在InvocationHandler的实现类里。而且只能是接口而不是继承一个类,因为动态代理对象默认已经继承了 Proxy 对象,而且 Java 不支持多继承,所以 JDK 动态代理要基于接口来实现。
基于ASM的CGLIB的动态代理
CGLIB(Code Generation Library)是一个基于ASM的字节码生成库,它允许我们在运行时对字节码进行修改和动态生成, 也是用来创建代理对象的。cglib的原理是继承, 他通过继承目标类,创建它的子类,在子类中重写父类中同名的方法, 实现功能的修改。因为cglib是继承,重写方法,所以要求目标类不能是final的, 方法也不能是final的。 cglib的要求目标类比较宽松, 只要能继承就可以了。cglib在很多的框架中使用,比如 mybatis ,spring框架中都有使用。如果发现没有实现接口该类而无法使用JDK代理,可以通过cglib代理实现。
这里有一篇十分好的示例博客。
Spring的AOP中的动态代理
AOP是一种被称为横切的技术,将那些影响多个类的公共行为封装到一个可重用的模块,并将其命名为 Aspect(切面)。所谓切面,就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来, 减少系统代码的重复,降低模块间的耦合度。和业务主要流程关系不大的功能拓展部分称为横切关注 点,经常发生在核心关注点的多出。AOP就是把核心关注点和横切关注点分离开。
spring AOP底层使用的就是动态代理来做到添加额外功能啦!它即使用到JDK动态代理,也使用到了CGLIB的动态代理,之所以用到两种动态代理,是因为JDK本身只提供基于接口的类,不支持类的代理,但是你不能保证你的类都是会继承接口的吧(虽然写项目的时候,大多业务类都是继承一个impl接口的嘤嘤嘤)。
来创建一个spring的工程,上业务方法:
public interface UserService {
void addUser();
void deleteUser();
void updateUser();
void getUser();
}
public class UserServiceImpl implements UserService {
/**
* 在业务方法中添加日志打印或者性能检测等......
* 如果在每个业务方法中去添加:
* 代码重复冗余,不易拓展维护
* 不符合高内聚、低耦合的要求
* 所以可以创建代理类,交给代理类为我们提供这些功能
*/
@Override
public void addUser() {
System.out.println("add");
}
@Override
public void deleteUser() {
System.out.println("delete");
}
@Override
public void updateUser() {
System.out.println("update");
}
@Override
public void getUser() {
System.out.println("get");
}
}
使用JDK代理
上代理类(重点!):
public class LogInvocationHandler implements InvocationHandler {
//声明target对象
//本案例的target就是UserServiceImpl类的对象
private Object target;
//创建构造函数,为target对象赋值
public LogInvocationHandler(Object target) {
this.target = target;
}
//代理类的业务方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object o = null;
System.out.println("start");
//实现业务增强
method.invoke(target,args);
System.out.println("end");
return o;
}
//提供取得代理类对象的方法
Object getProxy() {
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
}
以下是测试:
@Test
void contextLoads() {
LogInvocationHandler logInvocationHandler = new LogInvocationHandler(new UserServiceImpl());
//代理类是运行时产生而非我们手动创建的
UserService UserServiceProxy = (UserService) logInvocationHandler.getProxy();
UserServiceProxy.addUser();
}
使用CGLIB代理
假如我们的业务类不是实现接口的,就得使用CGLIB代理了,它需要添加一个依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
CGLIB主要是通过回调MethodInterceptor拦截器接口方法拦截,来实现自己的代理逻辑,类似于JDK中的InvocationHandler接口。
public class LogInterceptor implements MethodInterceptor{
private Object target;
//代理类的业务方法
@Override
public Object intercept(Object obj, Method method, Object[] arg, MethodProxy proxy) throws Throwable {
System.out.println("Before:"+method);
Object object=proxy.invokeSuper(obj, arg);
System.out.println("After:"+method);
return object;
}
//返回代理类对象
public Object getInstance(Object tatget) {
this.target = target;
//创建增强器
Enhancer enhancer = new Enhancer();
//使用增强其指定被代理的对象
enhancer.setSuperClass(this.target.getClass())
;
//设置回调方法
enhancer.setCallback(this);
//创建并返回代理对象
return enhancer.create();
}
}
然后测试方法如下,也是可以得到相同的结果的:
@Test
void test01() {
LogInterceptor cglib = new LogInterceptor();
//代理类是运行时产生而非我们手动创建的
UserServiceImpl UserService = (UserServiceImpl) cglib.getInstance(new UserServiceImpl);
UserService.addUser();
}
cglib代理就适合代理没有实现任何接口的代理类,但是spring里用到最多的还是JDK的动态代理啦~