1. 代理模式(Proxy Pattern)
代理模式根据不用的职责和应用场景有很多种形式,例如远程代理,虚拟代理,保护代理,防火墙代理、智能引用代理、缓存代理、同步代理、复杂隐藏代理、写入复制代理等。
但是它们都有一个共同的特点,为其它对象提供一种代理以控制对这个对象的访问。
1.1 和装饰者模式的区别
一般情况下,我们容易将装饰者模式和代理模式搞混。
- 装饰者模式:是在不使用继承,不改变原有对象的情况下增加或扩展对象行为,但是并不会禁用你对象的某个行为。
- 代理模式:而代理模式是控制这个对象的访问。
例如我们看看下面这个虚拟代理的例子:
当一个对象的创建开销很大,我们想要在真正使用到的时候才真正创建这个对象的时候,就可以用到虚拟代理。
interface Image {
public void displayImage();
}
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadImageFromDisk();
}
private void loadImageFromDisk() {
System.out.println("Loading " + filename);
}
public void displayImage() {
System.out.println("Displaying " + filename);
}
}
class ProxyImage implements Image {
private String filename;
private Image image;
public ProxyImage(String filename) {
this.filename = filename;
}
public void displayImage() {
if(image == null)
image = new RealImage(filename);
image.displayImage();
}
}
class ProxyExample {
public static void main(String[] args) {
Image image1 = new ProxyImage("HiRes_10MB_Photo1");
Image image2 = new ProxyImage("HiRes_10MB_Photo2");
image1.displayImage(); // loading necessary
image2.displayImage(); // loading necessary
}
}
例如ProxyImage
,我们隐藏了RealImage
中的loadImageFromDisk()
方法,创建ProxyImage
对象并不会立刻创建RealImage
对象,实现了对象的懒加载。
这和装饰者模式是有些出入的,它们的职责和目的不一样。装饰模式主要是强调对对象的功能扩展,而代理模式虽然也可以扩展对象的功能,但更偏向于对象的访问限制。
2. 动态代理
假设有这么一个Person类。
public interface Person {
String getName();
void setName(String name);
String getGender();
void setGender(String gender);
int getAge();
void setAge(int age);
}
public class PersonImpl implements Person {
private String name;
private String gender;
private int age;
@Override
public String getName() {
return name;
}
@Override
public void setName(String name) {
this.name = name;
}
@Override
public String getGender() {
return gender;
}
@Override
public void setGender(String gender) {
this.gender = gender;
}
@Override
public int getAge() {
return age;
}
@Override
public void setAge(int age) {
this.age = age;
}
}
如果我们想记录这个Person对象的信息修改次数怎么办呢?
我们是不是可以弄一个装饰类扩展一下。
public class DecorPerson implements Person {
private PersonImpl person;
private int modifiedTimes;
public DecorPerson(PersonImpl person) {
this.person = person;
}
@Override
public String getName() {
return person.getName();
}
@Override
public void setName(String name) {
modifiedTimes++;
person.setName(name);
}
@Override
public String getGender() {
return person.getGender();
}
@Override
public void setGender(String gender) {
modifiedTimes++;
person.setGender(gender);
}
@Override
public int getAge() {
return person.getAge();
}
@Override
public void setAge(int age) {
modifiedTimes++;
person.setAge(age);
}
public int getModifiedTimes() {
return modifiedTimes;
}
}
如果Person类拥有非常非常多setXxx()方法,为每一个setter方法都增加代码会不会非常繁琐。或者当我们再给Person类添加一个job属性时,这个DecorPerson是不是也要跟着修改呢。
所以Java就提供了一个代理类给我们使用java.lang.reflect.Proxy
,可以通过Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
这个方法创建代理对象。
首先我们需要实现一个InvocationHandler对象。
public class PersonInvocationHandler implements InvocationHandler {
private Person person;
private int modifiedTimes;
public PersonInvocationHandler(Person person) {
this.person = person;
}
public int getModifiedTimes() {
return modifiedTimes;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("set")) {
modifiedTimes++;
}
return method.invoke(person, args);
}
}
紧接着我们通过Proxy类创建Person的代理对象。
public class Main {
public static void main(String[] args) {
// write your code here
PersonInvocationHandler handler = new PersonInvocationHandler(new PersonImpl());
Person person = (Person) Proxy.newProxyInstance(
Person.class.getClassLoader(),
new Class[]{Person.class},
handler);
person.setAge(17);
person.setName("aitsuki");
person.setGender("male");
person.setAge(18)
System.out.println(handler.getModifiedTimes()); // 最终打印结果为 4
}
}
当然,如果我们需要隐藏Person的来历,我们可以提供一个工厂用来创建Person,让用户感觉不到这其实是一个代理对象。
public class PersonFactory {
public static Person getPerson() {
PersonInvocationHandler handler = new PersonInvocationHandler(new PersonImpl());
return (Person) Proxy.newProxyInstance(
Person.class.getClassLoader(),
new Class[]{Person.class},
handler);
}
}
上面演示了使用Proxy类实现动态代理的方式。那么现在就来说说动态代理的概念:
动态代理,严格意义上来说指的并不是一种代理模式,而是一种反射的运用技术,它可以在程序运行时通过反射创建一个目标接口的代理对象,是实现代理模式的一种途径。
如果你非要说动态代理是一种设计模式,它反而更像是装饰者模式或监听器模式,因为它是监听目标对象的方法调用,实现对某个方法的扩展。从而提高程序的扩展性和灵活性,以及是JAVA实现AOP(面向切面编程)的一个重要技术。
那么相对于动态代理就出现了“静态代理”这个词了,指的就需要在源码级别中维护一个代理类,当然,“静态代理”也并不是指一种代理模式了。
3. 面向切面编程(AOP–aspect oriented programming)
既然说到了动态代理,那么再简单说说面向切面编程。
面向切面编程可以看作是对面向对象编程的扩展,“切面”,我们可以理解为“关注点”。
例如上面的Person对象,它的主要关注点就是对象的操作,比如修改名字,性别等。而它的横切关注点则是日志记录,比如记录对象的修改次数。
但是如果直接在Person的实现类中直接写入日志操作的相关代码,那么日志系统就和Person产生了耦合,需要将日志记录这部分逻辑给单独的提取出来,而java中,动态代理则是面向切面编程的一个重要的实现手段。