Java静态代理、动态代理
代理是一个抽象的概念,简单理解就可以理解为在一个java类上去给它增加一些新的功能,但是却不用动原来的代码,在Java中分为静态代理和动态代理,然而动态代理又分为 jdk 动态代理和 cglib 动态代理,详情如下:
1、静态代理
方法:静态代理相当于是多写了一个代理类,在调用的时候调用的是代理类,在代理类中的处理还是原生的处理逻辑,不过在前后添加上需要添加的代码。
缺点:需要为每一个被代理的对象都创建一个代理类。
下面以购物举一个例子:
1、我们现在需要写一个接口去定义购物类的方法:
// 接口
public interface IShopping {
public void doShopping();
}
2、定义一个购物的实现类
public class Shopping implements IShopping {
private String name;
public Shopping(String name){
this.name = name;
}
@Override
public void doShopping() {
System.out.println(name + "买了火腿肠一个。");
System.out.println(name + "买了啤酒一罐。");
System.out.println(name + "买了剪刀一把。");
}
}
3、测试类直接运行
这里我们可以看出执行了实现类的代码
public class MainTest {
@Test
public void test01(){
IShopping shopping = new Shopping("小乐乐");
shopping.doShopping();
}
}
4、现在又有了新的需求,需要我们打印出,某人进入超市购物和离开超市的时间
由于现在我们用的是静态代理,所以我们需要写一个代理类,来包装之前的购物实现类,如下所示:
在这个类中我们可以发现,我们真正的执行逻辑还是之前的购物逻辑,不过在购物逻辑的前后添加了一写=些日志打印
public class ShoppingProxy implements IShopping {
private String name;
private Shopping shopping;
public ShoppingProxy(String name){
this.name = name;
this.shopping = new Shopping(name);
}
@Override
public void doShopping() {
System.out.println(name + "在" + currentTime() + "到达了超市。");
shopping.doShopping();
System.out.println(name+"到达了超市。");
System.out.println(name + "在" + currentTime() + "从超市出去。");
}
private String currentTime(){
//设置日期格式
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// new Date()为获取当前系统时间
return df.format(new Date());
}
}
5、测试类打印
没有动之前的逻辑,我们就实现了功能需求,但是缺点就是对于每一个需要代理的类我们都需要写一个新的代理类,太繁琐。
public class MainTest {
@Test
public void test01(){
IShopping shopping = new ShoppingProxy("小乐乐");
shopping.doShopping();
}
}
2、动态代理
2.1、jdk动态代理
Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
通过它创建动态的代理类,我们只需将别的类去调用我们实现的公共方法,就可以。
缺点:必须要实现接口。
1、首先我们先写一个接口,如下代码所示:
public interface ICalculator {
public Integer add(Integer i,Integer j);
public Integer sub(Integer i,Integer j);
public Integer mul(Integer i,Integer j);
public Integer div(Integer i,Integer j);
}
2、我们写一个需要被代理的类,这个被代理的类必须要实现上面的接口(因为jdk的动态代理是必须要实现接口的),如下所示:
public class Calculator implements ICalculator {
@Override
public Integer add(Integer i, Integer j) {
Integer result = i + j;
return result;
}
@Override
public Integer sub(Integer i, Integer j) {
Integer result = i - j;
return result;
}
@Override
public Integer mul(Integer i, Integer j) {
Integer result = i * j;
return result;
}
@Override
public Integer div(Integer i, Integer j) {
Integer result = i / j;
return result;
}
}
3、我们需要写一个代理打印日志的公共方法,这个公共方法将会给被代理的类弄一些屏障类似的东西,在执行真正的方法前后打印一些日志等等,可联系是spring中的aop进行理解,概念大致相似,代码如下所示:
public class MyInvocationHandler implements InvocationHandler {
private Object target;
private String prefix = "代理类打印:";
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String classAndMethod = prefix + "方法名:" + method.getName();
System.out.println(classAndMethod + ",开始执行:");
if(args == null || args.length == 0){
System.out.println(prefix + "没有参数");
}else{
System.out.println(prefix + "参数为:" + Arrays.asList(args));
}
/**
* 执行被代理的方法
* 参数
* Object obj 被代理的对象
* Object... args 被代理的方法参数,直接将args传进去
*/
Object result = method.invoke(target,args);
System.out.println(prefix + "返回值为:" + result);
System.out.println(classAndMethod + ",执行结束。");
return result;
}
}
4、接下来,我们就需要写我们真正的需要被代理的创建 jdk 动态代理的公共方法,然后需要打印日志的处理类指向第三步的方法即可,代码如下所示:
public class CommonProxy {
/**
* 公共jdk动态代理生成方法
* Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
* 上面这行代码就是创建一个 jdk 动态代理,我们现在只需要知道传入这三个参数即可
* 由于这里我们可以很明显的看见,参数需要一个接口
* 所以说, jdk 动态代理要求被代理的类得实现一个接口
* @param object
*/
public static Object createProxy(Object object){
/**
* ClassLoader loader 类加载器,通常指定的被代理类的接口的类加载器
* Class<?>[] interfaces 类型,通常用来被指定被代理类的接口的类型
* InvocationHandler h 委托执行的处理类:日志功能
*/
ClassLoader classLoader = object.getClass().getClassLoader();
Class<?>[] interfaces = object.getClass().getInterfaces();
InvocationHandler invocationHandler = new MyInvocationHandler(object);
// 动态创建代理类
Object o = Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
return o;
}
}
5、最后我们来写一个测试类,去动态给我们最开始定义的 Calculator 动态生成一个代理类,代码和运行截图如下所示:
public class MainTest {
@Test
public void test(){
ICalculator o = (ICalculator)CommonProxy.createProxy(new Calculator());
o.add(1,1);
}
}
2.2、cglib动态代理
对于cglib的动态代理,创建代理类的时候不需要传入接口参数,所以它不需要强制实现接口。
而是为被代理的对象创建一个子类,jdk 动态代理中的有些代码我们可以用到,这里面只需要改一下动态代理的公共方法和测试类即可。
1、创建动态代理公共方法并实现打印日志功能:
public class CglibCommonProxy implements MethodInterceptor {
/**
* 需要代理的目标对象
*/
private Object target;
public Object createProxy(Object objectTarget){
// 将传入进来的目标对象赋值给全局变量,在后续调用拦截方法时会用到
target = objectTarget;
// enhancer 字面意思,增强剂,也就是给被代理类搞一个屏障
Enhancer enhancer = new Enhancer();
//设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
enhancer.setSuperclass(objectTarget.getClass());
// 设置回调
enhancer.setCallback(this);
// 搞好这个代理类并返回
Object result = enhancer.create();
return result;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("Cglib动态代理,监听开始!");
//方法执行,参数:target 目标对象 objects 参数数组
Object invoke = method.invoke(target, objects);
System.out.println("Cglib动态代理,监听结束!");
return invoke;
}
}
2、测试类,测试类代码以及运行截图:
@Test
public void cglibTest(){
CglibCommonProxy cglibCommonProxy = new CglibCommonProxy();
ICalculator o = (ICalculator)cglibCommonProxy.createProxy(new Calculator());
o.add(1,1);
}