1.代理模式
- Java动态代理与设计模式中的代理模式有关,什么是代理模式呢?
代理模式:给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。
- 代理模式有什么用?
作用:通过代理可以控制访问某个对象的方法,在调用这个方法前做前置处理,调用这个方法后做后置处理。(即: AOP的微观实现!)
- 核心角色
-
抽象角色(接口):定义公共对外方法
-
真实角色(周杰伦):实现抽象角色,定义真实角色所要实现的业务逻辑,
-
代理角色(代理人):实现抽象角色,是真实角色的代理,通过调用真实角色的方法来完成业务逻辑,并可以附加自己的操作
2.静态代理
先通过实例来学习静态代理,然后理解静态代理的缺点,再来学习本文主角:动态代理
编写一个接口 Star ,以及该接口的一个实现类 RealStar
- 抽象角色:Star接口
public interface Star {
/**
* 面谈
*/
void confer();
/**
* 签合同
*/
void signContract();
/**
* 订票
*/
void bookTicket();
/**
* 唱歌
*/
void sing();
/**
* 收钱
*/
void collectMoney();
}
- 真实角色:RealStar实现类
public class RealStar implements Star {
public void bookTicket() {
}
public void collectMoney() {
}
public void confer() {
}
public void signContract() {
}
public void sing() {
System.out.println("周杰伦:唱兰亭序");
}
}
- 代理角色:ProxyStar
public class ProxyStar implements Star {
private Star star;
public ProxyStar(Star star) {
super();
this.star = star;
}
public void bookTicket() {
System.out.println("经纪人:定机票");
}
public void collectMoney() {
System.out.println("经纪人:收尾款");
}
public void confer() {
System.out.println("经纪人:面谈");
}
public void signContract() {
System.out.println("经纪人:签合同");
}
public void sing() {
star.sing();
}
}
- 客户端测试
public class Client {
public static void main(String[] args) {
Star proxy = new ProxyStar(new RealStar());
proxy.confer();
proxy.signContract();
proxy.bookTicket();
proxy.sing();
proxy.collectMoney();
}
}
- 运行结果
经纪人:面谈
经纪人:签合同
经纪人:定机票
周杰伦:唱兰亭序
经纪人:收尾款
- 静态代理优缺点
通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
-
当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
- 只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
- 新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
-
当接口需要增加、删除、修改方法的时候,实现类与代理类都要同时修改,不易维护。
-
代理类和实现类实现了相同的接口,这样就出现了大量的代码重复。
3.动态代理
3.1 JDK动态代理
- 抽象接口:star接口
public interface Star {
/**
* 唱歌
*/
void sing();
}
- 真实角色:RealStar实现类
public class RealStar implements Star {
public void sing() {
System.out.println("周杰伦:唱青花瓷");
}
}
- 代理工厂:
public class ProxyFactory {
// 被代理的对象,实际的方法执行者(周杰伦)
private Object star;
public ProxyFactory(Object star) {
this.star = star;
}
public Object getProxyObject(){
//返回一个代理对象
return Proxy.newProxyInstance(star.getClass().getClassLoader(),
star.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("经纪人:面谈");
System.out.println("经纪人:签合同");
System.out.println("经纪人:定机票");
// 调用 周杰伦 的 method 方法
Object invoke = method.invoke(star, args);
System.out.println("经纪人:收尾款");
// 返回方法的执行结果
return invoke;
}
});
}
}
- 运行结果
经纪人:面谈
经纪人:签合同
经纪人:定机票
周杰伦:唱青花瓷
经纪人:收尾款
- Proxy.newProxyInstance()
Proxy的newInstance方法对这个实例对象代理生成一个代理对象。看下newProxyInstance()的接口定义
各个参数的具体含义:
- loder,选用的类加载器。因为代理的是star,所以一般都会用加载star的类加载器。
- interfaces,被代理的类所实现的接口,这个接口可以是多个。
- h,绑定代理类的一个方法。
loder和interfaces基本就是决定了这个类到底是个怎么样的类。而h是InvocationHandler,决定了这个代理类到底是多了什么功能。所以动态代理的内容重点就是这个InvocationHandler。
- InvocationHandler接口
InvocationHandler作用就是,当代理对象的原本方法被调用的时候,会绑定执行一个方法,这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。
invoke方法各个参数的具体含义:
- proxy,代理的实例对象。
- method,对象被调用的方法。
- args,调用时的参数。
- 动态代理的使用场景
动态代理的好处我们从例子就能看出来,它比较灵活,可以在运行的时候才切入改变类的方法,而不需要预先定义它。
动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。
3.2 Cglib动态代理
cglib与动态代理最大的区别就是:
- 使用jdk动态代理的对象必须实现一个接口
- 使用cglib代理的对象则无需实现接口
CGLIB是第三方提供的包,所以需要引入jar包的坐标:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
如果你已经有spring-core的jar包,则无需引入,因为spring中包含了cglib。
- 真实角色:RealStar实现类
package com.by.proxy.CglibProxy;
public class RealStar{
public void sing() {
System.out.println("RealStar(周杰伦本人).sing()");
}
}
- 代理角色
package com.by.proxy.CglibProxy;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
//代理工厂
public class ProxyFactory implements MethodInterceptor {
//真实角色
private Object realObj;
public ProxyFactory(Object realObj) {
this.realObj = realObj;
}
/**'
* 获得子类代理对象
* @return
*/
public Object getProxyObject() {
//工具类
Enhancer en = new Enhancer();
//设置父类
en.setSuperclass(realObj.getClass());
//设置回调函数
en.setCallback(this);
//创建子类代理对象
return en.create();
}
/*
在子类中调用父类的方法
intercept方法参数说明:
obj : 代理对象
method : 真实对象中的方法的Method实例
args : 实际参数
methodProxy :代理对象中的方法的method实例
*/
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy methodProxy)throws Throwable {
System.out.println("真正的方法执行前!");
System.out.println("面谈,签合同,预付款,订机票");
Object result = method.invoke(realObj, args);
System.out.println("真正的方法执行后!");
System.out.println("收尾款");
return object;
}
}
- 测试
package com.by.proxy.CglibProxy;
//测试类
public class Client {
public static void main(String[] args) {
//获取代理对象
RealStar proxyObject =
(RealStar) new ProxyFactory(new RealStar()).getProxyObject();
proxyObject.sing();
}
}
3.3 JDK动态代理和CGLIB动态代理的区别?
Spring AOP中的动态代理主要有两种方式:JDK动态代理和CGLIB动态代理。
- JDK动态代理
如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
缺点:目标类必须有实现的接口
。如果某个类没有实现接口,那么这个类就不能用JDK动态代理。
- CGLIB动态代理
通过继承实现。如果目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library)可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。
CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。
优点:目标类不需要实现特定的接口
,更加灵活。
-
什么时候采用哪种动态代理?
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP
- 如果目标对象没有实现了接口,必须采用CGLIB库
-
两者的区别:
- jdk动态代理使用jdk中的类Proxy来创建代理对象,它使用反射技术来实现,不需要导入其他依赖。cglib需要引入相关依赖:asm.jar,它使用字节码增强技术来实现。
- 当目标类实现了接口的时候Spring Aop默认使用jdk动态代理方式来增强方法,没有实现接口的时候使用cglib动态代理方式增强方法