所有的努力,不是为了让别人觉得你了不起,而是为了能让自己打心底里看得起自己。
一、代理模式介绍
代理模式是设计模式中结构型模式的一种。当访问一个对象的时候因为一些原因不去访问这个对象,而是通过一个代理对象去访问这个对象,这个模式就是代理模式。
例如:购买火车票不去车站买,而是去代售点买票。
二、Java 中代理的三种方式
1、静态代理
静态代理的使用需要被代理的对象和代理对象拥有相同的方法,因此实现同一个接口是最好的做法。
下面演示通过代理对象打印被代理对象方法的入参和出参信息:
实现的接口:
/**
* @author ZhengNC
* @date 2020/7/1 10:12
*/
public interface Hello {
String hello(String name);
}
被代理对象:
/**
* @author ZhengNC
* @date 2020/7/1 10:12
*/
public class HelloImpl implements Hello {
@Override
public String hello(String name) {
System.out.println("执行 hello 方法。。。");
return "hello " + name;
}
}
Hello 的日志代理对象:
/**
* Hello 的日志代理
*
* @author ZhengNC
* @date 2020/7/1 10:14
*/
public class HelloLogProxy implements Hello {
private Hello target;
public HelloLogProxy(Hello target){
this.target = target;
}
@Override
public String hello(String name) {
System.out.println("入参:"+name);
String result = target.hello(name);
System.out.println("出参:"+result);
return result;
}
}
测试代码:
/**
* @author ZhengNC
* @date 2020/7/1 10:17
*/
public class Test {
public static void main(String[] args) {
//创建被代理的对象
Hello hello = new HelloImpl();
//创建代理对象
Hello helloLogProxy = new HelloLogProxy(hello);
//执行对象的方法
String result = helloLogProxy.hello("张三");
//打印执行方法的返回结果
System.out.println(result);
}
}
运行结果:
入参:张三
执行 hello 方法。。。
出参:hello 张三
hello 张三
静态代理的缺点很明显,就是如果被代理对象改变了,那么代理对象就要跟着改变。如果有很多需要被代理的对象,就要写很多代理类,非常不方便。
2、JDK 动态代理
JDK动态代理的特点:
- 代理类是利用JDK的API动态生成的。
- 不需要与被代理类实现同样的接口。
同样使用打印日志的例子演示动态代理的使用:
被代理对象实现的接口:
/**
* @author ZhengNC
* @date 2020/7/1 10:24
*/
public interface Hello {
String hello(String name);
}
被代理对象:
/**
* @author ZhengNC
* @date 2020/7/1 10:12
*/
public class HelloImpl implements Hello {
@Override
public String hello(String name) {
System.out.println("执行 hello 方法。。。");
return "hello " + name;
}
}
创建代理对象的工厂:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
/**
* 创建对象的日志代理
* 代理对象在执行时会打印出入参和出参信息
*
* @author ZhengNC
* @date 2020/7/1 10:26
*/
public class LogInvocation<T> implements InvocationHandler {
/**
* 被代理对象
*/
private T target;
public LogInvocation (T target){
this.target = target;
}
/**
*
* @param proxy 代理对象
* @param method 方法
* @param args 参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("入参:" + Arrays.toString(args));
Object result = method.invoke(target, args);
System.out.println("出参:" + result);
return result;
}
/**
* 根据目标对象类型创建代理对象
* @param implClass
* @param <T>
* @return 代理对象
*/
public static<T> T createProxy(Class implClass){
LogInvocation logInvocation = null;
try {
logInvocation = new LogInvocation(implClass.newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
T proxyObj = (T) Proxy.newProxyInstance(implClass.getClassLoader(), implClass.getInterfaces(), logInvocation);
return proxyObj;
}
}
测试代码:
/**
* @author ZhengNC
* @date 2020/7/1 10:47
*/
public class Test {
public static void main(String[] args) {
//创建代理对象
Hello proxyHello = LogInvocation.createProxy(HelloImpl.class);
//执行对象的方法
String result = proxyHello.hello("张三");
//打印方法执行的结果
System.out.println(result);
}
}
测试结果:
入参:[张三]
执行 hello 方法。。。
出参:hello 张三
hello 张三
JDK实现动态代理需要被代理对象实现接口。
3、CGLIB 动态代理
CGLIB 代理也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展。其底层是使用一个字节码框架ASM来转换字节码并生成新的类。
CGLIB 实现需要引入 CGLIB 的 jar 包,Spring 的核心包中已经包括了 CGLIB 的功能。
下面还是使用日志代理的例子演示 CGLIB 的代理实现:
被代理的类:
/**
* @author ZhengNC
* @date 2020/7/1 11:04
*/
public class Hello {
public String hello(String name) {
System.out.println("执行 hello 方法。。。");
return "hello " + name;
}
}
创建日志代理对象的工厂:
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* 创建对象的日志代理
* 代理对象在执行时会打印出入参和出参信息
*
* @author ZhengNC
* @date 2020/7/1 11:06
*/
public class LogProxyFactory<T> implements MethodInterceptor {
private T target;
public LogProxyFactory(T target){
this.target = target;
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("入参:" + Arrays.toString(objects));
Object result = method.invoke(target, objects);
System.out.println("出参:" + result);
return result;
}
public static<T> T createProxy(Class targetClass){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
try {
LogProxyFactory logProxyFactory = new LogProxyFactory(targetClass.newInstance());
enhancer.setCallback(logProxyFactory);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return (T) enhancer.create();
}
}
测试代码:
/**
* @author ZhengNC
* @date 2020/7/1 11:13
*/
public class Test {
public static void main(String[] args) {
//创建代理对象
Hello helloProxy = LogProxyFactory.createProxy(Hello.class);
//执行对象的方法
String result = helloProxy.hello("张三");
//打印结果
System.out.println(result);
}
}
测试结果:
入参:[张三]
执行 hello 方法。。。
出参:hello 张三
hello 张三
CGLIB 原理是创建对象的子类来实现代理。
因此被代理的类不能是 final 修饰的,如果使用 final 修饰类在运行时会报错。
被 final 或 static 修饰的方法也不能被代理,虽然不会报错,但是代理增强的功能会失效。