代理模式的定义:为另一个对象提供一个替身或占位符,以控制对这个对象的访问。(Head First设计模式给出的定义)
动态代理技术是整个Java技术中最重要的一个环节,它是学习Java框架的基础,不会动态代理技术,学习Spring框架是学不明白的。
动态代理技术就是用来产生一个对象的代理对象的。在开发中为什么会需要一个代理对象呢?
我们可以举一个现实生活中的例子:
每个明星都会有一个自己的经纪人,这个经纪人其实就是他们的代理人,当有剧组要找明星拍电影时,不能直接给这个明星打电话联系他,只能是联系到这个明星的代理人也就是经纪人。比如最近特别火的19版《倚天屠龙记》中赵敏的扮演者陈钰琪,她会唱歌、跳舞、会拍戏,在陈钰琪没有出名之前,我们可以直接找她唱歌、跳舞、拍戏,当她出名之后,她干的第一件事情就是找一个经纪人,这个经纪人就是陈钰琪的代理。当我们想要找陈钰琪表演时,不能直接找到陈钰琪了,陈钰琪说:“具体事宜你找我经纪人商谈吧!”,所以只能去找她的经纪人,因此陈钰琪的这个经纪人存在的价值就是拦截/阻止我们对陈钰琪的直接访问!!!
这个现实中的例子和我们在开发中是一样的,我们在开发中之所以要产生一个对象的代理对象,主要用于拦截/阻止对真实业务对象的访问,代理对象应该具有和目标对象相同的方法。
所以在这里我们必须要明白两个概念:
代理模式的关键特点是:代理对象是目标对象的扩展,并会调用目标对象。
静态代理在使用时,需要定义接口或父类,目标对象(被代理对象,可以理解为明星)与代理对象一起实现相同的接口或者是继承相同的父类。
下面我们举个案例进行解释:
Subject接口,比如我们先定义一个唱歌的方法:
/**
* @Auther: 马旭辉
* @Date: 2019/4/29 14:48
* @Description: 代理模式接口
*/
public interface Subject {
//唱歌的方法
void sing();
}
现在创建一个委托对象,它实现了Subject接口:
/**
* @Auther: 马旭辉
* @Date: 2019/4/29 14:47
* @Description: 明星--委托对象
*/
public class Star implements Subject {
@Override
public void sing() {
System.out.println("明星开始唱歌!");
}
}
然后再创建一个代理对象:
/**
* @Auther: 马旭辉
* @Date: 2019/4/29 15:32
* @Description: 明星的经纪人,代理对象
*/
public class Broker implements Subject {
private Subject subject;
public Broker(Subject subject) {
this.subject = subject;
}
@Override
public void sing() {
//对目标对象的唱歌sing方法进行补充
System.out.println("向大家问好!");
this.subject.sing();
System.out.println("谢谢大家,再见!");
}
}
最后我们进行测试一下,编写测试类:
/**
* 测试类
*/
public class Main {
public static void main(String[] args) {
//创建一个目标对象--明星
Star star = new Star();
//创建一个代理对象--经纪人
Broker tony = new Broker(star);
//执行代理对象的方法
tony.sing();
}
}
我们来看一下运行结果:
通过以上代码及运行结果,我们可以看出代理模式的特点,代理类需要接收一个Subject接口对象,任何一个实现了该接口的对象,都可以通过代理类进行代理,增加了通用性。
但是!!!也有缺点,每一个代理类都必须实现一遍委托类的接口,如果接口增加方法,则代理类也必须跟着修改。代理类每一个接口对象对应一个委托对象,而且如果委托对象非常多,则静态代理类就非常臃肿,难以胜任。
动态代理有以下特点:
JDK中生成代理对象的API:
代理类所在的包:java.lang.reflect.Proxy,这是JDK1.5以后才开始提供的类。
JDK实现代理只需要使用newProxyInstance()方法,但是该方法需要接收三个完整的参数,如下:
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
该方法是在Proxy类中的静态方法,且接收的三个参数依次为:
ClassLoader loader
:指定当前目标对象使用类加载器,获取加载器的方法是固定的。Class>[] interfaces
:目标对象实现的接口类型,使用泛型方式确认类型。InvocationHandler h
:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入。现在我们来举个例子解释一下动态代理模式:
首先依然需要一个Subject接口和一个委托类:
public interface Subject {
//唱歌的方法
void sing();
}
public class Star implements Subject {
@Override
public void sing() {
System.out.println("明星开始唱歌!");
}
}
前两步跟静态代理模式是一样的步骤,下面就有区别了,创建一个动态代理类,实现InvocationHandler接口,并重写该类的invoke方法 :
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @Auther: 马旭辉
* @Date: 2019/4/29 16:02
* @Description: 动态代理
*/
public class DynamicProxy implements InvocationHandler {
private Object object;
//构造方法
public DynamicProxy(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//在代理真实对象前我们可以添加一些自己的操作
System.out.println("先向大家问好!");
//当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的 handler对象的 invoke方法来进行调用
Object result = method.invoke(object, args);
//在代理真实对象之后我们也可以添加一些自己的操作
System.out.println("跟大家说再见!");
return result;
}
}
然后编写测试类:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* 测试类
*/
public class Main {
public static void main(String[] args) {
//我们要代理的真实对象
Subject subject = new Star();
//要代理哪个对象就把哪个对象传进去,最后是通过该真实对象来调用其方法的
InvocationHandler handler = new DynamicProxy(subject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象,需要传入三个参数:
* 1,handler.getClass().getClassLoader(),这里我们使用Handler这个类的ClassLoader对象来加载我们的代理对象。
* 2,subject.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象实现的接口,表示我要代理的是这个真实对象,这样我就可以调用这组接口中的方法了。
* 3,handler,这个参数的意思是将这个代理对象关联到了上方的InvocationHandler这个对象上。
*/
Subject tony = (Subject) Proxy.newProxyInstance(handler.getClass().getClassLoader(), subject.getClass().getInterfaces(), handler);
tony.sing();
}
}
最后我们查看控制台运行结果:
动态代理弥补了静态代理的不足,但是这个世界上没有十全十美的事物,我们可以看出静态代理和动态代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,假如不想让他们必须实现接口的话,那就可以使用CGLIB代理。
上面的静态代理和动态代理模式都是要求目标对象实现一个接口,但是有的时候目标对象是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式实现代理(可以先理解为让这个明星的儿子/女儿当这个明星的经纪人),这种方法就叫做:CGLIB代理。
CGLIB代理,也叫做子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。
CGLIB子类代理需要注意的是,目标对象不能为final!
一定要确保你的项目有spring的依赖!!!否则不能使用。如果是个特别单纯的项目那就加一个spring-core-xxx.jar
,下面进行代码实现CGLIB代理模式,先创建目标目标对象类:
/**
*
* @author 马旭辉
* @date: 2019年4月30日 下午2:11:57
* 没有实现任何接口
*/
public class Star {
// 唱歌的方法
public void sing() {
System.out.println("====正在唱歌====");
}
}
然后创建CGLIB子类代理工厂,对Star在内存中动态构建一个子类对象:
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
/**
*
* @author 马旭辉
* @date: 2019年4月30日 下午2:32:47
* CGLIB子类代理工厂
*/
public class ProxyFactory implements MethodInterceptor {
// 维护目标对象
private Object target;
public ProxyFactory(Object target) {
super();
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 returnValue;
}
}
然后编写测试类进行测试:
/**
*
* @author 马旭辉
* @date: 2019年4月30日 下午2:33:37
* 测试类
*/
public class Main {
public static void main(String[] args) {
// 目标对象
Star target = new Star();
// 代理对象
Star proxy = (Star) new ProxyFactory(target).getProxyInstance();
// 执行代理对象的方法
proxy.sing();
}
}
查看控制台输出结果:
可以看到我们的CGLIB代理模式已经写成功了,达到我们想要的效果了。
这就是Java中的三种代理模式,如有文章有误,还望大家指正。