动态代理设计模式简介
想要了解动态代理必须先了解代理设计模式。
在我们完成业务层的代码时,我们总是会分为:
业务核心代码和业务辅助性的代码;
业务核心代码:缺少了它,就不能完成业务操作,他是我们完成业务的最基础的代码,不可缺少。
辅助性代码:例如我们的事务控制,生成日志,性能测试代码等,缺少了这些代码,我们的业务仍然可以完成。
如果我们将每个业务逻辑都添加这些辅助性代码,这就造成了代码冗余,并且后期维护时如果修改这些辅助性代码,那么将修改所有业务方法,毫无疑问大大增加了维护难度,为了解决这个问题,代理设计模式诞生了。
我们看下面一段代码
首先我们定义一个接口,这个接口可以看做是法院提供给被告的一个用来辩护的接口,当然里面应该规定一个方法,那就是辩护的方法
public interface Defender {
void defend();//法院规定的被告人辩护的方法。
}
我们再来定义一个Person类,这个类有名字属性,并且实现了法院规定的Defender接口
我们可以把这个人看成一个被举报偷东西的人,他要在法庭上为自己辩护,因此要实现Defender接口
public class Person implements Defender{
private String name ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person() {
}
public Person(String name) {
this.name = name;
}
//这个人为自己辩护说自己没偷东西
public void defend(){
System.out.println(name+" : 我没有偷东西");
}
}
更保险的方式就是他请一个专业性更强的律师为自己辩护,这样成功的概率会增大。
重点来了
这个律师就可以看成是这个被告人的一个代理,如果这个律师想要帮被告人辩护,也就必须实现法院规定的Defender接口
我们继续思考以下问题:
既然律师帮别人辩护,那么每个上法庭的律师必然会对应一个客户,这个客户就是被告人。所以,我们将如下涉及Lawyer类:
1.他必须实现法院规定的Defender接口
2.每个律师都对映一个客户(被告人),所以我们将被告人设计成这个类的成员变量。
public class Lawyer implements Defender{
/*当然按照我们的分析来说,这里的成员变量应该是 Persion p;
但是我们将它定义成了Defender,这是因为需要辩护的可能不仅仅只有我们自己写的Person类,
还有可能其他Defender的实现类需要辩护(只要需要辩护就必须实现法院规定的Defender接口),
因此我们将这里的成员变量定义成了Defender类型的更合理。
当然我们定义成Person p;也无伤大雅。
*/
private Defender defender;
public Lawyer() {
}
public Lawyer(Defender defender) {
this.defender = defender;
}
public Defender getDefender() {
return defender;
}
public void setPerson(Defender defender) {
this.defender = defender;
}
// 律师辩护完,被告人自己还要为自己辩护
//因此律师执行完自己的职责以后,需要Defender对象自己再辩护一次
(这也是最重要最核心的一步,不可缺少的一步,如果缺少的这一步,那么代理设计模式就没有意义)
@Override
public void defend() {
//律师的各种论证
System.out.println("@#¥¥52!#¥@#¥……%@#&@@¥@\n因此我可以证明他没偷东西");
//被告人自己陈述。最重要的一步!不可或缺的一步
defender.defend();
}
}
我们也可以来演示一下结果。
public class Court {
//在法庭上,被告方开始辩护。
public static void main(String[] args) {
//我们无法确定通过工厂获得的实现类到底是Person在辩护,
//还是被告人的代理(Lawyer)在辩护。
getDefender(0).defend();
}
/*
这里我做了一个很粗糙的工厂,屏蔽了实现类的差异
*/
public static Defender getDefender(int i){...}
}
我们来看一下运行结果:
其实{…}里面的代码是这样的(皮一下)
if (i==0) return new Lawyer(new Person("包子"));
return new Person("包子");
代理模式算是写完了,我们也称这种方式为静态代理。试想一下,如果我们的接口有很多方法,那么我们的代理类就要实现接口里面的所有方法。
更可怕的是,如果我们有很多的接口,而这个接口又有很多实现类,那么我们不得不为每个实现类都写一个代理类,这无疑也是一种代码冗余,因此,java语言给我们提供了一种动态代理技术。
动态代理
静态代理的代理类在程序运行之前是已经写好的。在程序运行时通过类加载把静态代理类加载到虚拟机中。
但是动态代理不一样,他是程序运行时候动态的生成的代理类。这个类没有.java文件,也没有.class文件,当然他也没有ClassLoder,因此我们在获得这个动态代理类时候,需要指定类加载器,以便以后这个类能够正常使用。以下就是动态代理的获得方法。
public static Object getDynamicProxy(final Object target){//target在匿名内部类中被访问,因此需要指定为final
Object newProxyInstance = Proxy.newProxyInstance(
target.getClass().getClassLoader(),//手动置顶类加载器,以便动态代理类能够正常使用
target.getClass().getInterfaces(),//指定这个代理类所实现的接口
//决定了代理类所增加的方法
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("#$@%$&^*%&(#$%^@^$*^%\n因此他没有偷东西");//律师的辩护
method.invoke(target,args);//通过反射调用被告人的辩护方法
return null;
}
});
return newProxyInstance;
}
同样,创建动态代理也需要指定一个客户,那就是target参数。接下来我们来测试一下这个代理类的效果:
public static void main(String[] args) {
Person person = new Person("包子");//创建客户
Defender dynamicProxy = (Defender) getDynamicProxy(person);获得代理类(相当于律师)
dynamicProxy.defend();
}
和静态代理类的效果一样。
我们再创建一个新的需要辩护的实现类(实现Defender接口的实现类),并将这个新的类的对象传给getDynamicProxy方法,结果应该是一样的。接下来我们用代码验证。
我们使用匿名内部类直接在参数表中创建一个新的Defender实现类:
public static void main(String[] args) {
Defender dynamicProxy = (Defender) getDynamicProxy(new Defender() {
@Override
public void defend() {
System.out.println("史BR:我真的没有穿品如的衣服!!!");//新的客户需要的辩词
}
});
dynamicProxy.defend();
}
接下来我们来看看结果:
我们发现获取动态代理对象的方法适用于任何一个Defender的实现类。
所以,动态代理比静态代理相比,不必为每一个实现类都创建一个代理类。
动态代理的不足之处
尽管动态代理与静态代理相比有很多的优点,但是他仍然有一些不足之处。
我们发现,当我们对动态代理对象调用接口中的每一个方法的时候,都会为我们接口中的方法添加额外的功能。而那些我们不需要添加额外方法的不可避免的被动态代理添加了额外功能,这是我们不想遇见的。因此,我们引入了AOP(面向切面)编程思想,来有选择的为我们需要添加额外功能的方法添加额外的功能。这也是Spring框架中的重要组成部分。
关于AOP编程,我将会在下一篇中介绍。
如有不足之处,请大家批评指正!共同进步!