1.静态代理模式:
package cn.tedu.staticproxy;
public interface SJSkill {
public void 吃();
public void 唱歌();
}
package cn.tedu.staticproxy;
public class FBB implements SJSkill{
public void 吃(){
System.out.println("fbb吃饭。。。");
}
public void 唱歌(){
System.out.println("fbb唱歌。。。");
}
}
package cn.tedu.staticproxy;
public class JJRStaticProxy implements SJSkill{
private FBB fbb = new FBB();
@Override
public void 吃() {
System.out.println("权限认证:你谁啊????");
fbb.吃();
System.out.println("记录日志:等我,我记一下来访记录");
}
@Override
public void 唱歌() {
System.out.println("权限认证:你谁啊????");
fbb.唱歌();
System.out.println("记录日志:等我,我记一下来访记录");
}
}
package cn.tedu.staticproxy;
import org.junit.Test;
public class StaticProxyTest {
@Test
public void test01(){
JJRStaticProxy jjr = new JJRStaticProxy();
jjr.吃();
jjr.唱歌();
}
}
静态代理设计模式特点:
优点:结构清晰 易于理解
缺点:如果被代理者有多个方法,则代理者也需要开发多个方法,其中往往存在大量重复的代码。仍然存在代码的复用
静态代理设计模式解决了软件分层过程中 额外的功能代码侵入模块的问题,将额外的功能代码提取到了代理者中进行,但是静态代理实现的代理者中存在大量重复的代码,并没有解决代码重复问题。所以在真正开发中--包括spring的底层,基本不会使用静态代理
2.动态代理 - jdk内置的动态代理
在jdk中提供了动态代理实现工具类,直接使用该工具类就可以创建出代理者,并且可以通过内置的回调函数指定代理工作时的执行逻辑,从而实现基于jdk原生api的动态代理机制。
java.lang.reflect
类 Proxy
java.lang.Object
static Object | newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。 |
案例:
package cn.tedu.javaproxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import org.junit.Test;
public class JavaProxyTest {
@Test
public void test01(){
//被代理者
final FBB fbb = new FBB();
//java动态代理方式 生成fbb的代理者
/**
* classLoader:用来生成代理者类的类加载器,通常可以传入被代理者类的类加载器
* interfaces: 要求生成的代理者实现的接口们,通常就是实现和被代理者相同的接口,保证具有和被代理者相同的方法
* invocationHandler: 用来设定回调函数的回调接口,使用者需要写一个类实现此接口,从而实现其中的invoke方法,
* 在其中编写代码处理代理者调用方法时的回调过程,通常在这里调用真正对象身上的方法,并且在方法之前或之后做额外操作。
*/
SJSkill proxy = (SJSkill) Proxy.newProxyInstance(FBB.class.getClassLoader(),FBB.class.getInterfaces()
,new InvocationHandler() {
@Override
/**
* proxy: 代理者
* method:当前调用的方法对象
* args:挡墙调用的方法的参数数组
*/
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
if("拍电影".equals(method.getName())){
System.out.println("不好意思,给多少钱不拍了~~");
return null;
}else{
System.out.println("检验权限。。。。");
//真正地FBB执行的方法
Object returnObj = method.invoke(fbb, args);
System.out.println("记录日志。。。。");
return returnObj;
}
}
});
//从此之后,不允许直接调用被代理者身上的方法,而是要通过代理者来调用
//fbb.吃();
//fbb.唱歌();
//无论调用代理者身上的哪个方法,都会走到invoke方法中
proxy.吃();
proxy.唱歌();
proxy.拍电影();
}
}
java动态代理的原理图:
java动态代理的特点:
优点:不需要像静态代理一样被代理者都要实现一遍,而只需要在回调函数中进行处理就可以了,重复的代码只需要编写一次。
缺点:java的动态代理是通过代理者实现和别代理者相同接口来保证两者具有相同的方法的,如果被代理者想要被代理的方法不属于任何接口,则生成的代理者自然无法具有这个方法,也就无法实现对该方法的代理。所以java的动态代理机制是基于接口进行的,受制于要代理的方法是否有接口的支持。
3.动态代理-第三方包cglib实现的动态代理
CGLIB是第三方提供的动态代理的实现工具,不管有没有接口都可以实现动态代理。
CGLIB实现动态代理的原理是 生成的动态代理是被代理者的子类,所以代理者具有和父类即被代理者 相同的方法,从而实现代理。
导入CGLIB相关包
之前导入的spring包中就包含了CGLIB
spring-core-3.2.3.RELEASE.jar
开发CGLIB程序
案例:
package cn.tedu.cglibproxy;
import java.lang.reflect.Method;
import org.junit.Test;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CglibProxyTest {
@Test
public void test01(){
final FBB fbb = new FBB();
//增强器
Enhancer enhancer = new Enhancer();
//设定接口 -- 此方法要求生成的动态代理额外实现指定接口们 ,单cglib动态代理不是靠接口实现的,所以可以不设置
enhancer.setInterfaces(fbb.getClass().getInterfaces());
//设定父类 -- 此处要传入被代理者的类,cglib是通过集成被代理者的类来持有和被代理者相同的方法的,此方法必须设置
enhancer.setSuperclass(fbb.getClass());
//设定回调函数 -- 为增强器设定回调函数,之后通过增强器生成的代理对象调用任何方法都会走到此回调函数中,实现调用真正被代理对象的方法的效果
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
if("拍电影".equals(method.getName())){
System.out.println("对不起,不拍了~~~");
return null;
}else{
System.out.println("检查权限。。。");
Object returnObj = method.invoke(fbb, args);
System.out.println("记录日志。。。");
return returnObj;
}
}
});
//生成代理对象
FBB proxy = (FBB) enhancer.create();
proxy.吃();
proxy.唱歌();
proxy.拍电影();
}
}
CGLIB动态代理原理图:
CGLIB动态代理的特点:
优点:无论是否有接口都可以实现动态代理,使用场景基本不受限
缺点:第三方提供的动态代理机制,不是原生的,需要导入第三方开发包才可以使用。