代理模式
代理模式跟单例模式一样,都是面试问的比较频繁的一种设计模式,所以咱们是一定要学会的。一般来说吧,我们只需要掌握以下几点就可以轻松的应对面试官的各种问题了。
- 掌握代理模式的应用场景和实现原理。
- 了解静态代理和动态代理的区别。
- 了解CGLib和JDK Proxy的根本区别。
- 手写一次动态代理。
代理模式的定义
官方定义:代理模式指为其他对象提供一种代理,以控制对这个对象的访问。
我的理解是代理模式是可以在不修改被代理对象的基础上,通过扩展代理类,进行一些功能的附加与增强,有效防止目标对象的直接访问。代理类和被代理类应该共同实现一个接口或者共同继承某个类。
代理模式的适用场景
- 保护目标对象
- 增强目标对象
静态代理
静态代理就是按照代理模式写的代码,特点是代理类和目标类在代码里面是确定的,是在程序运行之前就已经存在的代理类的字节码文件,所以叫静态。
静态代理不够灵活,所以如果想要灵活,动态增加方法,增加行为,就需要动态代理了。
直接上代码
// 需求,设计一个看电影的简单静态代理
// 1.先创建一个接口类,需要目标类跟代理类一起实现的
// 2.创建目标类,实现接口类。
// 3.创建代理类,扩展目标类功能
// 3.1 实现接口类以及接口方法
// 3.2 声明目标类,实现构造方法,扩展目标类功能
public interface Movie{
//正在播放的电影
void play();
}
//目标类,实现Movie接口的类,
public class RealMovie implements Movie{
@Override
public void play{
//实现具体业务
System.out.println("您正在看周星驰的电影《功夫》");
}
}
//代理类,代理目标类,不修改目标类的结构
public class MovieProxy implements Movie
{
//声明目标类
RealMovie movie;
public MovieProxy(RealMovie movie){
super();
this.movie = movie;
}
//扩展目标类功能
@Override
public void play{
//ToDo
see(true);
movie.play();
see(false);
}
//扩展目标类功能
public void see(boolean isStart){
if(isStart)
System.out.println("电影即将开始...");
else
System.out.println("电影已经结束...");
}
//调用
public static void main(String[] args){
RealMovie movie1 = new RealMovie();
Movie movie = new MovieProxy(movie1);
movie.play();
}
}
动态代理
动态代理要为对象动态的增加方法、增加行为,并且动态代理是不存在代理类的字节码文件的,就需要一些实现手段。经常用的实现方式两种:反射实现,CGlib实现。
JDK跟CGlib的区别
JDK实现:JDK代理对于用户而言,依赖更强,调用也更复杂,基于反射机制,目标对象必须要有接口,生成的代理类是接口的一个实现类,通过InvocationHandler的invoke方法,JDK Proxy 生成逻辑较为简单,执行效率更低,每次都要反射。
CGlib实现:CGlib 采用继承的方式,覆盖父类的方法,CGlib对目标类没有任何要求,基于字节码实现,效率略低,但目标对象不需要有接口,生成的代理类是目标类的子类,目标类不能是final的。
JDK动态代理实现代码
//动态代理实现 - JDK实现
// 1.创建接口类,必须有
// 2.创建目标类,实现接口类的方法
// 3.创建代理类,实现InvocationHandler接口,实现invoke方法
public interface Movie{
//正在播放的电影
String play(String name);
}
//目标类
public class RealMovie implements Movie{
@Override
public String play(String name){
System.out.println("你正在观看" + name);
return "OK";
}
}
//代理类
public class ProxyHandler<T> implements InvocationHandler{
//声明动态目标类
private T target;
//动态代理
public ProxyHandler(T target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
System.out.println("电影即将开始...");
Object ret = method.invoke(target, args);
System.out.println("电影已经结束...");
return ret;
}
//调用
public static void main(String[] args){
// 使用 - 实例化目标类
RealMovie movie = new RealMovie();
// 声明代理类,通过参数指明目标类
ProxyHandler proxy = new ProxyHandler(movie);
// 生成代理对象实例,使用proxy类的一个静态方法
Movie movie = (Movie) Proxy.newProxyInstance(RealMovie.class.getClassLoader(), new Class[]{Movie.class}, proxy);
movie1.play("《功夫》");
}
}
Spring中的代理选择原则
- 当Bean有实现接口时,Spring就会用JDK的动态代理。
- 当Bean没有实现接口时,Spring选择CGlib。
- Spring可以通过配置强制使用CGlib,只需在Spring的配置文件中加入如下代码
<aop:aspectj-autoproxy proxy-target-class="true">
总结
优点
- 代理模式能将代理对象与真实被调用的目标对象分离。
- 一定程度上降低了系统的耦合程度,易于扩展。
- 代理可以起到保护目标对象的作用。
- 增强目标对象的职责。
缺点
代理模式会造成系统设计中类的数目增加。
在客户端和目标对象之间增加了一个代理对象,请求处理速度变慢。
增加了系统的复杂度。