【设计模式】代理模式
- 1.代理模式介绍
- 2. 🌰
- 2.1 代理模式中间件模型结构
- 2.2 代码实现
- Dao层接口
- 代理类定义
- 将Bean定义注册到Spring容器
- 配置⽂件spring-config
- 测试验证
- 3. InvocationHandler接口
- 附录
1.代理模式介绍
代理模式有点像⽼⼤和⼩弟,也有点像分销商。主要解决的问题是为某些资源的访问、对象的类的易⽤操作上提供⽅便使⽤的代理服务。⽽这种设计思想的模式经常会出现在我们的系统中,或者你⽤到过的组件中,它们都提供给你⼀种⾮常简单易⽤的⽅式控制原本你需要编写很多代码的进⾏使⽤的服务类。
类似这样的场景可以想到;
- 你的数据库访问层⾯经常会提供⼀个较为基础的应⽤,以此来减少应⽤服务扩容时不⾄于数据库连接数暴增。
- 使⽤过的⼀些中间件例如;RPC框架,在拿到jar包对接⼝的描述后,中间件会在服务启动的时候⽣成对应的代理类,当调⽤接⼝的时候,实际是通过代理类发出的socket信息进⾏通过。
- 另外像我们常⽤的 MyBatis ,基本是定义接⼝但是不需要写实现类,就可以对 xml 或者⾃定义注解⾥的 sql 语句进⾏增删改查操作。
2. 🌰
模拟实现mybatis-spring中代理类⽣成部分
对于Mybatis的使⽤中只需要定义接⼝不需要写实现类就可以完成增删改查操作,有疑问的⼩伙伴,在本章节中就可以学习到这部分知识。解析下来我们会通过实现⼀个这样的代理类交给spring管理的核⼼过程,来讲述代理类模式。
这样的案例场景在实际的业务开发中其实不多,因为这是将这种思想运⽤在中间件开发上,⽽很多⼩伙伴经常是做业务开发,所以对Spring的bean定义以及注册和对代理以及反射调⽤的知识了解的相对较少。但可以通过本章节作为⼀个⼊⻔学习,逐步了解。
2.1 代理模式中间件模型结构
- 此模型中涉及的类并不多,但都是抽离出来的核⼼处理类。主要的事情就是对类的代理和注册到spring中。
- 上图中最上⾯是关于中间件的实现部分,下⾯对应的是功能的使⽤。
2.2 代码实现
- 这⾥我们定义了⼀个模拟mybatis-spring中的⾃定义注解,⽤于使⽤在⽅法层⾯。
import java.lang.annotation.*;@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {String value() default "";
}
Dao层接口
import org.itstack.demo.design.agent.Select;public interface IUserDao {@Select("select userName from user where id = #{uId}")String queryUserInfo(String uId);
}
代理类定义
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.FactoryBean;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;public class MapperFactoryBean<T> implements FactoryBean<T> {private Logger logger = LoggerFactory.getLogger(MapperFactoryBean.class);private Class<T> mapperInterface;public MapperFactoryBean(Class<T> mapperInterface) {this.mapperInterface = mapperInterface;}@Overridepublic T getObject() throws Exception {InvocationHandler handler = (proxy, method, args) -> {Select select = method.getAnnotation(Select.class);logger.info("SQL:{}", select.value().replace("#{uId}", args[0].toString()));return args[0] + ",小傅哥,bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!";};return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, handler);}@Overridepublic Class<?> getObjectType() {return mapperInterface;}@Overridepublic boolean isSingleton() {return true;}
}
- 如果你有阅读过mybatis源码,是可以看到这样的⼀个类; MapperFactoryBean ,这⾥我们也模
拟⼀个这样的类,在⾥⾯实现我们对代理类的定义。 - 通过继承 FactoryBean ,提供bean对象,也就是⽅法; T getObject() 。
- 在⽅法 getObject() 中提供类的代理以及模拟对sql语句的处理,这⾥包含了⽤户调⽤dao层⽅法
时候的处理逻辑。 - 还有最上⾯我们提供构造函数来透传需要被代理类, Class mapperInterface ,在mybatis
中也是使⽤这样的⽅式进⾏透传。 - 另外 getObjectType() 提供对象类型反馈,以及 isSingleton() 返回类是单例的。
将Bean定义注册到Spring容器
import org.itstack.demo.design.IUserDao;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {GenericBeanDefinition beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(MapperFactoryBean.class);beanDefinition.setScope("singleton");beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(IUserDao.class);BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {// left intentionally blank}
}
- 这⾥我们将代理的bean交给spring容器管理,也就可以⾮常⽅便让我们可以获取到代理的bean。
这部分是spring中关于⼀个bean注册过程的源码。 - GenericBeanDefinition ,⽤于定义⼀个bean的基本信息 setBeanClass(MapperFactoryBean.class); ,也包括可以透传给构造函数信息 addGenericArgumentValue(IUserDao.class);
- 最后使⽤ BeanDefinitionReaderUtils.registerBeanDefinition ,进⾏bean的注册,也就
是注册到 DefaultListableBeanFactory 中。
配置⽂件spring-config
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"default-autowire="byName"><bean id="userDao" class="org.itstack.demo.design.agent.RegisterBeanFactory"/>
</beans>
- 接下来在配置⽂件中添加我们的bean配置,在mybatis的使⽤中⼀般会配置扫描的dao层包,这样
就可以减少这部分的配置。
测试验证
@Testpublic void test_IUserDao() {BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);String res = userDao.queryUserInfo("100001");logger.info("测试结果:{}", res);}
- 测试的过程⽐较简单,通过加载Bean⼯⼚获取我们的代理类的实例对象,之后调⽤⽅法返回结
果。 - 那么这个过程你可以看到我们是没有对接⼝先⼀个实现类的,⽽是使⽤代理的⽅式给接⼝⽣成⼀个
实现类,并交给spring管理。
3. InvocationHandler接口
InvocationHandler是Java中的一个接口,它通常是与动态代理一起使用的。在动态代理模式中,InvocationHandler接口的实现类充当代理对象方法调用的处理器,它会拦截代理对象的所有方法调用并执行自定义的逻辑。
package com.company.reflect.InvocationHandler;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** @author fan* @version 1.0* @date 2023/5/28*/
public class ProxyDemo {public static void main(String[] args) {// 创建一个实现了InvocationHandler接口的类InvocationHandler handler = new MyInvocationHandler();// 创建动态代理对象MyInterface proxy = (MyInterface) Proxy.newProxyInstance(MyInterface.class.getClassLoader(),new Class[]{MyInterface.class},handler);// 调用代理对象的方法proxy.sayHello();}
}// 需要代理的接口
interface MyInterface {void sayHello();
}// InvocationHandler的实现类
class MyInvocationHandler implements InvocationHandler {// 实现invoke方法来处理代理对象的方法调用@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("Before invoking method: " + method.getName());Object result = method.invoke(new MyClass(), args);System.out.println("After invoking method: " + method.getName());return result;}
}// 需要代理的类
class MyClass implements MyInterface {@Overridepublic void sayHello() {System.out.println("Hello World!");}
}/*** 在上面的示例中,我们定义了一个MyInterface接口和一个MyClass类,* 其中MyClass实现了MyInterface接口的方法。* 然后我们创建了一个实现了InvocationHandler接口的MyInvocationHandler类。这个类的invoke方法会在代理对象调用方法时被自动调用。* 在invoke方法中,我们可以编写自己的逻辑来处理代理方法的调用,然后使用Java反射机制来调用被代理对象的方法。** 最后,我们调用Proxy.newProxyInstance方法来创建一个动态代理对象,并将MyInvocationHandler实例传递给它。* 当我们使用代理对象调用sayHello方法时,MyInvocationHandler实例会拦截该方法的调用,并执行自己的逻辑。*/
附录
1.代理模式视频课程
2. 【Java基础】注解–@interface使用详解