结构型模式
大概学习完了五种创建型模式,分别是单例模式,工厂方法模式,抽象工厂方法模式,建造者模式,原型模式.下面要学习的是七种结构型模式,结构型模式(Structural Pattern)描述如何将类或者对象结合在一起形成更大的结构.结构型模式的目的是通过组合类或对象产生更大的结构以适应更高层次的逻辑需求,它包括:代理模式,装饰模式,适配器模式,组合模式,桥梁模式,外观模式,享元模式。
代理模式
定义
代理模式(Proxy Pattern)也叫委托模式,是一种使用率非常高的模式。英文原话是:Provide a surrogate or placeholder for another object to control access to it.意思是:为其他对象提供一种代理以控制对这个对象的访问。
代理模式是一项基本的设计技巧,许多模式如状态模式、策略模式、访问者模式本质上也采用了代理模式。
角色分类
1.抽象主题(Subject)角色:该角色是真实主题和代理主题的共同接口,以便在任何可以使用真实主题的地方都可以使用代理主题。
2.代理主题(Proxy Subject)角色:也叫为委托类、代理类,该角色负责控制类对真实主题的引用,负责在需要的时候创建或删除真实主题对象,并且在真实主题角色处理完毕前后做预处理和善后处理工作。
3.真实主题(Real Subject)角色:该角色也叫被委托角色或被代理角色,是业务逻辑的具体执行者。
/** * 主题角色 * 真实和代理的共同接口,可以在任何使用真实主题的地方使用代理主题. */ public interface Subject { //定义一个请求方法 public void request(); } /** * 真实主题角色 * 被代理角色,是业务逻辑的执行者 */ public class RealSubject implements Subject{ @Override public void request() { //业务逻辑处理 System.out.println("真实主题角色处理中..."); } } /** * 代理主题角色 * 在需要的时候创建或删除真实对象,并且在真实主题角色处理完毕前后做预处理和善后处理工作. */ public class ProxySubject implements Subject{ private Subject subject; //实例化的时候把需要代理的对象传进来 public ProxySubject(Subject subject) { this.subject = subject; } //代理后的方法执行内容 @Override public void request() { this.beforeRequest(); subject.request(); this.afterRequest(); } //请求前的操作 public void beforeRequest(){ //预处理 System.out.println(subject.getClass()+"被代理了,在它执行之前要执行预处理方法"); } //请求后的操作 public void afterRequest(){ //善后处理 System.out.println(subject.getClass()+"被代理了,在它执行之后要执行善后方法"); } } //调用 public class ProxyDemo { public static void main(String[] args) { Subject subject = new RealSubject(); System.out.println("未代理前执行request请求:------------------------"); subject.request(); System.out.println("代理后执行request请求:--------------------------"); subject = new ProxySubject(subject); subject.request(); } }
一个代理主题类可以代理多个真实主题,具体代理哪个真实主题由高层的应用模块决定,代理的真实主题可以通过代理类的构造函数传递。
代理模式的优点
1.职责清晰,真实的角色只需要实现实际的业务逻辑,而不用关心其他非本职的事务,附带的结果就是编程简洁清晰。
2.高扩展性,具体主题角色随需求不同可能有很多种,但只要实现了接口,代理类就完全可以在不做任何修改的情况下代理各种真实主题角色。
3.智能化,代理类可以在运行时才确定需要代理的真实主题,这是一种强大的功能。
代理模式的使用场景
代理模式的应用非常广泛。大到一个系统框架、企业平台,小到事务处理、代码片段,随处可见代理模式的使用。例如,Java RMI的远程调用就是一种代理模式的应用,现在流行的AOP也可以通过代理模式实现.
其他代理模式
上面的代理模式叫做静态代理模式,动态地理可以通过继承和聚合实现,静态代理可以做到在不修改目标对象的功能前提下,对目标功能扩展。但是因为静态代理代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.要解决这个问题可以使用动态代理模式
动态代理模式
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理.
java.lang.reflect.Proxy
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
ClassLoader loader
:指定当前目标对象使用的类加载器,获取加载器的方法是固定的 clazz.getClassLoader()
Class<?>[] interfaces
:被代理对象实现的接口的类型,使用泛型方式确认类型 clazz.getInterfaces()
InvocationHandler h
:事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
/** * 车类接口 */ public interface Moveable { public void run(); } /** * 车类接口实现类 */ public class Car implements Moveable { @Override public void run() { try { Thread.sleep(new Random().nextInt(3*1000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("car is running..."); } } /** * 代理之后的处理方法-处理时间记录 */ public class TimeInvocationHandler implements InvocationHandler { private Object object; public TimeInvocationHandler(Object object) { this.object = object; } /* * 用时代理 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始计算时间"); long startTime = System.currentTimeMillis(); method.invoke(object, args); long endTime = System.currentTimeMillis(); System.out.println("计算时间完毕,共用时"+(endTime-startTime)+"毫秒"); return null; } } /** * 代理后的处理方法-日志记录 */ public class LogInvocationHandler implements InvocationHandler { private Object object; public LogInvocationHandler(Object object) { this.object = object; } /* * 日志代理 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("日志开始记录"+method.getName()); method.invoke(object, args); System.out.println("日志记录完毕"); return null; } } //运行 public static void main(String[] args) { Car car = new Car(); Class<?> clazz = car.getClass(); //日志代理 InvocationHandler logHandler = new LogInvocationHandler(car); Moveable mv = (Moveable)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), logHandler); //用时代理 InvocationHandler timeHandler = new TimeInvocationHandler(mv); Moveable mv2 = (Moveable)Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), timeHandler); //运行run方法 mv2.run(); }
运行结果:
/** * 创建动态代理对象 * 动态代理不需要实现接口,但是需要指定接口类型 */ public class DynamicProxy { private Object target; public DynamicProxy(Object target) { this.target = target; } public Object getProxyInstance() { return Proxy.newProxyInstance(RealSubject.class.getClassLoader(), RealSubject.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { beforeRequest(); Object returnValue = method.invoke(target, args); //目标对象方法 afterRequest(); return returnValue; } }); } //请求前的操作 public void beforeRequest(){ //预处理 System.out.println(target.getClass()+"被动态代理了,在它执行之前要执行动态代理加入的预处理方法"); } //请求后的操作 public void afterRequest(){ //善后处理 System.out.println(target.getClass()+"被动态代理了,在它执行之后要执行动态代理加入的善后方法"); } } public class DynamicProxyDemo { public static void main(String[] args) { Subject subject = new RealSubject(); System.out.println(subject.toString()+"执行方法subject.request()结果为"); subject.request(); System.out.println("--------------------------------------"); subject = (Subject) new DynamicProxy(subject).getProxyInstance(); System.out.println("代理类"+subject.toString()+"执行方法request()方法结果为"); subject.request(); } }
执行后发现结果竟然是这个
这是因为System.out.println("代理类"+subject.toString()+"执行方法request()方法结果为");这行执行了toString()方法,toString()方法同样被代理了.这里有个疑问就是代理类的地址和被代理类的地址一样,执行request()方法结果却不一样,如果哪位牛人知道的话,欢迎评论.
Cglib代理
静态代理和动态代理模式都是要求目标对象是实现一个接口的目标对象,但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
- JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口,如果想代理没有实现接口的类,就可以使用Cglib实现.
- Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)
- Cglib包的底层是通过使用一个小而块的字节码处理框架ASM来转换字节码并生成新的类.不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉.
实现
1.引入cglib的jar文件,Spring的核心包中已经包括了Cglib功能,所以直接引入spring-core-4.2.3.RELEASE.jar即可.
2.引入功能包后,就可以在内存中动态构建子类
3.代理的类不能为final,否则报错
4.目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法.
public class Train { public void move(){ System.out.println("火车正在行驶..."); } } public class CglibProxy implements MethodInterceptor { private Enhancer enhancer = new Enhancer(); public Object getProxy(Class clazz){ //设置父类 enhancer.setSuperclass(clazz); enhancer.setCallback(this); //创建子类 return enhancer.create(); } /** * 拦截所有目标类方法的调用 * obj 目标类的实例 * m 目标方法的反射对象 * args 方法的参数 * proxy 代理类的实例 */ @Override public Object intercept(Object obj, Method m, Object[] args, MethodProxy proxy) throws Throwable { System.out.println("开始记录到日志..."); proxy.invokeSuper(obj, args); System.out.println("日志记录完毕."); return null; } } /** * 测试 */ public static void main(String[] args) { CglibProxy proxy = new CglibProxy(); Train t = (Train)proxy.getProxy(Train.class); t.move(); }
运行结果:
public class ProxyFactory implements MethodInterceptor { //维护目标对象 private Object target; public ProxyFactory(Object target) { this.target = target; } //给目标对象创建一个代理对象 public Object getProxyInstance(){ //1.工具类 Enhancer enhancer = new Enhancer(); //2.设置父类 enhancer.setSuperclass(target.getClass()); //3.设置调用函数 enhancer.setCallback(this); //4.创建子类(代理对象) return enhancer.create(); } @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { beforeRequest(); //执行目标对象的方法 Object returnvalue = method.invoke(target, args); afterRequest(); return returnvalue; } //请求前的操作 public void beforeRequest(){ //预处理 System.out.println(target.getClass()+"被代理了,在它执行之前要执行预处理方法"); } //请求后的操作 public void afterRequest(){ //善后处理 System.out.println(target.getClass()+"被代理了,在它执行之后要执行善后方法"); } } /* * 目标对象 * 没有实现任何接口 */ public class RealSubject { public void request() { System.out.println("----被代理对象请求方法!----"); } } public class CglibDemo { public static void main(String[] args) { RealSubject target = new RealSubject(); RealSubject proxy = (RealSubject) new ProxyFactory(target).getProxyInstance(); proxy.request(); } }
参考文章:Java的三种代理模式