1、什么是代理模式
为其它对象提供一个代理以控制对这个对象的访问。代理对象起到中介作用,可去掉功能服务或增加额外的服务。
代理模式的实现分为两类:静态代理和动态代理
2、静态代理
静态代理:代理和被代理对象在代理之前是确定的,它们都实现相同的接口或者继承相同的抽象类。
静态代理的实现分为聚合和继承两种方式
2.1 以“聚合方式”实现静态代理(推荐使用)
以下为以“聚合方式”实现静态代理的实现步骤:
第一步:创建被代理对象和代理对象要实现的接口
//被代理对象和代理对象要实现的接口
public interface ProxyInterface {
void marry();
}
第二步:创建被代理对象
public class NormalHome implements ProxyInterface{
@Override
public void marry() {
System.out.println("我们结婚啦");
}
}
第三步:创建代理对象,在代理对象中以聚合方式添加被代理对象
public class WeddingCompany implements ProxyInterface{
private ProxyInterface proxyInterface;
public WeddingCompany(ProxyInterface proxyInterface){
this.proxyInterface=proxyInterface;
}
@Override
public void marry() {
System.out.println("我们是婚庆公司的");
System.out.println("节日彩排");
proxyInterface.marry();
System.out.print("结婚完毕,我们需要做后续处理了,你们可以回家了,其余的事情我们公司来做");
}
}
第四步:聚合方式实现静态代理的测试类
public class Test {
public static void main(String[] args){
ProxyInterface proxyInterface=new WeddingCompany(new NormalHome());
proxyInterface.marry();
}
}
输出结果:
我们是婚庆公司的
节日彩排
我们结婚啦
结婚完毕,我们需要做后续处理了,你们可以回家了,其余的事情我们公司来做
2.2 以“继承方式”实现静态代理
以下为以“继承方式”实现静态代理的实现步骤:
第一步:创建被代理类要实现的接口
public interface CarInterface {
//卡车有行驶功能
public void run();
//卡车有停止功能
public void stop();
}
第二步:创建被代理类,被代理类实现上述创建好的接口
//被代理类实现接口,代表具备了两项功能
public class Car implements CarInterface{
//车辆行驶功能,实现行驶的逻辑
public void run()
{
System.out.println("车开动了!");
}
//车辆停车功能,实现停车的逻辑
public void stop()
{
System.out.println("车停下了!");
}
}
第三步:代理类继承被代理类,对外提供功能(可额外增加或者不增加其它的服务)
public class CarProxy extends Car{
public void run() {
//给汽车加速的逻辑
System.out.println("车加速!");
super.run();
}
public void stop() {
//给汽车减速的逻辑
System.out.println("车减速!");
super.stop();
}
}
第四步:以“继承方式”实现静态代理的测试
public class TestCar {
public static void main(String[] args){
//通过继承方式
Car car=new CarProxy();
car.run();
car.stop();
}
}
输出结果:
车加速!
车开动了!
车减速!
车停下了!
3、动态代理
动态代理的实现分为“JDK动态代理”和“CGLIB动态代理”两种方式
3.1 以“JDK动态代理方式”实现动态代理
第一步:创建被代理类要实现的接口和被代理的类
/**
* 被代理的类需实现的接口
*/
public interface Rent {
/**
* 出租房子
*/
public void rent();
}
/**
* 房东(被代理的类)
*/
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要出租房子!");
}
}
第二步:创建一个实现InvocationHandler接口的类,它必须实现invoke方法
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
// 获取代理实例
public Object getProxyInstance(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
/**
* 生成一个与代理实例相关联的“调用处理程序”(invoke方法),并将处理完后的结果返回
* @param proxy 被代理对象
* @param method 被调用方法
* @param args 被调用方法的参数,定义为Object[]表示可以有多个可以传入
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理的本质就是利用反射机制实现
System.out.println("通过中介(代理)租房开始....");
//Object result = method.invoke(rent,args);
Object result = method.invoke(rent);
System.out.println("通过中介(代理)租房结束....");
return result;
}
}
Proxy属于反射包中的一个类,它的静态方法newProxyInstance中有三个参数:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
loader:表示类加载器,一般情况下按照上述代码这种格式写就可以
interfaces:表示所代理对象实现的一组接口(Interface),因为接口是可以多实现的,在此处也就是Rent这一个接口
h:表示实现了InvocationHandler类的实例化后的对象,此处利用this表示(即当前对象)
特别说明:每个代理实例(proxyInstance)都有一个关联的调用处理程序,当在代理实例上调用真正的方法时,方法调用将被编码并分派到其调用处理程序的invoke方法
“调用处理程序”中method的invoke方法即可以传参也可以不用传承,两种写法都可以。
第三步:代码中使用“JDK动态代理方式”创建“代理对象”进行测试
public class Client {
public static void main(String[] args){
// 1.创建invocationHandler(即“调用处理程序”)
ProxyInvocationHandler pih =new ProxyInvocationHandler();
// 2.设置要代理的对象(Host表示房东,被代理对象)
pih.setRent(new Host());
// 3.获取代理实例
Rent proxy = (Rent)pih.getProxyInstance();
// 4.在代理实例上调用真正的方法
proxy.rent();
}
}
JDK动态代理的精华在于上述代码的第六行(pih.setRent(new Host())),我们可以在程序运行的过程中根据不同的业务场景来决定我们要代理的对象,动态生成相应的代理对象,进而去完成不同的任务。
执行上述代码,其输出结果为:
通过中介(代理)租房开始....
房东要出租房子!
通过中介(代理)租房结束....
所谓动态代理就是在运行时才生成代理对象,被代理对象需要实现一组interface。使用动态代理类时,必须实现InvocationHandler接口。
3.2 “JDK动态代理”的通用模板
上述InvocationHandler接口的实现类中获取代理实例需要依赖于被代理对象要实现的接口,因此可以对这部分代码进行改造使其成为“通用模板”,可以在代码中直接使用,示例代码如下所示:
第一步:创建被代理类要实现的接口和被代理的类
// 被代理对象实现的接口
public interface UserService {
void add();
}
// 被代理对象
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("新增用户");
}
}
第二步:创建一个 InvocationHandler 接口的实现类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的接口
private Object target;
public void setTarget(Object target) {
this.target = target;
}
// 获取代理实例
public Object getProxyInstance(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
// 处理代理实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
log(method.getName());
Object result = method.invoke(target);
return result;
}
public void log(String msg){
System.out.println("执行了"+msg+"方法");
}
}
第三步:代理模式“通用模板”的测试
public class Client {
public static void main(String[] args){
// 真实角色(被代理对象)
UserServiceImpl userService =new UserServiceImpl();
// 1.代理角色,不存在
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 2.设置要代理的对象
pih.setTarget(userService);
// 3.动态生成代理类
UserService proxy = (UserService) pih.getProxyInstance();
// 4.调用方法
proxy.add();
}
}
执行上述代码,其输出结果为:
执行了add方法
新增用户
3.3 “JDK动态代理”和“CGLIB动态代理”的区别
JDK动态代理:
1、只能实现实现了接口的类
2、没有实现接口的类不能实现JDK的动态代理
CGLIB动态代理:
1、针对类来实现代理的
2、对指定目标类产生一个子类,通过方法拦截技术来拦截所有父类方法的调用
使用目的:解决在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
4、代理模式的优缺点
优点:
1、职责清晰,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务,通过后期的代理完成一件完成事务,附带的结果就是编程简洁清晰。
2、代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
3、高扩展性
缺点:
1、由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
2、实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
5、代理模式的使用场景
最有名的是Spring AOP