1、单例bean 、 单例模式、单例池之间的联系和底层原理
2、BeanFactory 于 Application之间的联系和底层架构设计
3、FactoryBean的作用和底层工作原理
4、spring 整合mybaits 的核心底层原理
5、@mapperScan注解的底层工作原理
6、MapperScannerConfigurer底层工作原理
7、ImportBeanDefintionRegistarar 和FactoryBean的经典应用实战
8、MapperFactory和sqlsessionFactoryBean的作用和底层源码分析
1、创建一个项目例子
mapper类
public interface UserMapper {
}
service类
@Component
public class UserService {
@Autowired
private UserMapper userMapper; // userMapper bean对象
public void test(){
System.out.println("打印"+userMapper);
}
}
appliction类
@ComponentScan("com.spring") // 扫描包下面有 @Component、@service 等的bean
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 容器
applicationContext.register(Application.class); // 配置spring
applicationContext.refresh(); // 刷新
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
}
}
创建了 application 、service 、mapper 类
启动main函数 报错
1、为什么会报错?
原因: userService 没有扫描到bean . 创建这个bean 报错 ,因为userservice 使用到了usermapper .,usermapper 没有注入spring 容器的bean .spring 构建bean 时,会自动给bean 的属性附值,这里报错的原因是附值过程中,出现了错误. spring -> bean的构造方法得到对象-对象属性需要附值-附值成功后,才是一个bean .而对象的属性值需要从spring 的容器中来.
@Autowired 先 byType 在byName 找到spring 容器的bean
@Mapper 在spring 中是用不到的,在 springboot 中才能用到.
bean一定是对象 ,但是对象不一定是bean
单例bean :在spring容器中,不只会存在一种bean类型的对象.会存在多个,多个之间名字是不一样的,同一个名字bean 对象是一样的.
单例模式: 获取单例bean 的模式
单例池 : 用来实现单例bean 的,得到同一个对象, 主要用map.
spring 容器的bean 是单例的,但是名字有可能不同,获取bean 先获取 类型,在根据名字来获取
2、如何解决报错?
@Autowired (required = false) 默认是true 必须获取 userMapper 的bean 但 改完
false 之后就不会报错,但是会报空指针异常,获取的userMapper 中的方法时.
但这不是解决问题的根本方法.
3、怎么得到userMapepr 的bean 对象?
usermapper 是个接口,如何获取这个对象. 两种方案: 1、实现userMapper 的接口
根据实现的接口获取一个对象,2、获取 userMapper 的代理对象. 这里主要用第二种方案.
UserMapper 的bean 对象不是有spring 容器产生的,它是有mybatis 产生的一个代理对象. 通过jdk 动态代理产生的, 附值给 userMapper
4、如何把 mybatis 的代理对象放入spring 容器中?
首先 需要将代理对象 成功一bean .
4.1 如何产生bean ?
1、声明式: 注解 @Transan、 xml、 @Component 、@Service 等等
2、编程式 : 代码逻辑 TranSanManager 、BeanDefinition 方法
4.2 FactoryBean
上面的方式定义 接口类型的Bean 是实现不了,;
FactoryBean 特殊的Bean 会有两个对象
1、当前定义的FactoryBean 对象 bean 的名字 &xxx
2、FactoryBean 接口中返回的对象 bean的名字 xxx
实现 将mybatis 的代理对象 生成bean 加载 到spring 容器中来, 通过 mybatis 生成bean 也是通过jdk 动态代理生成的.
public class UserFactoryBean implements FactoryBean {
/**
* 获得一个对象
* @return
* @throws Exception
*/
@Override
public Object getObject() throws Exception {
// Mybatis userMapper的代理对象 也是通过jdk动态代理的
Object instance= Proxy.newProxyInstance(UserFactoryBean.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
// 代理逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()); // 代理对象的方法
return null; // userMapper 调用时,会toString() 返回 null
}
});
return instance;
}
/**
* 获取对象的类型
* @return
*/
@Override
public Class<?> getObjectType() {
return UserMapper.class;
}
}
在次调用时,就不会报错了
@ComponentScan("com.spring") // 扫描包下面有 @Component、@service 等的bean
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 容器
applicationContext.register(Application.class); // 配置spring
// 定义bean
AbstractBeanDefinition beanDefinition =BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
beanDefinition.setBeanClass(UserFactoryBean.class);
// 这里需要产生一个UserMapper的对象,但是userMapper 是一个接口,没有对象的默认构造方法,需要用到特殊的bean 动态代理
applicationContext.registerBeanDefinition("userFactory",beanDefinition );
applicationContext.refresh(); // 刷新
System.out.println(applicationContext.getBean("userFactory")); // factory 返回bean 的名字
System.out.println(applicationContext.getBean("&userFactory")); // factory bean 的名字
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
}
}
mybaits-spring jar包的作用 将mybatis的代理对象放到spring 容器中去 .
使用多个mapper 类动态代理到 Spring 容器中,上面的使用方法就不是很合理.上面一次只能代理一个类.
改造
1、UserService 获取两个bean
@Component
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private OrderMapper orderMapper;
public void test(){
System.out.println("打印"+userMapper+orderMapper);
}
}
2、FactoryBean 改造
public class FactoryBean implements org.springframework.beans.factory.FactoryBean {
private Class mapperClass; // 接受:接口 、java 类 ,附值什么接口类,就返回什么代理对象
public FactoryBean(Class mapperClass) {
this.mapperClass = mapperClass;
}
@Override
public Object getObject() throws Exception {
Object instance = Proxy.newProxyInstance(FactoryBean.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
// 代理逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()); // 代理对象的方法
return null;
}
});
return instance;
}
/**
* 获取对象的类型
*
* @return
*/
@Override
public Class<?> getObjectType() {
return mapperClass;
}
}
3、appplication
@ComponentScan("com.spring") // 扫描包下面有 @Component、@service 等的bean
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 容器
applicationContext.register(Application.class); // 配置spring
// 定义bean1
AbstractBeanDefinition beanDefinition1 =BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();;
beanDefinition1.setBeanClass(FactoryBean.class);
// 利用bean 对象的构成方法 将需要代理的类附值进去
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
applicationContext.registerBeanDefinition("xx1",beanDefinition1);
// 定义bean2
AbstractBeanDefinition beanDefinition2 =BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();;
beanDefinition2.setBeanClass(FactoryBean.class);
// 利用bean 对象的构成方法 将需要代理的类附值进去
beanDefinition2.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
applicationContext.registerBeanDefinition("xx2",beanDefinition2);
applicationContext.refresh(); // 刷新
System.out.println(applicationContext.getBean("xx1"));
System.out.println(applicationContext.getBean("xx2"));
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
}
}
结论 改造 factoryBean 通过使用factoryBean 的构造方法,将需要代理的mybatis 对象依次传入,就可以得传入时的代理对象.
ImportBeanDefinitionRegistrar : 注册多个beanDefinition
改造
1、注册类
/**
*
* BeanDefinition注册
*/
public class SpringImportBeanDefinitionRegistarer implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 定义bean1
AbstractBeanDefinition beanDefinition1 =BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();;
beanDefinition1.setBeanClass(FactoryBean.class);
// 利用bean 对象的构成方法产生一个bean
beanDefinition1.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
beanDefinitionRegistry.registerBeanDefinition("xx1",beanDefinition1);
// 定义bean2
AbstractBeanDefinition beanDefinition2 =BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();;
beanDefinition2.setBeanClass(FactoryBean.class);
// 利用bean 对象的构成方法产生一个bean
beanDefinition2.getConstructorArgumentValues().addGenericArgumentValue(OrderMapper.class);
beanDefinitionRegistry.registerBeanDefinition("xx2",beanDefinition2);
// 定义bean3
AbstractBeanDefinition beanDefinition3 =BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();;
beanDefinition3.setBeanClass(FactoryBean.class);
// 利用bean 对象的构成方法产生一个bean
beanDefinition3.getConstructorArgumentValues().addGenericArgumentValue(MeberMapper.class);
beanDefinitionRegistry.registerBeanDefinition("xx3",beanDefinition3);
}
}
2、application 改造
@ComponentScan(“com.spring”) // 扫描包下面有 @Component
@service 等的bean
@Import(SpringImportBeanDefinitionRegistarer.class)
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 容器
applicationContext.register(Application.class); // 配置spring
applicationContext.refresh(); // 刷新
System.out.println(applicationContext.getBean("xx1"));
System.out.println(applicationContext.getBean("xx2"));
System.out.println(applicationContext.getBean("xx3"));
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
}
}
改造之后跟上面定义多个 BeanDefinition是一样的道理,通过将定义方法放入ImportBeanDefinitionRegistarer 中,然后Import 到Application中,代码看起更简洁.
改造 ImportBeanDefinitionRegistarer
spring 默认扫描 @Component注解的类 ,不会扫描接口,
这里需要定义一个类来扫描
继承 ClassPathBeanDefinitionScanner
扫描
1、扫描路径
2、重写扫描方法
1、 定义扫描器
/**
* 扫描器 扫描得到的BeanDefinition 注册 BeanDefinitionRegistry
*/
public class SpringMapperScanner extends ClassPathBeanDefinitionScanner {
/**
* spring 创建扫描器 只关系类,不关心接口
*
* @param registry
*/
public SpringMapperScanner(BeanDefinitionRegistry registry) {
super(registry);
}
/**
* 重写方法
* 判断是不是一个bean
* 改为扫描接口
*
* @param beanDefinition
* @return
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// return super.isCandidateComponent(beanDefinition); 默认判断类返回true
return beanDefinition.getMetadata().isInterface();// 判断是一个接口返回 true
}
/**
* 重写 doScan 扫描到相关的类
* @param basePackages
* @return
*/
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
//return super.doScan(basePackages);
Set<BeanDefinitionHolder> beanDefinitionHolders=super.doScan(basePackages);
// 此处逻辑将spring 扫描的bean 转为mybatis接口 通过代理对象 使用构造方法生成的bean
// 将mybatis 的bean 转为Spring 需要的bean
for(BeanDefinitionHolder beanDefinitionHolder:beanDefinitionHolders){
BeanDefinition beanDefinition =beanDefinitionHolder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(FactoryBean.class.getName());
}
return beanDefinitionHolders;
}
}
2、改造 注册BeanDifinition 方法
SpringImportBeanDefinitionRegistarer
/**
*
* BeanDefinition注册
*/
public class SpringImportBeanDefinitionRegistarer implements ImportBeanDefinitionRegistrar {
/**
*
* @param annotationMetadata
* @param beanDefinitionRegistry 注册表
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
String path ="com.spring.mapper";
SpringMapperScanner springMapperScanner=new SpringMapperScanner(beanDefinitionRegistry);
// 增加一个过滤器这里先不管 ,返回的true
springMapperScanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
int scan=springMapperScanner.scan(path); // 扫描路径 .scan 方法默认回去调用 doScan 方法
System.out.println(scan);
}
}
3、application 类
@ComponentScan("com.spring") // 扫描包下面有 @Component、@service 等的bean
@Import(SpringImportBeanDefinitionRegistarer.class)
public class Application {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 容器
applicationContext.register(Application.class); // 配置spring
applicationContext.refresh(); // 刷新
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
}
}
总结: 现在有了spring 的扫描器 、beandefinition 注册器、factoryBean 构造方法. 在定义多个mybatis 的mapper 接口类就不会报错. 会自动扫描 - 注册- 定义为spring bean, 在spring 的容器就能拿到相关的bean.
问题? 怎么拿到mybatis 的代理对象 ? 怎么动态扫描路径?
怎么拿到mybatis 代理对象,目前是jdk 代理对象?
1、改造factoryBean 类
拿到mybatis 的代理对象
/**
* 通用 factoryBean 类
*/
public class FactoryBean implements org.springframework.beans.factory.FactoryBean {
private Class mapperClass; // 接受:接口 、java 类 ,附值什么接口类,就返回什么代理对象
private SqlSession sqlSession; // mybatis 代理对象
public FactoryBean(Class mapperClass) {
this.mapperClass = mapperClass;
}
// set 注入 需要spring容器首先要一个SqlSession bean ,然而这个bean 来自于SqlSessionactory;
@Autowired
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
// 将每一个mapper 加到配置中去
sqlSessionFactory.getConfiguration().addMapper(mapperClass);
this.sqlSession = sqlSessionFactory.openSession();
}
@Override
public Object getObject() throws Exception {
// Object instance = Proxy.newProxyInstance(FactoryBean.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
// // 代理逻辑
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println(method.getName()); // 代理对象的方法
// return null;
// }
// });
// return instance;
return sqlSession.getMapper(mapperClass);
}
/**
* 获取对象的类型
*
* @return
*/
@Override
public Class<?> getObjectType() {
return mapperClass;
}
}
2、 改造 application 拿到mybatis 的SqlSessionactory
@ComponentScan("com.spring") // 扫描包下面有 @Component、@service 等的bean
@Import(SpringImportBeanDefinitionRegistarer.class)
public class Application {
/**
* 构建数据库配置bean sqlSessionFactory
*
* @return
* @throws IOException
*/
@Bean
public SqlSessionFactory sqlSessionFactory() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("xx.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();// 容器
applicationContext.register(Application.class); // 配置spring
applicationContext.refresh(); // 刷新
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.test();
}
}
修改 factoryBean 取消 SqlSession set 方法的 @Autowired 注解
1、取消注解
**
* 通用 factoryBean 类
*/
public class FactoryBean implements org.springframework.beans.factory.FactoryBean {
private Class mapperClass; // 接受:接口 、java 类 ,附值什么接口类,就返回什么代理对象
private SqlSession sqlSession; // mybatis 代理对象
public FactoryBean(Class mapperClass) {
this.mapperClass = mapperClass;
}
// set 注入 需要spring容器首先要一个SqlSession bean ,然而这个bean 来自于SqlSessionactory;
//@Autowired
public void setSqlSession(SqlSessionFactory sqlSessionFactory) {
// 将每一个mapper 加到配置中去
sqlSessionFactory.getConfiguration().addMapper(mapperClass);
this.sqlSession = sqlSessionFactory.openSession();
}
@Override
public Object getObject() throws Exception {
// Object instance = Proxy.newProxyInstance(FactoryBean.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
// // 代理逻辑
// @Override
// public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// System.out.println(method.getName()); // 代理对象的方法
// return null;
// }
// });
// return instance;
return sqlSession.getMapper(mapperClass);
}
/**
* 获取对象的类型
*
* @return
*/
@Override
public Class<?> getObjectType() {
return mapperClass;
}
}
2、修改 扫描器 中 beandefinition 的配置方法
/**
* 扫描器 扫描得到的BeanDefinition 注册 BeanDefinitionRegistry
*/
public class SpringMapperScanner extends ClassPathBeanDefinitionScanner {
/**
* spring 创建扫描器 只关系类,不关心接口
*
* @param registry
*/
public SpringMapperScanner(BeanDefinitionRegistry registry) {
super(registry);
}
/**
* 重写方法
* 判断是不是一个bean
* 改为扫描接口
*
* @param beanDefinition
* @return
*/
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
// return super.isCandidateComponent(beanDefinition); 默认判断类
return beanDefinition.getMetadata().isInterface();// 判断是一个接口返回 true
}
/**
* 重写 doScan 扫描到相关的类
* @param basePackages
* @return
*/
@Override
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
//return super.doScan(basePackages);
Set<BeanDefinitionHolder> beanDefinitionHolders=super.doScan(basePackages);
// 此处逻辑将spring 扫描的bean 转为mybatis接口 通过代理对象 使用构造方法生成的bean
// 将mybatis 的bean 转为Spring 需要的bean
for(BeanDefinitionHolder beanDefinitionHolder:beanDefinitionHolders){
//BeanDefinition beanDefinition =beanDefinitionHolder.getBeanDefinition();
// 修改为 GenericBeanDefinition 取消 FactoryBean 中Autowire 注解 同时设置 AutowireMode
GenericBeanDefinition beanDefinition= (GenericBeanDefinition) beanDefinitionHolder.getBeanDefinition();
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
beanDefinition.setBeanClassName(FactoryBean.class.getName());
beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME);
}
return beanDefinitionHolders;
}
}
如果解决写死扫描的路径?
写一个注解
1、添加一个 SpringScan 注解
并将 Application Import 方法加到这个注解中来,Spring 启动中,会自动加载 这个注解中 SpringImportBeanDefinitionRegistarer这个类中的方法,我们在类中的方法就可以取到这个注解.
/**
* 注解
* 扫描路径
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE) // 写在类上面
@Import(SpringImportBeanDefinitionRegistarer.class)
public @interface SpringScan {
String value();
}
2、 获得这个注解
改造 Beandefinition 获取扫描路径
/**
*
* BeanDefinition注册
*/
public class SpringImportBeanDefinitionRegistarer implements ImportBeanDefinitionRegistrar {
/**
*
* @param annotationMetadata
* @param beanDefinitionRegistry 注册表
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
// 获得扫描路径 "com.spring.mapper";
Map<String,Object> newannotationMetadata =annotationMetadata.getAnnotationAttributes(SpringScan.class.getName());
String path = (String) newannotationMetadata.get("value");
// 拿到扫描路径
//String path ="com.spring.mapper";
SpringMapperScanner springMapperScanner=new SpringMapperScanner(beanDefinitionRegistry);
// 增加一个过滤器
springMapperScanner.addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
int scan=springMapperScanner.scan(path); // 扫描路径 .scan 方法默认回去调用 doScan 方法
System.out.println(scan);
}
}
MapperScanConfiguration 扫描配置 代替 @MapperScan 和 @Configuration
spring的作用是代替 EJB 解偶.