java设计模式之代理模式详解
代理模式介绍
在某些开发场景下,一个类不能直接引用一个类,于是在这个中间产生了代理类,代理类能够代表被代理对象执行方法增强等相关操作,起到一个中介的作用。
代理模式中一般有三种角色:
- 抽象主题角色,是代理类和代理类实现的共同规范,可以是接口也可以抽象类。
- 真实主题角色,又叫做被代理类,业务中主要逻辑都是在这里面实现的。
- 代理主题角色,又叫做代理类,一般的代理类会在代理类的方法前后进行完善操作,具备加强方法的功能。
代理类属于结构型模式,分为动态代理和静态代理。
代理模式的应用场景
代理模式在生活中很常见,比如说司机,婚介,房产中介,卖票黄牛等,在程序中也比较常见,比如说事物管理,日志监听。一般是代理对象和被代理对象在无法直接或者不方便直接建立联系的情况下使用。
使用代理模式,一般有两个作用,一是保护对象,二是增强对象。
代理模式的简单写法
先创建一个抽象主题角色,也就是真实主题角色和代理主题角色需要共同实现的接口规范
public interface ISubject {
void request();
}
创建真实主题角色,也就是被代理对象
public class RealSubject implements ISubject {
public void request() {
System.out.println("real service is called.");
}
}
创建代理主题角色,也就是代理对象
public class Proxy implements ISubject {
private ISubject subject;
public Proxy(ISubject subject){
this.subject = subject;
}
public void request() {
before();
subject.request();
after();
}
public void before(){
System.out.println("called before request().");
}
public void after(){
System.out.println("called after request().");
}
}
使用客户端创建代理主题角色调用真实主题角色的被代理方法。
public class Client {
public static void main(String[] args) {
Proxy proxy = new Proxy(new RealSubject());
proxy.request();
}
}
运行代码结果
可以发现,利用代理主题角色调用真实主题角色的方法完全执行到位了,代理主题角色具备真实主题角色的全部功能,并且可以根据业务需求在代理方法前后进行功能加强。
静态代理
现在有一个场景,目前社会上有很多单身男女,年纪大了需要找对象,现在就有一青年张三年龄到了,但是一直没有对象。张三本人的要求是肤白貌美大长腿,因此他本人不急,不达到标准不结婚。但是张老三很急,于是张老三开始给张三物色对象。
这就是一个很经典的代理模式的场景,因为张老三和张三都是人,所以我们先定义一个抽象主题角色Person接口
public interface IPerson {
void findLove();
}
创建真实主题角色张三
public class ZhangSan implements IPerson {
public void findLove() {
System.out.println("儿子要求:肤白貌美大长腿");
}
}
创建代理主题角色张老三
public class ZhangLaosan implements IPerson {
private ZhangSan zhangsan;
public ZhangLaosan(ZhangSan zhangsan) {
this.zhangsan = zhangsan;
}
public void findLove() {
System.out.println("张老三开始物色");
zhangsan.findLove();
System.out.println("开始交往");
}
}
创建客户端测试张老三替张三找对象。
public class Test {
public static void main(String[] args) {
ZhangLaosan zhangLaosan = new ZhangLaosan(new ZhangSan());
zhangLaosan.findLove();
}
}
运行代码,输出结果
可以发现张老三在替张三找对象的过程中进行了功能增强,这种针对于某一个具体代理对象的代理叫做静态代理。
动态代理
静态代理可以给指定对象进行代理,进行功能增强。但是有些情况下我们需要给一类对象进行代理,并且进行定制化服务。比如说媒婆需要给所有单身群体进行物色对象,而不是针对于某一个具体的人服务。这种情况下我们就要使用动态代理模式,动态代理底层很复杂,但是我们一般不需要关注底层api实现,java已经给我们封装好了实现。目前最常见的动态代理有基于jdk的动态代理和cglb的类库。
jdk动态代理
实现方式
创建一个类实现InvocationHandler 接口并且重写invoke方法作为代理类,编写一个获取实例的方法,实例的入参和出参都必须是抽象主题角色,实际上是一种将真实主题角色传入该方法然后利用Proxy.newProxyInstance方法构造获取代理对象,代理对象的方法增强功能在代理类的invoke方法中实现,代理对象调用真实主题角色的方法时实际上就是出发代理主题角色的invoke方法。
代码示例
我们可以用jdk的动态代理先来实现媒婆替人物色对象这一个过程。IPerson接口和Zhangsan这个对象和上面保持不变。
我们来创建一个动态代理对象媒婆
public class JdkMeipo implements InvocationHandler {
private IPerson target;
public IPerson getInstance(IPerson target){
this.target = target;
Class<?> clazz = target.getClass();
return (IPerson) Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(this.target,args);
after();
return result;
}
private void after() {
System.out.println("双方同意,开始交往");
}
private void before() {
System.out.println("我是媒婆,已经收集到你的需求,开始物色");
}
}
在创建一个要相亲的对象赵六,赵六的要求是有车有房学历高
public class ZhaoLiu implements IPerson {
public void findLove() {
System.out.println("赵六要求:有车有房学历高");
}
public void buyInsure() {
}
}
此时张三和赵六都是代理主题角色,我们来创建一个客户端来模拟媒婆替张三和赵六找对象的过程。
public class Test {
public static void main(String[] args) {
JdkMeipo jdkMeipo = new JdkMeipo();
IPerson zhangsan = jdkMeipo.getInstance(new Zhangsan());
zhangsan.findLove();
IPerson zhaoliu = jdkMeipo.getInstance(new ZhaoLiu());
zhaoliu.findLove();
}
}
运行结果:
可以发现,媒婆这一对象可以为所有人类找对象,这就是动态代理的优点:可以为一类对象动态创建代理对象。
静态代理在业务中的应用
我们知道在实际业务场景中数据量一多就要进行分库分表,所以我们就要通过设置数据源来切换数据源,我们来模拟一波切换数据源的实例。
先创建一个订单类
public class Order {
private Object orderInfo;
//订单创建时间进行按年分库
private Long createTime;
private String id;
public Object getOrderInfo() {
return orderInfo;
}
public void setOrderInfo(Object orderInfo) {
this.orderInfo = orderInfo;
}
public Long getCreateTime() {
return createTime;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
创建持久层dao对象
public class OrderDao {
public int insert(Order order){
System.out.println("OrderDao创建Order成功!");
return 1;
}
}
创建业务层service对象的实现接口IOrderService
public interface IOrderService {
int createOrder(Order order);
}
创建service实现接口对象
public class OrderService implements IOrderService {
private OrderDao orderDao;
public OrderService(){
//如果使用Spring应该是自动注入的
//我们为了使用方便,在构造方法中将orderDao直接初始化了
orderDao = new OrderDao();
}
public int createOrder(Order order) {
System.out.println("OrderService调用orderDao创建订单");
return orderDao.insert(order);
}
}
以上就是一个很经典的三层架构例子,我们来根据订单的时间来动态设置数据源。使用静态代理实现。
先创建一个动态数据源类,用ThreadLocal实现。
public class DynamicDataSourceEntity {
public final static String DEFAULE_SOURCE = null;
private final static ThreadLocal<String> local = new ThreadLocal<String>();
private DynamicDataSourceEntity(){}
public static String get(){return local.get();}
public static void restore(){
local.set(DEFAULE_SOURCE);
}
//DB_2018
//DB_2019
public static void set(String source){local.set(source);}
public static void set(int year){local.set("DB_" + year);}
}
使用静态代理创建订单业务实现类的代理类
public class OrderServiceStaticProxy implements IOrderService {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
private IOrderService orderService;
public OrderServiceStaticProxy(IOrderService orderService) {
this.orderService = orderService;
}
public int createOrder(Order order) {
Long time = order.getCreateTime();
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据" );
DynamicDataSourceEntity.set(dbRouter);
this.orderService.createOrder(order);
DynamicDataSourceEntity.restore();
return 0;
}
}
测试静态代理
public class DbRouteProxyTest {
public static void main(String[] args) {
try {
Order order = new Order();
// order.setCreateTime(new Date().getTime());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2020/03/01");
order.setCreateTime(date.getTime());
// IOrderService orderService = (IOrderService)new OrderServiceDynamicProxy().getInstance(new OrderService());
IOrderService orderService = (IOrderService)new OrderServiceStaticProxy(new OrderService());
orderService.createOrder(order);
}catch (Exception e){
e.printStackTrace();
}
}
}
输出结果
上面的订单业务实现类是对创建订单进行方法增强的。在创建订单之前,先设置好数据源,创建订单之后,数据源重新设置为默认值。数据源设置类采用线程单例ThreadLocal实现。
调用类图如下:
动态代理在业务实践中的应用
上面的根据订单时间设置数据源的例子,我们也可以基于动态代理实现。
首先定义订单业务实现类的动态代理对象实现InvocationHandler接口,提供获取代理对象的方法,重写invoke方法,在invoke方法中做方法增强
public class OrderServiceDynamicProxy implements InvocationHandler {
private SimpleDateFormat yearFormat = new SimpleDateFormat("yyyy");
Object proxyObj;
public Object getInstance(Object proxyObj) {
this.proxyObj = proxyObj;
Class<?> clazz = proxyObj.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(),clazz.getInterfaces(),this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before(args[0]);
Object object = method.invoke(proxyObj,args);
after();
return object;
}
private void after() {
System.out.println("Proxy after method");
//还原成默认的数据源
DynamicDataSourceEntity.restore();
}
//target 应该是订单对象Order
private void before(Object target) {
try {
//进行数据源的切换
System.out.println("Proxy before method");
//约定优于配置
Long time = (Long) target.getClass().getMethod("getCreateTime").invoke(target);
Integer dbRouter = Integer.valueOf(yearFormat.format(new Date(time)));
System.out.println("静态代理类自动分配到【DB_" + dbRouter + "】数据源处理数据");
DynamicDataSourceEntity.set(dbRouter);
}catch (Exception e){
e.printStackTrace();
}
}
}
客户端测试代码
public class DbRouteProxyTest {
public static void main(String[] args) {
try {
Order order = new Order();
// order.setCreateTime(new Date().getTime());
SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd");
Date date = sdf.parse("2020/03/01");
order.setCreateTime(date.getTime());
IOrderService orderService = (IOrderService)new OrderServiceDynamicProxy().getInstance(new OrderService());
// IOrderService orderService = (IOrderService)new OrderServiceStaticProxy(new OrderService());
orderService.createOrder(order);
}catch (Exception e){
e.printStackTrace();
}
}
}
运行结果
使用动态代理的好处是相较于静态代理可以为一切真实主题角色创建代理对象,而不局限于某一种群体,在实际业务开发中,我们用动态代理的地方较多。
手写jdk动态代理实现逻辑
上面我们已经了解了jdk动态代理可以灵活创建所有对象的代理对象,前提是被代理对象需要实现一个接口。那么通过这种方式创建代理对象的底层逻辑是什么嘞,我们通过手写一个自定义jdk动态代理实现来分析一下这个过程。
首先自定义一个处理器GPInvocationHandler
public interface GPInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
定义类加载器对象,加载class类到内存中
public class GPClassLoader extends ClassLoader {
private File classPathFile;
public GPClassLoader(){
String classPath = GPClassLoader.class.getResource("").getPath();
this.classPathFile = new File(classPath);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = GPClassLoader.class.getPackage().getName() + "." + name;
if(classPathFile != null){
File classFile = new File(classPathFile,name.replaceAll("\\.","/") + ".class");
if(classFile.exists()){
FileInputStream in = null;
ByteArrayOutputStream out = null;
try{
in = new FileInputStream(classFile);
out = new ByteArrayOutputStream();
byte [] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1){
out.write(buff,0,len);
}
return defineClass(className,out.toByteArray(),0,out.size());
}catch (Exception e){
e.printStackTrace();
}
}
}
return null;
}
}
定义proxy工具类
public class GPProxy {
public static final String ln = "\r\n";
public static Object newProxyInstance(GPClassLoader classLoader, Class<?> [] interfaces, GPInvocationHandler h){
try {
//1、动态生成源代码.java文件
String src = generateSrc(interfaces);
// System.out.println(src);
//2、Java文件输出磁盘
String filePath = GPProxy.class.getResource("").getPath();
// System.out.println(filePath);
File f = new File(filePath + "$Proxy0.java");
FileWriter fw = new FileWriter(f);
fw.write(src);
fw.flush();
fw.close();
//3、把生成的.java文件编译成.class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manage = compiler.getStandardFileManager(null,null,null);
Iterable iterable = manage.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null,manage,null,null,null,iterable);
task.call();
manage.close();
//4、编译生成的.class文件加载到JVM中来
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
f.delete();
//5、返回字节码重组以后的新的代理对象
return c.newInstance(h);
}catch (Exception e){
e.printStackTrace();
}
return null;
}
private static String generateSrc(Class<?>[] interfaces){
StringBuffer sb = new StringBuffer();
sb.append(GPProxy.class.getPackage() + ";" + ln);
sb.append("import " + interfaces[0].getName() + ";" + ln);
sb.append("import java.lang.reflect.*;" + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getName() + "{" + ln);
sb.append("GPInvocationHandler h;" + ln);
sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
sb.append("this.h = h;");
sb.append("}" + ln);
for (Method m : interfaces[0].getMethods()){
Class<?>[] params = m.getParameterTypes();
StringBuffer paramNames = new StringBuffer();
StringBuffer paramValues = new StringBuffer();
StringBuffer paramClasses = new StringBuffer();
for (int i = 0; i < params.length; i++) {
Class clazz = params[i];
String type = clazz.getName();
String paramName = toLowerFirstCase(clazz.getSimpleName());
paramNames.append(type + " " + paramName);
paramValues.append(paramName);
paramClasses.append(clazz.getName() + ".class");
if(i > 0 && i < params.length-1){
paramNames.append(",");
paramClasses.append(",");
paramValues.append(",");
}
}
sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "(" + paramNames.toString() + ") {" + ln);
sb.append("try{" + ln);
sb.append("Method m = " + interfaces[0].getName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{" + paramClasses.toString() + "});" + ln);
sb.append((hasReturnValue(m.getReturnType()) ? "return " : "") + getCaseCode("this.h.invoke(this,m,new Object[]{" + paramValues + "})",m.getReturnType()) + ";" + ln);
sb.append("}catch(Error _ex) { }");
sb.append("catch(Throwable e){" + ln);
sb.append("throw new UndeclaredThrowableException(e);" + ln);
sb.append("}");
sb.append(getReturnEmptyCode(m.getReturnType()));
sb.append("}");
}
sb.append("}" + ln);
return sb.toString();
}
private static Map<Class,Class> mappings = new HashMap<Class, Class>();
static {
mappings.put(int.class,Integer.class);
}
private static String getReturnEmptyCode(Class<?> returnClass){
if(mappings.containsKey(returnClass)){
return "return 0;";
}else if(returnClass == void.class){
return "";
}else {
return "return null;";
}
}
private static String getCaseCode(String code,Class<?> returnClass){
if(mappings.containsKey(returnClass)){
return "((" + mappings.get(returnClass).getName() + ")" + code + ")." + returnClass.getSimpleName() + "Value()";
}
return code;
}
private static boolean hasReturnValue(Class<?> clazz){
return clazz != void.class;
}
private static String toLowerFirstCase(String src){
char [] chars = src.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
}
修改订单代理实现类代理对象,让其实现自定义的InvocationHandler,重写Invoke方法,在invoke前后进行方法进行方法增强处理。
重新测试,可以发现结果符合我们的预期。
总结一下,jdk动态代理有5个步骤
- 根据真实主题角色实现的接口动态生成代理类的java文件
- 将代理类的java文件加载到磁盘中
- 使用编译工具将java类转换成代理类的class对象
- 将class对象加载到jvm中
- 用代理对象的构造方法反射产生新对象返回
cglib动态代理
除了jdk动态代理之外,还有一种方式可以实现动态代理,那就是cglib类库提供的动态代理实现。
还是以媒婆为例子,我们来看一下cglib的动态代理。
首先创建真实主题角色儿子
public class Customer {
public void findLove(){
System.out.println("儿子要求:肤白貌美大长腿");
}
}
cglib动态代理不需要定义抽象主题角色,也就是真实主题角色和代理主题角色所要共同实现的接口。所以我们来直接创建一个代理主题角色媒婆。
public class CGlibMeipo implements MethodInterceptor {
public Object getInstance(Class<?> clazz) throws Exception{
//相当于Proxy,代理的工具类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object obj = methodProxy.invokeSuper(o,objects);
after();
return obj;
}
private void before(){
System.out.println("我是媒婆,我要给你找对象,现在已经确认你的需求");
System.out.println("开始物色");
}
private void after(){
System.out.println("OK的话,准备办事");
}
}
创建客户端进行测试
public class CglibTest {
public static void main(String[] args) {
try {
//JDK是采用读取接口的信息
//CGLib覆盖父类方法
//目的:都是生成一个新的类,去实现增强代码逻辑的功能
//JDK Proxy 对于用户而言,必须要有一个接口实现,目标类相对来说复杂
//CGLib 可以代理任意一个普通的类,没有任何要求
//CGLib 生成代理逻辑更复杂,效率,调用效率更高,生成一个包含了所有的逻辑的FastClass,不再需要反射调用
//JDK Proxy生成代理的逻辑简单,执行效率相对要低,每次都要反射动态调用
//CGLib 有个坑,CGLib不能代理final的方法
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"D://cglib_proxy_classes");
Customer obj = (Customer) new CGlibMeipo().getInstance(Customer.class);
System.out.println(obj);
obj.findLove();
} catch (Exception e) {
e.printStackTrace();
}
}
}
jd-jui使用
我们运行上面的客户端测试代码,可以发现生成cglib动态代理类的过程中,由于我们将代理类输出到了磁盘中,可以看到磁盘中出现了三个class文件。
我们要看java形式的代码,就必须要进行反编译操作,这里我们选择jd-jui进行反编译。
官网下载链接:http://jd.benow.ca/
下载完成之后打开,将需要反编译的.class文件拖入进去就可以了
cglib动态代理底层原理
- 生成代理类继承被继承类,重写被代理类的所有非final方法,因为在继承机制中是被final修饰的方法是不能被重写的,所以这也是cglib的一个弊端,不能代理final修饰的方法。
- 代理对象调用this.findLove方法->调用MethodInterceptor的intercept方法->调用methodProxy.invokeSuper(o, params)->通过init()来初始化生成代理类和被代理类的FastClass
- 创建了代理类和被代理类的fastclass,通过代理类的fastclass中的invoke方法,通过Index去调用代理类的对应索引的方法
cglib和jdk动态代理的区别
- cglib动态代理对象是继承被代理类的,jdk动态代理对象是实现抽象主题接口的。
- jdk动态代理类是通过底层拼接实现的,创建过程较为简单,cglib动态代理类是通过asm框架生成的,生成过程较为复杂
- jdk动态代理类调用代理方法是通过反射实现的,调用流程叫长,而cglib在生成被代理类的过程中已经给每个代理方法建立了Index,每次调用相应的方法只需要调用代理类的fastclass的invoke方法就行,被代理类的invoke方法会通过索引找到代理类对应的方法进行调用,调用时间较短。
代理模式在spring中的应用
org.springframework.aop.framework.ProxyFactoryBean#getObject 方法中有两个实现,一个是getSingletonInstance(),这种情况是在spring没有配置的情况下,获取的对象都是单例的。采用双重单例模式实现,而newPrototypeInstance()方法在获取的类没有实现接口时,使用cglib动态代理实现。
否则则采用jdk动态代理。
@Override
@Nullable
public Object getObject() throws BeansException {
initializeAdvisorChain();
if (isSingleton()) {
return getSingletonInstance();
}
else {
if (this.targetName == null) {
logger.info("Using non-singleton proxies with singleton targets is often undesirable. " +
"Enable prototype proxies by setting the 'targetName' property.");
}
return newPrototypeInstance();
}
}
静态代理和动态代理的区别
- 静态代理采用手动实现扩展的方式,如果真实主题角色增加了方法,则代理类也要相应增加方法。违背了开闭原则
- 动态代理采用运行时动态生成代理类的方式,被代理类改变了也不需要改变代理类,符合开闭原则。
- 对于动态代理,如果方法增强的逻辑有新增,那么结合策略模式,也只需要增加扩展类,不需要修改原有代码,符合开闭原则。
代理模式的优势
- 在业务开发中将调用类与被调用类的逻辑分离,降低了系统的耦合性
- 通过代理模式,可以提高程序的业务扩展性
- 可以保护目标对象
- 可以增强目标对象的功能
代理模式的缺点
- 增加了系统中类的数量
- 增加了系统的复杂度