软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。汇总目录链接:【玩转23种Java设计模式】学习目录汇总整理
一、简介
代理模式的定义:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
它的组成:
- 抽象角色:通过接口或抽象类声明真实角色实现的业务方法。
- 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。
- 真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。
二、实例
这次举例可能难度大了一点,因为这个是代理模式是“大厂面试热点”,所以打算通过Mybatis举例说一下。如果看懂这个,以后被问到“代理模式在什么框架中有应用?”、“Mybatis中使用了什么设计模式?怎么用的”......等等,咱们至少能答上来一部分。
模拟实现⼀个Mybatis中对类的代理过程,只需要定义接⼝,就可以关联到⽅法注解中的 sql 语句完成对数据库的操作。简单模拟下,实际源码肯定更复杂点。
1、自定义注解(Select)
定义了⼀个模拟mybatis-spring中的自定义注解,使用在方法层面。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Select {
String value() default "";
}
2、Dao层接口(IUserDao)
public interface IUserDao {
@Select("select * from user where id = #{id}")
String selectById(Integer id);
}
3、代理类(MapperFactoryBean)
@Slf4j
public class MapperFactoryBean<T> implements FactoryBean<T> {
private Class<T> mapperInterface;
public MapperFactoryBean(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Override
public T getObject() throws Exception {
InvocationHandler handler = (proxy, method, args) -> {
Select select = method.getAnnotation(Select.class);
log.info("query sql:{}", select.value());
log.info("param:{}", args[0]);
return "1, 张三(模拟从数据库查询的返回)";
};
return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, handler);
}
@Override
public Class<?> getObjectType() {
return mapperInterface;
}
@Override
public boolean isSingleton() {
return true;
}
}
MapperFactoryBean 通过继承 FactoryBean ,提供bean对象,也就是方法T getObject() 。在方法 getObject() 中提供类的代理以及模拟对sql语句的处理,这里包含了用户调用dao层方法时候的处理逻辑。
4、Bean定义注册到Spring容器(RegisterBeanFactory)
public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {
@Override
public 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);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
}
我们将代理的bean交给spring容器管理,以方便我们获取到代理的bean。这里是在spring中关于⼀个bean注册过程的源码。GenericBeanDefinition ,用于定义⼀个bean的基本信息,也包括可以透传给构造函数信息,最后使用BeanDefinitionReaderUtils.registerBeanDefinition,进行bean的注册,也就是注册到DefaultListableBeanFactory 中。
5、spring-mybatis.xml
<?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.xsd">
<bean id="userDao" class="com.test.RegisterBeanFactory"/>
</beans>
6、测试
@SpringBootTest
public class ApplicationTests {
@Test
public void test() {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-mybatis.xml");
IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
String res = userDao.selectById(1);
System.out.println("res = " + res);
}
}
控制台输出:
query sql:select * from user where id = #{id}
param:1
res = 1, 张三(模拟从数据库查询的返回)
三、总结
1、优点
- 职责清晰。代理模式能将代理对象与真实被调用的目标对象分离,真实的角色就是实现实际的业务逻辑,不用关心其他非本职责的事务。
- 高扩展性。一定程度上降低了系统的耦合度,扩展性好。
- 代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
2、缺点
- 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢。
- 实现代理模式需要额外的工作,有些代理模式的实现非常复杂。
3、应用场景
- 权限控制
- AOP实现
- Mybatis mapper
- Spring的事务
- 全局捕获异常
- Rpc远程调用接口
- 日志的采集