设计模式-代理模式
代理(Proxy)是一种设计模式,提供了对目标对象另外的访问方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能.
这里使用到编程中的一个思想:不要随意去修改别人已经写好的代码或者方法,如果需改修改,可以通过代理的方式来扩展该方法
举个例子来说明代理的作用:
1. 假设我们想邀请一位明星,那么并不是直接连接明星,而是联系明星的经纪人,来达到同样的目的.明星就是一个目标对象,他只要负责活动中的节目,而其他琐碎的事情就交给他的代理人(经纪人)来解决.这就是代理思想在现实中的一个例子
2. 例如你想要带你的爱人去你去浪漫的土耳其,然后一起去东京和巴黎…… 在出行时你需要购买到达土耳其的机票,而你没有去机场的售票厅,购买而是在某个旅行网站上购买的。你通过在旅行网站购票完成了你的购票操作,这个旅行网站就是代理。
代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象
而我们常用的代理有:静态代理、动态代理(JDK代理、cglib代理)
1.1 静态代理
概念
静态代理比较简单,是由程序员编写的代理类,并在程序运行前就编译好的,而不是由程序动态产生代理类,这就是所谓的静态。
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
考虑这样的场景,管理员在网站上执行操作,在生成操作结果的同时需要记录操作日志。这是很常见的,此时就可以使用代理模式。
UML图
实现
//方式一:接口式静态代理
//1.抽象操作接口
public interface Manager {
void doSomething();
}
//2.实现操作接口的类
public class Admin implements Manager {
public void doSomething() {
System.out.println("Admin do something.");
}
}
//3.以聚合方式实现的代理操作
public class AdminPoly implements Manager{
private Manager manager;
public AdminPoly(Manager manager) {
this.manager = manager;
}
public void doSomething() {
System.out.println("Log:admin操作开始");
admin.doSomething();
System.out.println("Log:admin操作结束");
}
}
//4.测试代码
public class ProxyPatternAdminDemo {
public static void main(String[] args) {
Admin admin = new Admin();
Manager m = new AdminPoly(admin);
m.doSomething();
}
}
//方式二:继承式静态代理
//与上面的方式仅代理类和测试代码不同
//1.代理类
public class AdminsProxy extends Admins {
@Override
public void doSomething() {
System.out.println("Log:admin操作开始");
super.doSomething();
System.out.println("Log:admin操作开始");
}
}
//2.测试代码
public class ProxyPatternAdminsDemo {
public static void main(String[] args) {
AdminsProxy proxy = new AdminsProxy();
proxy.doSomething();
}
}
总结
优点:
- 可以做到在符合开闭原则的情况下对目标对象进行功能扩展。
- 协调调用者和被调用者,降低了系统的耦合度。
- 代理对象作为客户端和目标对象之间的中介,起到了保护目标对象的作用。
缺点:- 我们得为每一个服务都得创建代理类,工作量太大,不易管理。同时接口一旦发生改变,代理类也得相应修改。
- 由于在客户端和真实主题之间增加了代理对象,因此会造成请求的处理速度变慢;
- 实现代理模式需要额外的工作(有些代理模式的实现非常复杂),从而增加了系统实现的复杂度。
1.2 动态代理
动态代理不同于静态代理的特点是它更为灵活,因为动态代理就是在运行期间动态生成代理类。
动态代理分两种,一种是基于接口实现的Java Proxy(Java自带的),一种是基于继承实现的cglib代理。下面会分别给出一个小demo,并且从源码解析角度来解析二者动态代理的实现。
1.2.1 jdk 代理
假设有五百个不一样的人要结婚,都交给婚庆公司来操办,那么按照静态代理的思路来做,我们需要写五百个真实角色,并且代理角色持有这五百个真实角色。这显然不合逻辑。这时候动态代理就应运而生了。
public interface Marry {
public void marry();
}
public class Walidake implements Marry {
@Override
public void marry() {
System.out.println(this.getClass().getSimpleName() + "结婚啦");
}
}
public class WeddingCompany implements InvocationHandler{
private Object target;
public WeddingCompany(Object target) {
this.target = object;
}
// 生成代理对象
public Object getProxy() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(loader, interfaces, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("marry".equals(method.getName())) {
System.out.println("婚礼筹备");
method.invoke(object, args);
System.out.println("婚礼结束");
}
return null;
}
}
InvocationHandler相当于一个处理器,在invoke方法中我们能够操作真实对象,可以附加其他操作。而我们通过Proxy.newProxyInstance(…)方法生成代理。下面invoke参数的解释说明。
参数 | 说明 |
---|---|
proxy | 指代我们所代理的那个真实对象 |
method | 指代的是我们所要调用真实对象的某个方法的Method对象 |
args | 指代的是调用真实对象某个方法时接受的参数 |
实现InvocationHandler接口并附加操作后,获取代理角色。
//第一个人
Walidake walidake = new Walidake();
WeddingCompany weddingCompany = new WeddingCompany();
Marry marry = weddingCompany.getInstance(walidake).getProxy();
marry.marry();
System.out.println();
//第二个人
Other other = new Other();
WeddingCompany weddingCompany = new WeddingCompany();
Marry marry2 = weddingCompany.getInstance(other).getProxy();
marry2.marry();
动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写。
动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性 。
因为Java的反射机制可以生成任意类型的动态代理类。
java.lang.reflact包中的Proxy类和InvocationHandler接口 提供了生成动态代理的能力
但是使用JDK动态代理
生成的代理类是继承了Proxy类的,这就是说明了为什么使用JDK动态代理不能实现继承式动态代理,
原因是Java不允许多继承,而生成的代理类本身就已经继承了Proxy类。
**总结:**以上代码只有标黄的部分是需要自己写出,其余部分全都是固定代码。由于java封装了newProxyInstance这个方法的实现细节,所以使用起来才能这么方便,具体的底层原理将会在下一小节说明。
**缺点:**可以看出静态代理和JDK代理有一个共同的缺点,就是目标对象必须实现一个或多个接口,加入没有,则可以使用Cglib代理。
1.2.2 cglib代理
cglib是一个强大的高性能的代码生成包,可以为那些没有接口的类创建模仿(moke)对象。上一小节我们说到Java Proxy是通过生成字节码,再把类加载进内存后实现Proxy进行动态代理的。同样地,cglib也是通过生成操作字节码的技术实现动态代理的。但与前者不同的是它并不直接操作字节码,而是通过一个小而快的字节码处理框架ASM(Java字节码操控框架),来转换字节码并生成新的类。因此,cglib包要依赖于asm包,需要一起导入。
前提条件:
需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5.jar
目标类不能为final
目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法
/**
* 目标对象,没有实现任何接口
*/
public class Singer{
public void sing() {
System.out.println("唱一首歌");
}
}
/**
* Cglib子类代理工厂
*/
public class ProxyFactory implements MethodInterceptor{
// 维护目标对象
private Object target;
public ProxyFactory(Object target) {
this.target = target;
}
// 给目标对象创建一个代理对象
public Object getProxyInstance(){
//1.工具类
Enhancer en = new Enhancer();
//2.设置父类
en.setSuperclass(target.getClass());
//3.设置回调函数
en.setCallback(this);
//4.创建子类(代理对象)
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;
}
}
/**
* 测试类
*/
public class Test{
public static void main(String[] args){
//目标对象
Singer target = new Singer();
//代理对象
Singer proxy = (Singer)new ProxyFactory(target).getProxyInstance();
//执行代理对象的方法
proxy.sing();
}
}
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理。在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样对每一个方法或方法组合进行处理。Proxy 很美很强大,但是仅支持 interface 代理。Java 的单继承机制注定了这些动态代理类们无法实现对 class 的动态代理。好在有cglib为Proxy提供了弥补。class与interface的区别本来就模糊,在java8中更是增加了一些新特性,使得interface越来越接近class,当有一日,java突破了单继承的限制,动态代理将会更加强大。