23种设计模式介绍:https://mp.csdn.net/postedit/90552052
1.1 意图
在有些情况下,一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又如找女朋友、找保姆、找工作等都可以通过找中介完成。在软件设计中,使用代理模式的例子也很多,例如,要访问的远程对象比较大(如视频或大图像等),其下载要花很多时间。还有因为安全原因需要屏蔽客户端直接访问真实对象,如某单位的内部数据库等。
1.2 模式定义
代理模式(Proxy Pattern) :给某一个对象提供一个代 理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。
1.3 使用场景
按职责来划分,通常有以下使用场景: 1、远程代理。 2、虚拟代理。 3、Copy-on-Write 代理。 4、保护(Protect or Access)代理。 5、Cache代理。 6、防火墙(Firewall)代理。 7、同步化(Synchronization)代理。 8、智能引用(Smart Reference)代理。
注意事项: 1、和适配器模式的区别:适配器模式主要改变所考虑对象的接口,而代理模式不能改变所代理类的接口。 2、和装饰器模式的区别:装饰器模式为了增强功能,而代理模式是为了加以控制。
几种常用的代理模式:
- 图片代理:一个很常见的代理模式的应用实例就是对大图浏览的控制。
- 用户通过浏览器访问网页时先不加载真实的大图,而是通过代理对象的方法来进行处理,在代理对象的方法中,先使用一个线程向客户端浏览器加载一个小图片,然后在后台使用另一个线程来调用大图片的加载方法将大图片加载到客户端。当需要浏览大图片时,再将大图片在新网页中显示。如果用户在浏览大图时加载工作还没有完成,可以再启动一个线程来显示相应的提示信息。通过代理技术结合多线程编程将真实图片的加载放到后台来操作,不影响前台图片的浏览。
- 远程代理:远程代理可以将网络的细节隐藏起来,使得客户端不必考虑网络的存在。客户完全可以认为被代理的远程业务对象是局域的而不是远程的,而远程代理对象承担了大部分的网络通信工作。
- 虚拟代理:当一个对象的加载十分耗费资源的时候,虚拟代理的优势就非常明显地体现出来了。虚拟代理模式是一种内存节省技术,那些占用大量内存或处理复杂的对象将推迟到使用它的时候才创建。
1.4 模式结构
代理模式包含如下角色:
- Subject: 抽象主题角色
- Proxy: 代理主题角色
- RealSubject: 真实主题角色
1.5 时序图
1.6 demo
创建一个 Image 接口和实现了 Image 接口的实体类。ProxyImage 是一个代理类,减少 RealImage 对象加载的内存占用。
ProxyPatternDemo,演示类使用 ProxyImage 来获取要加载的 Image 对象,并按照需求进行显示。
步骤1:创建Image接口
package DesignPattern23.StaticProxy;
/**
* Description:
* Author:
* Date: 2019/11/11
*/
public interface Image {
void display();
}
步骤2:创建接口的实体类。RealImage,ProxyImage
package DesignPattern23.StaticProxy;
/**
* Description:
* Author:
* Date: 2019/11/11
*/
public class RealImage implements Image{
private String fileName;
public RealImage(String fileName){
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName){
System.out.println("Loading " + fileName);
}
}
package DesignPattern23.StaticProxy;
/**
* Description:
* Author:
* Date: 2019/11/11
*/
public class ProxyImage implements Image{
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName){
this.fileName = fileName;
}
@Override
public void display() {
if(realImage == null){
realImage = new RealImage(fileName);
}
realImage.display();
}
}
步骤3:创建测试类ProxyPatternDemo
package DesignPattern23.StaticProxy;
/**
* Description:
* Author:
* Date: 2019/11/11
*/
public class ProxyPatternDemo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图像将从磁盘加载
image.display();
System.out.println("~~~~~~~~~~~~");
// 图像不需要从磁盘加载
image.display();
}
}
运行结果为:
Loading test_10mb.jpg
Displaying test_10mb.jpg
~~~~~~~~~~~~
Displaying test_10mb.jpg
Process finished with exit code 0
1.7 总结
在代理模式中,要求给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英文叫做Proxy或Surrogate,它是一种对象结构型模式。 - 代理模式包含三个角色:抽象主题角色声明了真实主题和代理主题的共同接口;代理主题角色内部包含对真实主题的引用,从而可以在任何时候操作真实主题对象;真实主题角色定义了代理角色所代表的真实对象,在真实主题角色中实现了真实的业务操作,客户端可以通过代理主题角色间接调用真实主题角色中定义的方法。 - 代理模式的优点在于能够协调调用者和被调用者,在一定程度上降低了系统的耦合度;其缺点在于由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢,并且实现代理模式需要额外的工作,有些代理模式的实现非常复杂。远程代理为一个位于不同的地址空间的对象提供一个本地的代表对象,它使得客户端可以访问在远程机器上的对象,远程机器可能具有更好的计算性能与处理速度,可以快速响应并处理客户端请求。- 如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建,这个小对象称为虚拟代理。虚拟代理通过使用一个小对象来代表一个大对象,可以减少系统资源的消耗,对系统进行优化并提高运行速度。 - 保护代理可以控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
以上为静态代理内容,下面说动态代理,
动态代理,重要程度★★★★★
Java领域中,常用的动态代理实现方式有两种,一种是利用JDK反射机制生成代理,另外一种是使用CGLIB代理。
JDK代理必须要提供接口,而CGLIB则不需要,可以直接代理类。
1.jdk代理demo
package DesignPattern23.DynamicProxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Description:
* Author:
* Date: 2019/11/11
*/
public class JDKDynamicProxyDemo {
public static void main(String[] args) {
People chinese = new Chinese();
PeopleInvocationHandler invocationHandler = new PeopleInvocationHandler(chinese);
People proxy = (People) Proxy.newProxyInstance(chinese.getClass().getClassLoader(),
chinese.getClass().getInterfaces(), invocationHandler);
proxy.sayHello();
/*//使用jdk8.0的lambda表达式
People proxy2 = (People) Proxy.newProxyInstance(chinese.getClass().getClassLoader(),chinese.getClass().getInterfaces(),
(proxy,method, args2)-> {
if (method.getName().equalsIgnoreCase("sayHello")){
System.out.println("中国人");
return method.invoke(chinese,args);
}
return null;
});
proxy2.sayHello();*/
}
}
interface People{
void sayHello();
}
class Chinese implements People{
@Override
public void sayHello() {
System.out.println("Chinese say hello.");
}
}
class PeopleInvocationHandler implements InvocationHandler {
private Object people;
public PeopleInvocationHandler(Object people) {
this.people = people;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("中国人");
Object invoke = method.invoke(people, args);
return invoke;
}
}
demo2:
步骤1:一个程序员Developer,他会开发code,他调试debug。
package DesignPattern23.DynamicProxy;
/**
* Description:
* Author:
* Date: 2019/11/11
*/
public interface Developer {
void code();
void debug();
}
步骤2:程序员有很多分类,其中有Java程序员JavaDeveloper,他会开发Java代码,会调试Java代码。
package DesignPattern23.DynamicProxy;
/**
* Description:
* Author:
* Date: 2019/11/11
*/
public class JavDeveloper implements Developer{
private String name;
public JavDeveloper(String name) {
this.name = name;
}
@Override
public void code() {
System.out.println(this.name + "is coding java");
}
@Override
public void debug() {
System.out.println(this.name + "is debugging java");
}
}
但是呢,有个叫Zack的程序员它在开发之前,会祈祷一下,这样他开发的代码就不会有bug。Zack的这种“特异功能”是后天练出来的,并没有哪种程序员有这种特性。虽然我们也可以定义一个拥有这样特性的程序员,但是拥有各种乱七八糟特性的程序千千万。我们什么时候才能定义完,而能保证不漏呢?其实我们没有必要去定义他,因为他是后天养成的,我们应该在这个程序员的成长期去实现这个特性,而不是在他出生之前定义。
我们来看下代码是怎么实现的:
package DesignPattern23.DynamicProxy;
import java.lang.reflect.Proxy;
/**
* Description:
* Author:
* Date: 2019/11/11
*/
public class JavaDynamicProxy {
public static void main(String[] args) {
JavDeveloper zack = new JavDeveloper("Zack");
System.out.println(zack.getClass().getClassLoader());
Developer zackProxy = (Developer)Proxy.newProxyInstance(zack.getClass().getClassLoader(),zack.getClass().getInterfaces(),
((proxy, method, args1) -> {
if (method.getName().equalsIgnoreCase("code")){
System.out.println("Zack is praying before coding~");
return method.invoke(zack,args);
}
if (method.getName().equalsIgnoreCase("debug")){
System.out.println("Zack no need debug");
return null;
}
return null;
}));
zackProxy.code();
zackProxy.debug();
}
}
如果Zack只是一个普通的Java程序员,那么他的开发结果是:
Zack is coding java
Zack is debugging java
但是真正的Zack(代理后):
Zack is praying for the code!
Zack is coding java
Zack's have no bug!No need to debug!
回看下上面是如何使用动态代理的,生成一个实例对象,然后用Proxy.newProxyInstance()方法对这个实例对象代理生成一个代理对象。
有没有留意到zackProxy的类型是Developer接口,而不是一个实现类。因为zack在被代理后生成的对象,并不属于Developer接口的任何一个实现类。但是它是基于Developer接口和zack的类加载代理出来的。看下newProxyInstance()的接口定义:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
这三个参数具体的含义来看看注解是怎么描述的:
- loder,选用的类加载器。因为代理的是zack,所以一般都会用加载zack的类加载器。
- interfaces,被代理的类所实现的接口,这个接口可以是多个。
- h,绑定代理类的一个方法。
loder和interfaces基本就是决定了这个类到底是个怎么样的类。而h是InvocationHandler,决定了这个代理类到底是多了什么功能。所以动态代理的内容重点就是这个InvocationHandler。
看下InvocationHandler的源码:
public interface InvocationHandler {
/**
* Processes a method invocation on a proxy instance and returns
* the result. This method will be invoked on an invocation handler
* when a method is invoked on a proxy instance that it is
* associated with.
*
* @param proxy the proxy instance that the method was invoked on
*
* @param method the {@code Method} instance corresponding to
* the interface method invoked on the proxy instance. The declaring
* class of the {@code Method} object will be the interface that
* the method was declared in, which may be a superinterface of the
* proxy interface that the proxy class inherits the method through.
*
* @param args an array of objects containing the values of the
* arguments passed in the method invocation on the proxy instance,
* or {@code null} if interface method takes no arguments.
* Arguments of primitive types are wrapped in instances of the
* appropriate primitive wrapper class, such as
* {@code java.lang.Integer} or {@code java.lang.Boolean}.
*
* @return the value to return from the method invocation on the
* proxy instance. If the declared return type of the interface
* method is a primitive type, then the value returned by
* this method must be an instance of the corresponding primitive
* wrapper class; otherwise, it must be a type assignable to the
* declared return type. If the value returned by this method is
* {@code null} and the interface method's return type is
* primitive, then a {@code NullPointerException} will be
* thrown by the method invocation on the proxy instance. If the
* value returned by this method is otherwise not compatible with
* the interface method's declared return type as described above,
* a {@code ClassCastException} will be thrown by the method
* invocation on the proxy instance.
*
* @throws Throwable the exception to throw from the method
* invocation on the proxy instance. The exception's type must be
* assignable either to any of the exception types declared in the
* {@code throws} clause of the interface method or to the
* unchecked exception types {@code java.lang.RuntimeException}
* or {@code java.lang.Error}. If a checked exception is
* thrown by this method that is not assignable to any of the
* exception types declared in the {@code throws} clause of
* the interface method, then an
* {@link UndeclaredThrowableException} containing the
* exception that was thrown by this method will be thrown by the
* method invocation on the proxy instance.
*
* @see UndeclaredThrowableException
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
根据注解描述可知,InvocationHandler作用就是,当代理对象的原本方法被调用的时候,会绑定执行一个方法,这个方法就是InvocationHandler里面定义的内容,同时会替代原本方法的结果返回。
InvocationHandler接收三个参数
- proxy,代理后的实例对象。
- method,对象被调用方法。
- args,调用时的参数。
在上面例子中:
如果把return method.invoke(zack,args);改为return method.invoke(proxy,args);invoke的对象不是zack,而是proxy,根据上面的说明猜猜会发生什么?
答:会不停地循环调用。因为proxy是代理类的对象,当该对象方法被调用的时候,会触发InvocationHandler,而InvocationHandler里面又调用一次proxy里面的对象,所以会不停地循环调用。并且,proxy对应的方法是没有实现的。所以是会循环的不停报错。
动态代理的原理:
先看newProxyInstance源码:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
可以发现:
大概就是把接口复制出来,通过这些接口和类加载器,拿到这个代理类cl。然后通过反射的技术复制拿到代理类的构造函数(这部分代码在Class类中的getConstructor0方法),最后通过这个构造函数new个一对象出来,同时用InvocationHandler绑定这个对象。
2.CGLIB代理demo
待定。
动态代理的使用场景:
动态代理的好处我们从例子就能看出来,它比较灵活,可以在运行的时候才切入改变类的方法,而不需要预先定义它。
动态代理一般我们比较少去手写,但我们用得其实非常多。在Spring项目中用的注解,例如依赖注入的@Bean、@Autowired,事务注解@Transactional等都有用到,换言之就是Srping的AOP(切面编程)。
这种场景的使用是动态代理最佳的落地点,可以非常灵活地在某个类,某个方法,某个代码点上切入我们想要的内容,就是动态代理其中的内容。所以下一篇我们来细致了解下Spring的AOP到底是怎么使用动态代理的。