## 改造一
启动类
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
context.register(AppConfig.class);
context.refresh();
UserService userService = (UserService) context.getBean("userService");
userService.test();
Mapper层
public interface OrderMapper {
@Select("select 'user'")
String SelectById();
}
Service层
@Component
public class UserService {
@Autowired
private OrderMapper orderMapper;
public void test(){
System.out.println(orderMapper.SelectById());
}
}
@Component
public class ZhqFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
//创建一个代理对象
Object proxyInstance = Proxy.newProxyInstance(ZhqFactoryBean.class.getClassLoader(), new Class[]{OrderMapper.class}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
return null;
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return OrderMapper.class;
}
}
配置类
@ComponentScan("com.zhouyu")
public class AppConfig {}
- 以上是简单mybatis整合spring的例子,主要就是想要让orderMapper成功注入到UserService 中,成功执行sql的查询嘛。
- 由于orderMapper是一个接口,没法直接注入,只能功能动态代理的方式进行注入,== 后续可以cjlib动态代理==
问题
- 同样是单例Bean,ZhqFactoryBean和UserService的执行顺序?如果先执行UserService ,是不是就不能进行注入了,因为此时这个ZhqFactoryBean没有对OrderMapper执行代理逻辑?
- 现在只是注入一个Mapper对象,如果注入多个,怎么可以更好的设计?
改造二
应对第二个问题可以使用改造ZhqFactoryBean的构造方法,然后再注册BeanDefinition的时候进行传入当前类,就会把所需要的mapper即变成Beandefinition又会创建他的代理对象。
改造ZhqFactoryBean构造方法,使用他变成通用类。
public class ZhqFactoryBean implements FactoryBean {
private Class aClass;
public ZhqFactoryBean(Class aClass) {
this.aClass = aClass;
}
@Override
public Object getObject() throws Exception {
//创建一个代理对象
Object proxyInstance = Proxy.newProxyInstance(ZhqFactoryBean.class.getClassLoader(), new Class[]{aClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method);
return null;
}
});
return proxyInstance;
}
@Override
public Class<?> getObjectType() {
return aClass;
}
}
1)启动类改造
AnnotationConfigApplicationContext registry = new AnnotationConfigApplicationContext();
registry.register(AppConfig.class);
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
beanDefinition1.setBeanClass(ZhqFactoryBean.class);
AbstractBeanDefinition beanDefinition2 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition2.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
beanDefinition2.setBeanClass(ZhqFactoryBean.class);
registry.registerBeanDefinition("userMapper",beanDefinition1);
registry.registerBeanDefinition("orderMapper",beanDefinition2);
registry.refresh();
UserService userService = (UserService) registry.getBean("userService");
userService.test();
2)也可以使用BeanDefinitionRegistryPostProcessor
@Component
public class ZhqBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
beanDefinition1.setBeanClass(ZhqFactoryBean.class);
AbstractBeanDefinition beanDefinition2 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition2.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
beanDefinition2.setBeanClass(ZhqFactoryBean.class);
registry.registerBeanDefinition("userMapper",beanDefinition1);
registry.registerBeanDefinition("orderMapper",beanDefinition2);
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
问题
- BeanDefinitionRegistryPostProcessor 是在Bean生命周期的什么时候执行的?
- 这样每次都需要定义一个BeanDefinition才能让mapper产生代理,怎么使用注解方法,一次性扫描包路径的所有mapper。
改造三
添加注解类用于扫描mapper包
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface ZhqScan {
String value();
}
//启动类添加
@ZhqScan("com.zhouyu.mapper")
注解中获取包路径,改造spring扫描
@Component
public class ZhqBeanPostProcessor implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//扫描路径
Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(ZhqScan.class.getName());
String path = (String) annotationAttributes.get("value");
//如果这样还是使用的spring的扫描逻辑
ZhqClassPathDefinitionScanner scanner = new ZhqClassPathDefinitionScanner(registry);
scanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
scanner.scan(path);
}
}
ZhqClassPathDefinitionScanner重载doScan,改造Beandefiniton使他通过代理
public class ZhqClassPathDefinitionScanner extends ClassPathBeanDefinitionScanner {
public ZhqClassPathDefinitionScanner(BeanDefinitionRegistry registry) {
super(registry);
}
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
BeanDefinition beanDefinition = beanDefinitionHolder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(ZhqFactoryBean.class.getName());
}
System.out.println(beanDefinitionHolders.size());
return beanDefinitionHolders;
}
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface();
}
}
思路:
- mybatis使用mapper接口进行数据库查询,首先得注册成为beandefinition中,但是mapper是一个接口,不能直接注册成beandefinition,那怎么办?需要用一个代理对象进行代理,改造一用到FactoryBean,生成BeanDefintion的同时,产生对mapper进行代理。
- 对于怎么批量对mapper进行代理,改造二考虑到使用构造方法,通过创建BeanDefinition的同时调用构造方法直接产生了代理对象。但是对于怎么批量产生mapper就要看改造三了。
- 改造三,使用注解将指定路径下的所有mapper都扫描到。但是手动写扫描太过负责,所以参考spring的扫描逻辑,他使用到的就是ClassPathBeanDefinitionScanner这个类实现的扫描功能,但是他又有只扫描接口,且带@Commopent注解的,所以创建一个新类继承他,并重写响应的方法,使他即使是接口的类也能扫描成为beanDefinition.
- 但是光扫描出来成为deandefinition不太行啊,需要他对接成为代理对象,所以重写doscan方法,对扫描后的Beandefinition进行处理
问题:
- 现在只是仿照mybatis进行操作,而且代理对象是咱们自己写的?
改造四
让mybatis进行代理
public class ZhqFactoryBean implements FactoryBean {
private Class aClass;
private SqlSession sqlSession;
public ZhqFactoryBean(Class aClass) {
this.aClass = aClass;
}
@Autowired
public void setSession(SqlSessionFactory sqlSessionFactory) {
//使用sqlSessionFactory对sqlSession进行赋值
sqlSessionFactory.getConfiguration().addMapper(aClass);
this.sqlSession = sqlSessionFactory.openSession();
}
@Override
public Object getObject() throws Exception {
//将接口让mybaits进行代理,但是sqlSession需要引入
return sqlSession.getMapper(aClass);
}
@Override
public Class<?> getObjectType() {
return aClass;
}
}
注入SqlSessionFactory,自定义注入
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
return sqlSessionFactory;
}
mybatis配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<!-- 使用jdbc事务管理 -->
<transactionManager type="JDBC"/>
<!-- 数据库连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://***:3307/zhq?characterEncoding=utf-8&useSSL=false"/>
<property name="username" value="***"/>
<property name="password" value="***"/>
</dataSource>
</environment>
</environments>
</configuration>
参考图片