- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
工厂Bean后置处理器,主要是用来向容器中添加一些Bean的定义信息
下面给出的是演示过程中,可能会用到的依赖:
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
例子:
@Component
public class Bean2 {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
public Bean2() {
log.debug("我被 Spring 管理啦");
}
}
@Controller
public class Bean3 {
private static final Logger log = LoggerFactory.getLogger(Bean3.class);
public Bean3() {
log.debug("我被 Spring 管理啦");
}
}
public class Bean4 {
private static final Logger log = LoggerFactory.getLogger(Bean4.class);
public Bean4() {
log.debug("我被 Spring 管理啦");
}
}
public class Bean1 {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
public Bean1() {
log.debug("我被 Spring 管理啦");
}
}
@Mapper
public interface Mapper1 {
}
@Mapper
public interface Mapper2 {
}
public class Mapper3 {
}
@Configuration
@ComponentScan("com.two")
public class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean(initMethod = "init")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
/*
BeanFactory 后处理器的作用
*/
public class A05 {
private static final Logger log = LoggerFactory.getLogger(A05.class);
public static void main(String[] args) throws IOException {
// ⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// ⬇️初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// ⬇️销毁容器
context.close();
}
}
此时容器中只有一个Bean,显然config配置类的注解都没有被解析,此时就需要添加工厂后置处理器来解析这些注解了,从而可以往容器中添加更多的bean的定义信息
public class A05 {
private static final Logger log = LoggerFactory.getLogger(A05.class);
public static void main(String[] args) throws IOException {
// ⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// @ComponentScan @Bean @Import @ImportResource
context.registerBean(ConfigurationClassPostProcessor.class);
// ⬇️初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// ⬇️销毁容器
context.close();
}
}
可以看出ConfigurationClassPostProcessor还能把@Component以及其衍生注解标注的类,如果被扫描到后,都会被放入容器中
/*
BeanFactory 后处理器的作用
*/
public class A05 {
private static final Logger log = LoggerFactory.getLogger(A05.class);
public static void main(String[] args) throws IOException {
// ⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// @ComponentScan @Bean @Import @ImportResource
context.registerBean(ConfigurationClassPostProcessor.class);
// @MapperScan注解
context.registerBean(MapperScannerConfigurer.class, bd -> {
//指定扫描的包名
bd.getPropertyValues().add("basePackage", "com.two.mapper");
});
// ⬇️初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// ⬇️销毁容器
context.close();
}
}
MapperScannerConfigurer还会额外干些事情
ConfigurationClassPostProcessor 可以解析
@ComponentScan
@Bean
@Import
@ImportResource
MapperScannerConfigurer 可以解析
Mapper 接口
这里不会去直接翻阅其源码,而是通过引用spring源码中一些类的方法,来基本还原大致的解析过程
public class ComponentScanPostProcessor implements BeanDefinitionRegistryPostProcessor {
/**
* 该方法会在context.refresh中被调用
*/
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
//这里按理来说,应该从context中或取到外部传入的config配置类对象才对
//AnnotationUtils获取Config配置类上的ComponentScan注解,如果没有标注范围null
ComponentScan componentScan = AnnotationUtils.findAnnotation(Config.class, ComponentScan.class);
//如果配置类标注了该注解
if (componentScan != null) {
//获取注解中标注的basePackages属性标注扫描的包路径
for (String p : componentScan.basePackages()) {
//输出需要扫描的包路径
System.out.println("需要扫描的包路径为: "+p);
// com.dhy.component -> classpath*:com/dhy/component/**/*.class
// /**/*.class表示当前包和子包下面所有的类
String path = "classpath*:" + p.replace(".", "/") + "/**/*.class";
//输出该转换后的资源路径
System.out.println("转换后的资源路径: "+path);
//读取类的原信息工厂---可以从二进制字节码文件中读取出相关信息---底层使用了asm字节码重组技术
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
//通过PathMatchingResourcePatternResolver去定位符合这些路径的资源集合
Resource[] resources = new PathMatchingResourcePatternResolver().getResources(path);
//负责解析@Component注解中标注的beanName
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
for (Resource resource : resources) {
//System.out.println(resource);
//当前resource是一个class文件
//通过原信息工厂获取到原信息读取器
MetadataReader reader = factory.getMetadataReader(resource);
System.out.println("类名:" + reader.getClassMetadata().getClassName());
//从二进制class文件中读取出当前类上的注解信息
AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
System.out.println("是否加了 @Component:" + annotationMetadata.hasAnnotation(Component.class.getName()));
//例如:@Controller注解上加了@Component注解
System.out.println("是否加了 @Component 派生:" + annotationMetadata.hasMetaAnnotation(Component.class.getName()));
//只选择加了Component或者其派生注解
if (annotationMetadata.hasAnnotation(Component.class.getName())
|| annotationMetadata.hasMetaAnnotation(Component.class.getName())) {
//将扫描到的符合条件的class包装成一个BeanDefinition
AbstractBeanDefinition bd = BeanDefinitionBuilder
//传入class类名
.genericBeanDefinition(reader.getClassMetadata().getClassName())
.getBeanDefinition();
//通过传入的BeanDefinition生成一个beanName
String name = generator.generateBeanName(bd, beanFactory);
//注册BeanDefinition进BeanFactory--传入BeanName,BeanDefinition
beanFactory.registerBeanDefinition(name, bd);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
将我们自定义的ComponentScanPostProcessor注册到Bean工厂中:
public class A05 {
private static final Logger log = LoggerFactory.getLogger(A05.class);
public static void main(String[] args) throws IOException {
// ⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
// 解析 @ComponentScan
//自定义的ComponentScanPostProcessor,专门解析ComponentScan注解
context.registerBean(ComponentScanPostProcessor.class);
// ⬇️初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// ⬇️销毁容器
context.close();
}
}
输出:
13:06:16.413 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@2d363fb3
13:06:16.442 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.two.ComponentScanPostProcessor'
需要扫描的包路径为: com.two
转换后的资源路径: classpath*:com/two/**/*.class
类名:com.two.A05
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.AtBeanPostProcessor
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.Bean1
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.ComponentScanPostProcessor
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.Config
是否加了 @Component:false
是否加了 @Component 派生:true
13:06:16.586 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Overriding bean definition for bean 'config' with a different definition: replacing [Root bean: class [com.two.Config]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Generic bean: class [com.two.Config]; scope=; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
类名:com.two.MapperPostProcessor
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.component.Bean2
是否加了 @Component:true
是否加了 @Component 派生:false
类名:com.two.component.Bean3
是否加了 @Component:false
是否加了 @Component 派生:true
类名:com.two.component.Bean4
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.mapper.Mapper1
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.mapper.Mapper2
是否加了 @Component:false
是否加了 @Component 派生:false
类名:com.two.mapper.Mapper3
是否加了 @Component:false
是否加了 @Component 派生:false
13:06:16.607 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
13:06:16.607 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean2'
13:06:16.607 [main] DEBUG com.two.component.Bean2 - 我被 Spring 管理啦
13:06:16.608 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean3'
13:06:16.608 [main] DEBUG com.two.component.Bean3 - 我被 Spring 管理啦
config
com.two.ComponentScanPostProcessor
//这里看出我们自定义的ComponentScanPostProcessor生效了
bean2
bean3
13:06:16.634 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@2d363fb3, started on Sun Mar 27 13:06:16 CST 2022
Process finished with exit code 0
工厂Bean后置处理器是在refresh方法中,每个节点被调用的,而Bean的后置处理器是在getBean方法执行Bean生命周期过程中被调用的,前者主要是为了向容器中增加Bean的定义信息,后者是为了干预Bean生命周期中各个阶段的行为。
public class AtBeanPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
//推荐使用这种方式读取,因为他是直接通过asm读取字节码,不会走反射过程和类加载过程
//这里直接写死配置类的路径了
MetadataReader reader = factory.getMetadataReader(new ClassPathResource("com/two/Config.class"));
//获取当前配置类中被@Bean注解标注的方法
Set<MethodMetadata> methods = reader.getAnnotationMetadata().getAnnotatedMethods(Bean.class.getName());
//遍历这些方法
for (MethodMetadata method : methods) {
System.out.println("当前被@Bean注解标注的方法名为: "+method);
//获取@Bean注解中的属性
Map<String, Object> annotationAttributes = method.getAnnotationAttributes(Bean.class.getName());
//找到initMethod属性对应的值
String initMethod = annotationAttributes.get("initMethod").toString();
//下面针对每个一个方法生成一个BeanDefinition
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
//我们需要把这些被@Bean标注的方法变成工厂方法---工厂方法负责生产产品
//设置工厂方法名和当前工厂方法属于的对象
builder.setFactoryMethodOnBean(method.getMethodName(), "config");
//设置自动装配模式,默认为AbstractBeanDefinition.AUTOWIRE_NO
//如果工厂方法上有参数,那么参数值是否需要自动注入? ---这就是自动装配模式需要管的事情
//对于工厂方法的参数而言,自动装配模式应该是构造方法的自动注入
builder.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
//设置初始化方法名称
if (initMethod.length() > 0) {
builder.setInitMethodName(initMethod);
}
//生成BeanDefinition
AbstractBeanDefinition bd = builder.getBeanDefinition();
//注册BeanDefinition到BeanFactory
beanFactory.registerBeanDefinition(method.getMethodName(), bd);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
将自定义的AtBeanPostProcessor注册到BeanFactory中
public class A05 {
private static final Logger log = LoggerFactory.getLogger(A05.class);
public static void main(String[] args) throws IOException {
// ⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
//注册自定义的工厂Bean后置处理器
context.registerBean(AtBeanPostProcessor.class);
// ⬇️初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// ⬇️销毁容器
context.close();
}
}
输出:
13:27:15.227 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Refreshing org.springframework.context.support.GenericApplicationContext@2d363fb3
13:27:15.257 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'com.two.AtBeanPostProcessor'
当前被@Bean注解标注的方法名为: com.two.Config.bean1()
当前被@Bean注解标注的方法名为: com.two.Config.sqlSessionFactoryBean(javax.sql.DataSource)
当前被@Bean注解标注的方法名为: com.two.Config.dataSource()
13:27:15.365 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'config'
13:27:15.365 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'bean1'
13:27:15.366 [main] DEBUG com.two.Bean1 - 我被 Spring 管理啦
13:27:15.366 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'sqlSessionFactoryBean'
13:27:15.372 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'dataSource'
13:27:15.488 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} inited
13:27:15.491 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Autowiring by type from bean name 'sqlSessionFactoryBean' via factory method to bean named 'dataSource'
13:27:15.497 [main] DEBUG org.apache.ibatis.logging.LogFactory - Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter.
13:27:15.502 [main] DEBUG org.mybatis.spring.SqlSessionFactoryBean - Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration
13:27:15.545 [main] DEBUG org.mybatis.spring.SqlSessionFactoryBean - Property 'mapperLocations' was not specified.
//自定义的工厂Bean后置处理器生效了
config
com.two.AtBeanPostProcessor
bean1
sqlSessionFactoryBean
dataSource
13:27:15.571 [main] DEBUG org.springframework.context.support.GenericApplicationContext - Closing org.springframework.context.support.GenericApplicationContext@2d363fb3, started on Sun Mar 27 13:27:15 CST 2022
13:27:15.572 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closing ...
13:27:15.572 [main] INFO com.alibaba.druid.pool.DruidDataSource - {dataSource-1} closed
我们先看一下mybaits是如何处理这个mapper接口的
@Configuration
@ComponentScan(basePackages = "com.two")
public class Config {
@Bean
public Bean1 bean1() {
return new Bean1();
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean(initMethod = "init")
public DruidDataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
@Bean
public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}
@Bean
public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}
}
Mybaits对于每个mapper接口,会使用一个MapperFactoryBean来包装该mapper接口,其实我们可以猜到以后要注入该mapper接口时,就是从MapperFactoryBean获取mapper接口对应的实现类,其实MapperFactoryBean就是一个工厂类
@Bean
public MapperFactoryBean<Mapper1> mapper1(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}
@Bean
public MapperFactoryBean<Mapper2> mapper2(SqlSessionFactory sqlSessionFactory) {
MapperFactoryBean<Mapper2> factory = new MapperFactoryBean<>(Mapper2.class);
factory.setSqlSessionFactory(sqlSessionFactory);
return factory;
}
这里是手动通过@Bean完成的关联注入功能,现在我们要实现通过@Mapper或者@MapperScan接口扫描自动完成这样的功能
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
//定位资源,PathMatchingResourcePatternResolver可以通过模糊匹配定位资源,并且可以一次性定位多个资源
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:com/two/mapper/**/*.class");
//beanName生成器
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
//缓存元数据读取器工厂---生产读取并解析class文件的reader
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
//遍历资源
for (Resource resource : resources) {
//读取并解析class文件的reader
MetadataReader reader = factory.getMetadataReader(resource);
//class文件解析完后得到的类元数据信息
ClassMetadata classMetadata = reader.getClassMetadata();
//当前class上注解的元数据信息
AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
//是否是一个接口并且标注了@Mapper注解
if (classMetadata.isInterface()&&annotationMetadata.hasAnnotation(Mapper.class.getName())) {
//生成一个BeanDefinition
AbstractBeanDefinition bd = BeanDefinitionBuilder
//Bean的类型是MapperFactoryBean
.genericBeanDefinition(MapperFactoryBean.class)
//联系这@Bean原生注入中的这一行代码: MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
//添加构造方法参数
.addConstructorArgValue(classMetadata.getClassName())
//设置自动注入模式---按照类型注入
//联系这一行: factory.setSqlSessionFactory(sqlSessionFactory);
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
.getBeanDefinition();
//AnnotationMetadata默认会根据BeanDefinition来中的传入的当前BeanDefinition中对应的Bean.class类名首字母小写即bean作为beanName
//但是这里因为所有接口都要被MapperFactoryBean接管,因此这会导致所生成的BeanName都是一样的
//对于一样的BeanName,IOC的选择是新的覆盖旧的
String name = generator.generateBeanName(bd, beanFactory);
//注册Bean
beanFactory.registerBeanDefinition(name, bd);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
针对这里说的问题,我来演示一下:
public class A05 {
private static final Logger log = LoggerFactory.getLogger(A05.class);
public static void main(String[] args) throws IOException {
// ⬇️GenericApplicationContext 是一个【干净】的容器
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("config", Config.class);
//注册一个ConfigurationClassPostProcessor负责解析@ComponentScan @Bean @Import @ImportResource
context.registerBean(ConfigurationClassPostProcessor.class);
//注册我们写的Bean工厂后置处理器
context.registerBean(MapperPostProcessor.class);
// ⬇️初始化容器
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
// ⬇️销毁容器
context.close();
}
}
mapper包下面有这三个类:
@Mapper
public interface Mapper1 {
}
@Mapper
public interface Mapper2 {
}
public class Mapper3 {
}
Spring团队的解决思路类似下面这样:
public class MapperPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanFactory) throws BeansException {
try {
//定位资源,PathMatchingResourcePatternResolver可以通过模糊匹配定位资源,并且可以一次性定位多个资源
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("classpath:com/two/mapper/**/*.class");
//beanName生成器
AnnotationBeanNameGenerator generator = new AnnotationBeanNameGenerator();
//缓存元数据读取器工厂---生产读取并解析class文件的reader
CachingMetadataReaderFactory factory = new CachingMetadataReaderFactory();
//遍历资源
for (Resource resource : resources) {
//读取并解析class文件的reader
MetadataReader reader = factory.getMetadataReader(resource);
//class文件解析完后得到的类元数据信息
ClassMetadata classMetadata = reader.getClassMetadata();
//当前class上注解的元数据信息
AnnotationMetadata annotationMetadata = reader.getAnnotationMetadata();
//是否是一个接口并且标注了@Mapper注解
if (classMetadata.isInterface()&&annotationMetadata.hasAnnotation(Mapper.class.getName())) {
//生成一个BeanDefinition
AbstractBeanDefinition bd = BeanDefinitionBuilder
//Bean的类型是MapperFactoryBean
.genericBeanDefinition(MapperFactoryBean.class)
//联系这@Bean原生注入中的这一行代码: MapperFactoryBean<Mapper1> factory = new MapperFactoryBean<>(Mapper1.class);
//添加构造方法参数
.addConstructorArgValue(classMetadata.getClassName())
//设置自动注入模式---按照类型注入
//联系这一行: factory.setSqlSessionFactory(sqlSessionFactory);
.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE)
.getBeanDefinition();
//为每个接口生成一个BeanDefinition---但是该BeanDefinition作用仅仅是来生成一个BeanName
AbstractBeanDefinition bd2 = BeanDefinitionBuilder.genericBeanDefinition(classMetadata.getClassName()).getBeanDefinition();
//这样每次通过bd2生成的BeanName就会不同了
String name = generator.generateBeanName(bd2, beanFactory);
//注册Bean
beanFactory.registerBeanDefinition(name, bd);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
再测试:
举例:
public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {
private static final Logger log = LoggerFactory.getLogger(MyBean.class);
@Override
public void setBeanName(String name) {
log.debug("当前bean " + this + " 名字叫:" + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.debug("当前bean " + this + " 容器是:" + applicationContext);
}
@Override
public void afterPropertiesSet() throws Exception {
log.debug("当前bean " + this + " 初始化");
}
}
主启动类:
/*
Aware 接口及 InitializingBean 接口
*/
public class A06 {
private static final Logger log = LoggerFactory.getLogger(A06.class);
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("myBean", MyBean.class);
context.refresh();
context.close();
}
}
结果:
Aware 接口提供了一种【内置】 的注入手段,例如
BeanNameAware 注入 bean 的名字
BeanFactoryAware 注入 BeanFactory 容器
ApplicationContextAware 注入 ApplicationContext 容器
EmbeddedValueResolverAware 注入 ${} 解析器
InitializingBean 接口提供了一种【内置】的初始化手段
对比
内置的注入和初始化不受扩展功能的影响,总会被执行
而扩展功能受某些情况影响可能会失效
因此 Spring 框架内部的类常用内置注入和初始化
2. 有同学说: b、c、d 的功能用 @Autowired 就能实现啊, 为啥还要用 Aware 接口呢
简单地说:
a. @Autowired 的解析需要用到 bean 后处理器, 属于扩展功能
b. 而 Aware 接口属于内置功能, 不加任何扩展, Spring 就能识别
某些情况下, 扩展功能会失效, 而内置功能不会失效
例1: 你会发现用 Aware 注入 ApplicationContext 成功, 而 @Autowired 注入 ApplicationContext 失败
例子1:
public class MyBean implements BeanNameAware, ApplicationContextAware, InitializingBean {
private static final Logger log = LoggerFactory.getLogger(MyBean.class);
@Override
public void setBeanName(String name) {
log.debug("当前bean " + this + " 名字叫:" + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.debug("当前bean " + this + " 容器是:" + applicationContext);
}
@Override
public void afterPropertiesSet() throws Exception {
log.debug("当前bean " + this + " 初始化");
}
@Autowired
public void aaa(ApplicationContext applicationContext) {
log.debug("当前bean " + this + " 使用@Autowired 容器是:" + applicationContext);
}
@PostConstruct
public void init() {
log.debug("当前bean " + this + " 使用@PostConstruct 初始化");
}
}
再次执行程序,查看@Autowired注解和@PostConstruct注解是否生效了
显然并没有生效,我们只有加入相应的工厂Bean后置处理器,才能生效,下面验证:
public class A06 {
private static final Logger log = LoggerFactory.getLogger(A06.class);
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("myBean", MyBean.class);
//解析Autowire注解的
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
//解析Resource,PostConstrcut和PreDestory注解的
context.registerBean(CommonAnnotationBeanPostProcessor.class);
context.refresh();
context.close();
}
}
我们增加一个配置类Config1
@Configuration
public class MyConfig1 {
private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
log.debug("注入 ApplicationContext");
}
@PostConstruct
public void init() {
log.debug("初始化");
}
@Bean // beanFactory 后处理器------->关键点
public BeanFactoryPostProcessor processor1() {
return beanFactory -> {
log.debug("执行 processor1");
};
}
}
我们需要增加ConfigurationClassPostProcessor工厂Bean后置处理器来处理@Configuration和@Bean注解
/*
Aware 接口及 InitializingBean 接口
*/
public class A06 {
private static final Logger log = LoggerFactory.getLogger(A06.class);
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("myConfig1", MyConfig1.class);
context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
context.registerBean(CommonAnnotationBeanPostProcessor.class);
context.registerBean(ConfigurationClassPostProcessor.class);
context.refresh();
context.close();
}
}
问题来了,@Autowired注解和@PostConstruct注解凉凉了
我们先来看一下Bean的生命周期流程图:
下面再来进一步分析一下@Autowired注解失效的原因:Java 配置类不包含 BeanFactoryPostProcessor 的情况
Java 配置类包含 BeanFactoryPostProcessor 的情况,因此要创建其中的 BeanFactoryPostProcessor 必须提前创建 Java 配置类,而此时的 BeanPostProcessor 还未准备好,导致 @Autowired 等注解失效
对应代码
@Configuration
public class MyConfig1 {
private static final Logger log = LoggerFactory.getLogger(MyConfig1.class);
@Autowired
public void setApplicationContext(ApplicationContext applicationContext) {
log.debug("注入 ApplicationContext");
}
@PostConstruct
public void init() {
log.debug("初始化");
}
@Bean // ⬅️ 注释或添加 beanFactory 后处理器对应上方两种情况
public BeanFactoryPostProcessor processor1() {
return beanFactory -> {
log.debug("执行 processor1");
};
}
}
注意
解决方法:
@Configuration
public class MyConfig2 implements InitializingBean, ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(MyConfig2.class);
@Override
public void afterPropertiesSet() throws Exception {
log.debug("初始化");
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
log.debug("注入 ApplicationContext");
}
@Bean // beanFactory 后处理器
public BeanFactoryPostProcessor processor2() {
return beanFactory -> {
log.debug("执行 processor2");
};
}
}
a. Aware 接口提供了一种【内置】 的注入手段, 可以注入 BeanFactory, ApplicationContext
b. InitializingBean 接口提供了一种【内置】的初始化手段
c. 内置的注入和初始化不受扩展功能的影响, 总会被执行, 因此 Spring 框架内部的类常用它们
演示:
public class Bean1 implements InitializingBean {
private static final Logger log = LoggerFactory.getLogger(Bean1.class);
@PostConstruct
public void init1() {
log.debug("初始化1");
}
@Override
public void afterPropertiesSet() throws Exception {
log.debug("初始化2");
}
public void init3() {
log.debug("初始化3");
}
}
public class Bean2 implements DisposableBean {
private static final Logger log = LoggerFactory.getLogger(Bean2.class);
@PreDestroy
public void destroy1() {
log.debug("销毁1");
}
@Override
public void destroy() throws Exception {
log.debug("销毁2");
}
public void destroy3() {
log.debug("销毁3");
}
}
@SpringBootApplication
public class A07_1 {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(A07_1.class, args);
context.close();
}
@Bean(initMethod = "init3")
public Bean1 bean1() {
return new Bean1();
}
@Bean(destroyMethod = "destroy3")
public Bean2 bean2() {
return new Bean2();
}
}
测试:
测试2:
public class A07_2 {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerBeanDefinition(
"myBean",
BeanDefinitionBuilder.genericBeanDefinition(MyBean.class)
.setDestroyMethodName("destroy")
.getBeanDefinition()
);
System.out.println(beanFactory.getBean(MyBean.class));
beanFactory.destroySingletons(); // 销毁之后, 仍可创建新的单例
System.out.println(beanFactory.getBean(MyBean.class));
}
static class MyBean {
public MyBean() {
System.out.println("MyBean()");
}
public void destroy() {
System.out.println("destroy()");
}
}
}
可以看出,对于beanFactory 来说,只有手动调用destroySingletons方法,才会去销毁这些单例Bean,销毁完后,可创建新的Bean
Spring 提供了多种初始化手段,除了 @PostConstruct,@Bean(initMethod) 之外,还可以实现 InitializingBean 接口来进行初始化,如果同一个 bean 用了以上手段声明了 3 个初始化方法,那么它们的执行顺序是
与初始化类似,Spring 也提供了多种销毁手段,执行顺序为
在当前版本的 Spring 和 Spring Boot 程序中,支持五种 Scope
有些文章提到有 globalSession 这一 Scope,也是陈旧的说法,目前 Spring 中已废弃
但要注意,如果在 singleton 注入其它 scope 都会有问题,解决方法有
@Scope("session")
@Component
public class BeanForSession {
private static final Logger log = LoggerFactory.getLogger(BeanForSession.class);
@PreDestroy
public void destroy() {
log.info("destroy");
}
}
@Scope("request")
@Component
public class BeanForRequest {
private static final Logger log = LoggerFactory.getLogger(BeanForRequest.class);
@PreDestroy
public void destroy() {
log.info("destroy");
}
}
@Scope("application")
@Component
public class BeanForApplication {
private static final Logger log = LoggerFactory.getLogger(BeanForApplication.class);
@PreDestroy
public void destroy() {
log.info("destroy");
}
}
测试:
@RestController
public class MyController {
@Lazy
@Autowired
private BeanForRequest beanForRequest;
@Lazy
@Autowired
private BeanForSession beanForSession;
@Lazy
@Autowired
private BeanForApplication beanForApplication;
@GetMapping(value = "/test", produces = "text/html")
public String test(HttpServletRequest request, HttpSession session) {
ServletContext sc = request.getServletContext();
String sb = "<ul>" +
"<li>" + "request scope:" + beanForRequest + "</li>" +
"<li>" + "session scope:" + beanForSession + "</li>" +
"<li>" + "application scope:" + beanForApplication + "</li>" +
"</ul>";
return sb;
}
}
/*
singleton, prototype, request, session, application
jdk >= 9 如果反射调用 jdk 中方法,会报访问异常
jdk <= 8 不会有问题
演示 request, session, application 作用域
打开不同的浏览器, 刷新 http://localhost:8080/test 即可查看效果
如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED
*/
@SpringBootApplication
public class A08 {
public static void main(String[] args) {
SpringApplication.run(A08.class, args);
/*
学到了什么
a. 有几种 scope
b. 在 singleton 中使用其它几种 scope 的方法
c. 其它 scope 的销毁
1. 可以将通过 server.servlet.session.timeout=10s 观察 session bean 的销毁---默认30分钟失效
2. ServletContextScope 销毁机制疑似实现有误
*/
}
}
Tomcat的Session实现原理,这里说明了为什么至少需要
其它 scope 的销毁时机
可以将通过 server.servlet.session.timeout=30s 观察 session bean 的销毁
ServletContextScope 销毁机制疑似实现有误,,待定
以单例注入多例为例
@Scope("prototype")
@Component
public class F1 {
}
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}
@Scope("prototype")
@Component
public class F3 {
}
@Scope("prototype")
@Component
public class F4 {
}
有一个单例对象 E
@Component
public class E {
private static final Logger log = LoggerFactory.getLogger(E.class);
private F f;
public E() {
log.info("E()");
}
@Autowired
public void setF(F f) {
this.f = f;
log.info("setF(F f) {}", f.getClass());
}
public F getF() {
return f;
}
}
测试
E e = context.getBean(E.class);
F f1 = e.getF();
F f2 = e.getF();
System.out.println(f1);
System.out.println(f2);
输出
com.dhy.demo.cycle.F@6622fc65
com.dhy.demo.cycle.F@6622fc65
发现它们是同一个对象,而不是期望的多例对象
对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F
@Component
public class E {
@Autowired
@Lazy
public void setF(F f) {
this.f = f;
log.info("setF(F f) {}", f.getClass());
}
// ...
}
注意
输出
E: setF(F f) class com.itheima.demo.cycle.F$$EnhancerBySpringCGLIB$$8b54f2bc
F: F()
com.dhy.demo.cycle.F@3a6f2de3
F: F()
com.dhy.demo.cycle.F@56303b57
从输出日志可以看到调用 setF 方法时,f 对象的类型是代理类型
4种解决方法如果 jdk > 8, 运行时请添加 --add-opens java.base/java.lang=ALL-UNNAMED,否则spring可能会反射调用jdk内部方法,造成访问权限异常被拒
@Component
public class E {
@Lazy
@Autowired
private F1 f1;
@Autowired
private F2 f2;
@Autowired
private ObjectFactory<F3> f3;
@Autowired
private ApplicationContext context;
public F1 getF1() {
return f1;
}
public F2 getF2() {
return f2;
}
public F3 getF3() {
return f3.getObject();
}
public F4 getF4() {
return context.getBean(F4.class);
}
}
对于f2的设置如下:
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}
本质也是生成一个代理对象,每次使用代理对象的任意方法时,由代理创建新的 F2 对象
剩余两种方法,都是从IOC容器中获取bean对象
出现上面scope失效的原因在于在依赖注入阶段就去获取了对应scope的值,然后后续所有使用到该scope类型的对象都是直接返回一开始注入好的值,而不是每次都向容器去获取一下。
因此想要解决这个办法,就必须要推迟socpe bean的获取,方法有上面说的四种
更多细节可以参考本文
关闭。这个问题是opinion-based .它目前不接受答案。 想要改进这个问题? 更新问题,以便 editing this post 可以用事实和引用来回答它. 关闭 9 年前。 Improve
介绍篇 什么是MiniApis? MiniApis的特点和优势 MiniApis的应用场景 环境搭建 系统要求 安装MiniApis 配置开发环境 基础概念 MiniApis架构概述
我正在从“JavaScript 圣经”一书中学习 javascript,但我遇到了一些困难。我试图理解这段代码: function checkIt(evt) { evt = (evt) ? e
package com.fastone.www.javademo.stringintern; /** * * String.intern()是一个Native方法, * 它的作用是:如果字
您会推荐哪些资源来学习 AppleScript。我使用具有 Objective-C 背景的传统 C/C++。 我也在寻找有关如何更好地开发和从脚本编辑器获取更快文档的技巧。示例提示是“查找要编写脚本的
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 4年前关闭。 Improve thi
关闭。这个问题不满足Stack Overflow guidelines .它目前不接受答案。 想改善这个问题吗?更新问题,使其成为 on-topic对于堆栈溢出。 7年前关闭。 Improve thi
关闭。这个问题不符合 Stack Overflow guidelines 。它目前不接受答案。 想改善这个问题吗?更新问题,以便堆栈溢出为 on-topic。 6年前关闭。 Improve this
我是塞内加尔的阿里。我今年60岁(也许这是我真正的问题-笑脸!!!)。 我正在学习Flutter和Dart。今天,我想使用给定数据模型的列表(它的名称是Mortalite,请参见下面的代码)。 我尝试
关闭。这个问题是off-topic .它目前不接受答案。 想改进这个问题? Update the question所以它是on-topic对于堆栈溢出。 9年前关闭。 Improve this que
学习 Cappuccino 的最佳来源是什么?我从事“传统”网络开发,但我对这个新框架非常感兴趣。请注意,我对 Objective-C 毫无了解。 最佳答案 如上所述,该网站是一个好地方,但还有一些其
我正在学习如何使用 hashMap,有人可以检查我编写的这段代码并告诉我它是否正确吗?这个想法是有一个在公司工作的员工列表,我想从 hashMap 添加和删除员工。 public class Staf
我正在尝试将 jQuery 与 CoffeScript 一起使用。我按照博客中的说明操作,指示使用 $ -> 或 jQuery -> 而不是 .ready() 。我玩了一下代码,但我似乎无法理解我出错
还在学习,还有很多问题,所以这里有一些。我正在进行 javascript -> PHP 转换,并希望确保这些做法是正确的。是$dailyparams->$calories = $calories;一条
我目前正在学习 SQL,以便从我们的 Magento 数据库制作一个简单的 RFM 报告,我目前可以通过导出两个查询并将它们粘贴到 Excel 模板中来完成此操作,我想摆脱 Excel 模板。 我认为
我知道我很可能会因为这个问题而受到抨击,但没有人问,我求助于你。这是否是一个正确的 javascript > php 转换 - 在我开始不良做法之前,我想知道这是否是解决此问题的正确方法。 JavaS
除了 Ruby-Doc 之外,哪些来源最适合获取一些示例和教程,尤其是关于 Ruby 中的 Tk/Tile?我发现自己更正常了 http://www.tutorialspoint.com/ruby/r
我只在第一次收到警告。这正常吗? >>> cv=LassoCV(cv=10).fit(x,y) C:\Python27\lib\site-packages\scikit_learn-0.14.1-py
按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visit the
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be
我是一名优秀的程序员,十分优秀!