1.改造过后的小型购物网站的问题
改造过后的小型购物网站成功解决了耦合的问题,但是在很多地方仍然存在非该层应该实现的功能,造成了无法"高内聚"的现象
,同时存在大量重复代码,开发效率低下。比如在service层添加一些处理事务的代码,service层中的每一个方法都需要处理相同的事务,而事务处理不属于service层应该做的工作。此时可以通过代理设计模式,将这部分代码提取到代理者中,简化层中的代码。
2.静态代理模式
例子:
明星在成名之前:谁都可以找他吃饭,唱歌。明星成名之后,找了一个经济人,别人如果再想找明星吃饭,就得找经济人。经纪人就是明星的代理人,他会检验来访人的身份,看是否有资格(比如来访人是明星的朋友等等)与明星吃饭,然后再查看一下明星的日程,看明星是否有时间,如果明星有时间并且来访人与明星吃饭,那么经纪人就会安排来访人与明星吃饭,最后经纪人记录来访人的来访信息。
由上述可知,经纪人增强了明星的能力:对明星原来的吃饭和唱歌的功能进行了增强。即:在原有的功能的基础上增加了额外的
效果。这,就是代理设计模式的本质。所以,代理设计模式其实是增强一个对象功能的一种方式。
案例:
写一个明星类,一个经纪人类,他们都有相同的方法,所以写一个接口来让这两个类实现这个接口。这样他们就有了相同的方法
,但是经济人是不能和来访人吃饭唱歌的,所以他还得找明星,因此经纪人类中得有明星类对象。 然后经纪人类中的吃饭、唱歌
等方法,实际上调用的是明星类中的吃饭和唱歌的方法,我们可以在经济人的吃饭等方法中添加一些经济人所特有的功能,比如
验证身份,记录来访人信息等。
以上就是一个简单的静态代理设计模式,它有如下特点:
优点:结构清晰,易于理解
缺点:如果被代理者有多个方法,则代理者也需要开发多个方法,其中往往存在大量重复的代码(比如明星每有一个方法,经纪人
都需要验证身份、记录信息等等),仍然存在代码重复。
总结:静态代理模式解决了软件分层过程中额外的功能的代码侵入模块的问题,将额外的功能代码提取到了代理者中进行,但是
静态代理实现的代理者中存在大量重复的代码,并没有解决代码重复的问题。所以在真正开发中--包括spring底层,基本不会使
用静态代理。
3.动态代理模式-jdk内置的动态代理
在jdk中提供了动态代理实现的工具类-->Proxy(在java.lang.reflect 包下),直接使用该工具类中的newProxyInstance方法就可以创建出代理者,并且可以通过内置的回调函数,指定代理在工作时的执行逻辑,从而开发出基于jdk原生的动态代理。
static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。(说人话就是上面的方法可以给你提供一个代理)
其中的参数介绍:
1)loader:类加载器
.java-编译器-> .class -类加载器->字节码
这个方法底层会动态的生成一个代理,而这个过程中需要用到类
加载器
建议使用被代理类的类加载器,如:
fbb.getClass().getClassLoader()或
FBB.class.getClassLoader()
2)interfaces:要求生成的代理者实现的接口们,通常就是实现
和被代理者相同的接口,保证具有和被代理者相同的方法
3)invocationHandler:用来设定回调函数的回调接口,使用者需
要写一个类实现此接口(在此我们使用一个匿名内部类),从而实
现其中的invoke方法,在其中编写代码处理代理者调用方法时的
回调过程,通常在这里调用真正对象身上的方法,并且在方法之
前或之后做额外的操作。
@Override
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable {
//内部类访问外部类的变量,外部类的变量必须是final的,即被代理类类型的外部类变量必须是final的
//invoke方法有返回值,即:代理方法的返回值与被代理的类的方法的返回值相同
Object returnObj = method.invoke(被代理类的对象, args);
return returnObj;
}
参数解释:
proxy:代理者
method:当前调用的方法对象(反射:Method是代表方法的类,其中有一个invoke(Object obj,Object...args)方法,让这个方法在某个对象身上以什么参数去执行,实际上就像是调用被代理对象身上拥有的方法)
args:当前被调用的方法的参数数组
java动态代理的特点:
优点:
不需要像静态代理一样把被代理的方法都要实现一遍,而只需要在回调函数中进行处理就可以了,重复代码只需要编写一次。
缺点:
java的动态代理是通过代理者实现和被代理者实现相同的接口来保证两者具有相同的方法的,如果被代理者想要被代理的方法不属于任何接口,则生成的代理者自然无法具有这个方法
,也就无法实现对该方法的处理。所以java的动态代理机制,受制于是否有接口的存在。
4.动态代理模式-第三方包cglib实现的动态代理
CGLIB是第三方提供的动态代理的实现工具 ,不管有没有接口都可以实现
CGLIB实现动态代理的原理是生成的动态代理是被代理者的子类,所以代理者具有和父类即被代理者相同的方法,从而完成代理。
案例:
1)导入CGLIB相关包--->存在于spring-corexxx.jar包中
2)开发CGLIB程序
public class CglibProxyTest {
@Test
public void test01(){
final 被代理类 对象名 = new 被代理类();
//增强器-->因为动态代理的功能就是增强原有的对象
Enhancer enhancer = new Enhancer();
//设定接口 -- 此方法要求生成的动态代理额外实现指定接口们 ,单cglib动态代理不是靠接口实现的,所以可以不设置
enhancer.setInterfaces(fbb.getClass().getInterfaces());
//设定父类 -- 此处要传入被代理者的类,cglib是通过集成被代理者的类来持有和被代理者相同的方法的,此方法必须设置
enhancer.setSuperclass(fbb.getClass());
//设定回调函数 -- 为增强器设定回调函数,之后通过增强器生成的代理对象调用任何方法都会走到此回调函数中,实现调用真正被代理对象的方法的效果
//MethodInterceptor()--->方法拦截器,用来拦截被代理类中的方法
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
System.out.println("检查权限。。。");
Object returnObj = method.invoke(对象名, args);
System.out.println("记录日志。。。");
return returnObj;
}
}
});
//生成代理对象
被代理类 proxy = (被代理类) enhancer.create();
proxy.被代理类中的方法名();
}
}