S-第二十二周_设计模式-结构型-代理模式

58 阅读2分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 11 天,点击查看活动详情

定义

「大话设计模式」代理模式(Proxy): 为其他对象提供一种代理以控制对这个对象的访问。

设计模式之美:在不改变原始类的情况下,通过引入代理类来给原始类附加功能(有点装饰器的味道)

实战

通过模拟 mybatis-spring 中代理类生成部分来学习动态代理。

  • BeanDefinitionRegistryPostProcessor spring 的接口类用于处理对 bean 的定义注册。
  • GenericBeanDefinition 定义 bean 的信息,在spring-mybatis 中使用到的是: ScannedGenericBeanDefinition 略有不同。
  • FactoryBean 用于处理工厂的类。

Select 注解(这里其实和我以前写的 日志注解同理,都是动态代理)。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    String value() default "";
}

注册 bean 的信息:我们将代理的bean交给spring容器管理

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 beanFactory) throws BeansException {

    }

}

代理类定义:在⽅方法 getObject() 中提供类的代理以及模拟对sql语句的处理,这里包含了用户调用dao层方法时候的处理逻辑

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;
    }
    
    @Override
    public 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] + " == 从数据库中查询到了数据 == ";
        };
        return (T) Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

Dao 层

public interface IUserDao {
    @Select("select * from access_api_log where id=#{uId}")
    String queryInfo(String uId);
}

测试类:

public class ApiTest {
    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test_IUserDao() {
        ClassPathXmlApplicationContext beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
        IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
        String res = String.valueOf(userDao.queryInfo("1"));
        logger.info("测试结果:{}", res);
    }

}

我们一般项目中是配置扫描 Dao 接口,这里是为了方便。直接在测试类中使用ClassPathXmlApplicationContext 读取配置。

<?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="com.practic.thinkinpatterndesign.结构型模式.代理模式.agent.RegisterBeanFactory"/>

</beans>

总结

在设计模式之美中:

对代理分类:

  • 动态代理:反射,比如上面的列子以及日志注解的实现
  • 静态代理:实现接口和继承两种方式

常年写 crud 的我,对于上面的代码感觉很神奇。不过对于底层实现也不再那么恐惧。应加强学习。