代理模式
何为代理模式?顾名思义可以简单理解为代为管理,代理模式就是为其他对象提供额外服务,控制访问(前置处理),或做善后处理(后置处理)。有了代理之后,可以在原来功能的基础上由代理完成另外一部分事情。
这么说估计还是有点难理解,那我举个简单栗子吧。比如英雄联盟周年盛典要邀请周杰伦去现场唱歌,显然举办方要直接联系到周杰伦可能会有点复杂,所以举办方可以先联系到杰伦的经济人,谈好价钱后再由经纪人安排杰伦去现场唱歌。经纪人便是一个代理者的角色,而这个流程便是代理模式。
上面这个例子,估计很多人都见到过类似的,看完之后可能对代理这个概念会有一定的了解,但是回到我们上面的定义,我们提到了代理的主要作用是在实现功能拓展,在原来基础上由代理完成一部分事情。可是按照我上面举的例子,似乎代理的作用仅仅是防止直接访问目标对象给系统带来的不必要复杂性,那么,问题来了,代理模式是如何实现功能拓展的呢?接下来让我们一步步来分析。
实例分析
假如英雄联盟主办方成功请到了周杰伦,杰伦准备唱英雄这首歌
public class Singer {
public void sing(){
System.out.println("击杀 双杀 三杀 Penta kill");
}
}
但是节目方希望杰伦(一个new出来的Singer对象)可以在唱歌的开始前和结束后可以和观众互动,为了实现这个需求,我们就必须对Singer源代码进行修改
public class Singer {
public void sing(){
System.out.println("大家好,我是周杰伦,哎呦不错哦");
System.out.println("击杀 双杀 三杀 Penta kill");
System.out.println("观众朋友们,我唱完歌啦,我要回去喝奶茶啦 拜拜");
}
}
显然,在实际开发中,对源代码进行修改是我们极力要避免的。那么我们如何实现在不修改源代码的基础上拓展我们的功能呢?这就要用到我们的代理模式了。代理模式又可以分为三种,接下来我们一一进行讲解。
静态代理模式
我们首先声明一个抽象Singer角色(接口)
public interface Singer {
public void sing();
}
再通过实现 Singer接口声明一个真实角色,负责实现接口中的方法
class RealSinger implements Singer{
@Override
public void sing() {
System.out.println("击杀 双杀 三杀 Penta kill");
}
}
接下来就是实现功能拓展的关键步骤了,再回忆下我们的需求,是想让周同学在唱歌前后和观众互动,在不改变源代码的情况下我们可以使用一个代理类,同样实现Singer接口,我们先做这个操作
class ProxySinger implements Singer{
@Override
public void sing() {
System.out.println("大家好,我是周杰伦,哎呦不错哦");
System.out.println("观众朋友们,我唱完歌啦,我要回去喝奶茶啦 拜拜");
}
}
目前为止,和观众互动已经实现了,那么怎么在他们之间把原来的唱歌部分加上去呢,我们不妨继续对这个代理类进行修改
class ProxySinger implements Singer{
private Singer singer;
public ProxySinger(Singer singer) {
this.singer = singer;
}
@Override
public void sing() {
System.out.println("大家好,我是周杰伦,哎呦不错哦");
singer.sing();
System.out.println("观众朋友们,我唱完歌啦,我要回去喝奶茶啦 拜拜");
}
}
客户端测试
public static void main(String[] args) {
Singer singer = new RealSinger();
Singer proxy = new ProxySinger(singer);
proxy.sing();
}
这样的话,我们的目标就达到啦,成功做到了功能拓展和代码复用,而且无需修改源代码,美滋滋。
动态代理(jdk代理)
在静态代理中,代理对象必须提前写出,如果接口层发生了变化,代理对象的代码也要进行维护。如果能在运行时动态地写出代理对象,不但减少了一大批代理类的代码,也少了不断维护的烦恼,不过运行时的效率必定受到影响。这种方式就是动态代理
和静态代理一样,首先先声明抽象角色和真实角色
public interface Singer {
public void sing();
}
class RealSinger implements Singer{
@Override
public void sing() {
System.out.println("击杀 双杀 三杀 Penta kill");
}
}
既然是动态代理,顾名思义,即动态生成代理类。代理对象不需要实现目标对象接口,通过JAVA的API动态生成目标对象的代理对象。
Proxy.newProxyInstance(。。。),其中有三个参数:
ClassLoader loader:目标对象的类加载器
Class<?>[] interfaces:目标对象的接口类型
InvocationHandler h:事件处理函数,实现对目标对象的操作。
代理工厂类
public class ProxyFactory {
private Object obj;
public ProxyFactory(Object obj) {
this.obj = obj;
}
public Object getInstance(){
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("大家好,我是周杰伦,哎呦不错哦"); //增加的功能
Object returnObject = method.invoke(obj,args);
System.out.println("观众朋友们,我唱完歌啦,我要回去喝奶茶啦 拜拜"); //增加的功能
return returnObject;
}
});
}
}
其实大部分代码都是写死的。。在invoke方法中增加我们要拓展的功能就行了。
客户端测试
public static void main(String[] args) {
Singer singer = new RealSinger();
Singer proxy = (Singer)new ProxyFactory(singer).getInstance();
proxy.sing();
}
Cglib代理
静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,而Cglib代理,则是一种不需要实现接口的代理模式。
使用Cglib代理的前提条件
- 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
- 目标类不能为final
- 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
首先定义一个目标对象类
public class Singer {
public void sing() {
System.out.println("击杀 双杀 三杀 Penta kill");
}
}
再定义代理工厂类,大部分代码都是固定的
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target) {
this.target = target;
}
//给目标对象创建代理对象
public Object getProxyInstance(){
//1.工具类,允许为非接口类型创建一个Java代理。Enhancer动态创建了给定类型的子类但是拦截了所有的方法。
//和Proxy不一样的是,不管是接口还是类他都能正常工作
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回掉函数(因为MethodInterceptor继承了Callback类,默认执行intercept方法)
en.setCallback(this);
//4.创建子类
return en.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("大家好,我是周杰伦,哎呦不错哦");
Object returnObject = method.invoke(target,objects);
System.out.println("观众朋友们,我唱完歌啦,我要回去喝奶茶啦 拜拜");
return returnObject;
}
}
客户端测试
public static void main(String[] args) {
Singer singer =(UserDao) new CglibProxy(new Singer()).getProxyInstance();
singer.save();
}
核心角色
根据我们前面对三种代理模式的介绍,我们可以提炼出代理模式总的几个核心角色
抽象角色:定义代理角色和真实角色的公共对外方法
真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。关注真正的业务逻辑!
代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。将统一的流程控制放到代理角色中处理!
应用场景
典型的就是Spring AOP的实现,在spring AOP中
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理