一、bean创建方式
- xml+<bean/>
- xml:context+注解(@Component+4个@Bean)
- 配置类+扫描+注解(@Component+4个@Bean)
-@Bean定义FactoryBean接口
-@ImportResource
-@Configuration注解的proxyBeanMethods属性 - @Import导入bean的类
-@Import导入配置类 - AnnotationConfigApplicationContext调用register方法
- @Import导入ImportSelector接口
- @Import导入ImportBeanDefinitionRegistrar接口
- @Import导入BeanDefinitionRegistryPostProcessor接口
1. XML方式声明bean
<?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-->
<bean id="bookService"
class="com.heyj.service.impl.BookServiceImpl"
scope="singleton"/>
<!--声明第三方开发bean-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
</beans>
2. XML+注解方式声明bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.heyj"/>
</beans>
- 使用@Component及其衍生注解@Controller 、@Service、@Repository定义bean
@Service
public class BookServiceImpl implements BookService {
}
- 使用@Bean定义第三方bean,并将所在类定义为配置类或Bean
@Component
public class DbConfig {
@Bean
public DruidDataSource getDataSource() {
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
3. 配置类+扫描+注解
- 注解方式声明配置类
@Configuration
@ComponentScan("com.heyj")
public class SpringConfig {
@Bean
public DruidDataSource getDataSource() {
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
3.1 实现FactoryBean接口的类方式
public class BookFactoryBean implements FactoryBean<Book> {
public Book getObject() throws Exception {
Book book = new Book();
// 进行book对象相关的初始化工作
return book;
}
public Class<?> getObjectType() {
return Book.class;
}
}
public class SpringConfig8 {
@Bean
public BookFactoryBean book() { // 创建的Book类型的bean
return new BookFactoryBean();
}
}
3.2 配置类+配置文件(系统迁移)
@Configuration
@ComponentScan("com.itheima")
@ImportResource("applicationContext-config.xml")
public class SpringConfig2 {
}
3.3 使用proxyBeanMethods
表示是否代理配置类中标有@bean的方法,默认是true
- proxyBeanMethods=true 是代理,从容器中获取bean
- proxyBeanMethods=false 不代理,配置类中的@bean方法如同普通方法,每条用一次创建一个bean对象。
@Configuration(proxyBeanMethods = false)
public class SpringConfig3 {
@Bean
public Book book() {
System.out.println("book init ..."); // 每调用一次book()方法都会创建一个Book
return new Book();
}
}
4. 使用@Import导入要注入的bean
- 使用@Import注解导入要注入的bean对应的字节码
@Import(Dog.class)
public class SpringConfig5 {
}
- 被导入的bean无需使用注解声明为bean
public class Dog {
}
此形式可以有效的降低源代码与Spring技术的耦合度
那么,可以
使用@Import注解导入配置类:
@Import(DbConfig.class)
public class SpringConfig {
}
5. 容器注册bean
- 使用上下文对象在容器初始化完毕后注入bean
public class AppImport {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx =
new AnnotationConfigApplicationContext(SpringConfig5.class);
ctx.register(Cat.class);
String[] names = ctx.getBeanDefinitionNames();
for (String name : names) {
System.out.println(name);
}
}
}
6. 导入实现了ImportSelector接口的类
public class MyImportSelector implements ImportSelector {
public String[] selectImports(AnnotationMetadata metadata) {
boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Import");
if (flag) {
return new String[]{"com.heyj.domain.Dog"};
}
return new String[]{"com.heyj.domain.Cat"};
}
}
然后在配置类上@import上面的MyImportSelector.class
7. 导入实现了ImportBeanDefinitionRegistrar接口的类
- 导入实现了ImportBeanDefinitionRegistrar接口的类,通过BeanDefinition的注册器注册实名bean,实现对 容器中bean的裁定,例如对现有bean的覆盖,进而达成不修改源代码的情况下更换实现的效果
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder
.rootBeanDefinition(BookServiceImpl2.class)
.getBeanDefinition();
registry.registerBeanDefinition("bookService", beanDefinition);
}
}
8. 导入实现了BeanDefinitionRegistryPostProcessor接口的类
- 导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean, 实现对容器中bean的最终裁定
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class)
.getBeanDefinition();
registry.registerBeanDefinition("bookService", beanDefinition);
}
}
二、bean创建之控制
1. 根据任意条件确认是否加载bean
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
try {
Class<?> clazz = Class.forName("com.itheima.ebean.Mouse");
if (clazz != null) {
return new String[]{"com.itheima.bean.Cat"};
}
} catch (ClassNotFoundException e) {
return new String[0];
}
return null;
}
}
2. 使用@Conditional注解的派生注解设置各种组合条件控制bean的加载
- 匹配指定类
public class SpringConfig {
@Bean
@ConditionalOnClass(Mouse.class)
public Cat tom() {
return new Cat();
}
}
- 未匹配指定类
public class SpringConfig {
@Bean
@ConditionalOnClass(Mouse.class)
@ConditionalOnMissingClass("com.heyj.bean.Wolf")
public Cat tom() {
return new Cat();
}
}
- 匹配指定类型的bean
@Import(Mouse.class)
public class SpringConfig {
@Bean
@ConditionalOnBean(Mouse.class)
public Cat tom() {
return new Cat();
}
}
- 匹配指定名称的bean
@Import(Mouse.class)
public class SpringConfig {
@Bean
@ConditionalOnBean(name = "com.heyj.bean.Mouse")
public Cat tom() {
return new Cat();
}
}
@Import(Mouse.class)
public class SpringConfig {
@Bean
@ConditionalOnBean(name = "jerry")
public Cat tom() {
return new Cat();
}
}
- 匹配指定环境
public class SpringConfig {
@Bean
@ConditionalOnClass(name = "com.mysql.jdbc.Driver")
public DruidDataSource dataSource() {
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
三、自动配置原理
Springboot遵循“约定优于配置”的原则,使用注解对一些常规的配置项做默认配置,减少或不使用xml配置,让你的项目快速运行起来。Springboot还为大量的开发常用框架封装了starter,如今引入框架只要引入一个starter,你就可以使用这个框架,只需少量的配置甚至是不需要任何配置。
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {SpringApplication.run(MyApplication .class, args);}
}
我们看到,MyApplication作为入口类,入口类中有一个main方法,这个方法其实就是一个标准的Java应用的入口方法,一般在main方法中使用SpringApplication.run()来启动整个应用。值得注意的是,这个入口类要使用@SpringBootApplication注解声明,它是SpringBoot的核心注解。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
// 略
}
从上面看是由3个注解合成的:
- @SpringBootConfiguration 其实就是@Configuration--》@Component
@EnableAutoConfiguration
- @ComponentScan 一般是解析需要扫描的目录
所以,这个注解里面,最主要的就是@EnableAutoConfiguration,这么直白的名字,一看就知道它要开启自动配置,SpringBoot要开始了,于是默默进去@EnableAutoConfiguration的源码。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
// 略
}
可以看到,在@EnableAutoConfiguration注解内使用到了@import注解
来完成导入配置的功能,而EnableAutoConfigurationImportSelector内部则是使用了SpringFactoriesLoader.loadFactoryNames方法进行扫描具有META-INF/spring.factories文件的jar包。下面是1.5.8.RELEASE实现源码:
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
try {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
AnnotationAttributes attributes = getAttributes(annotationMetadata);
//扫描具有META-INF/spring.factories文件的jar包
List<String> configurations = getCandidateConfigurations(annotationMetadata,
attributes);
//去重
configurations = removeDuplicates(configurations);
//排序
configurations = sort(configurations, autoConfigurationMetadata);
//删除需要排除的类
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return configurations.toArray(new String[configurations.size()]);
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
//加载spring.factories实现
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
Assert.notEmpty(configurations,
"No auto configuration classes found in META-INF/spring.factories. If you "
+ "are using a custom packaging, make sure that file is correct.");
return configurations;
}
任何一个springboot应用,都会引入spring-boot-autoconfigure,而spring.factories文件就在该包下面。spring.factories文件是Key=Value形式,多个Value时使用,隔开,该文件中定义了关于初始化,监听器等信息,而真正使自动配置生效的key是org.springframework.boot.autoconfigure.EnableAutoConfiguration,如下所示:
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
//省略
上面的EnableAutoConfiguration配置了多个类,这些都是Spring Boot中的自动配置相关类;在启动过程中会解析对应类配置信息。每个Configuation类都定义了相关bean的实例化配置。都说明了哪些bean可以被自动配置,什么条件下可以自动配置,并把这些bean实例化出来。如果我们自定义了一个starter的话,也要在该starter的jar包中提供 spring.factories文件,并且为其配置org.springframework.boot.autoconfigure.EnableAutoConfiguration对应的配置类。所有框架的自动配置流程基本都是一样的,判断是否引入框架,获取配置参数,根据配置参数初始化框架相应组件。下面的redis相关的自动配置的部分源码:
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(RedisOperations.class) //自动配置必须要有RedisOperations类在classpath里。
@EnableConfigurationProperties(RedisProperties.class) //去加载默认配置信息
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })//导入相应客户端
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")//如果容器中没有名字叫redisTemplate才去创建该bean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
}
我们可以看到自动化配置代码中有很多我们之前没有用到的注解配置:
@Configuration:这个配置就不用多做解释了,我们一直在使用
@EnableConfigurationProperties:这是一个开启使用配置参数的注解,value值就是我们配置实体参数映射的ClassType,将配置实体作为配置来源。
以下为SpringBoot内置条件注解:
@ConditionalOnBean:当SpringIoc容器内存在指定Bean的条件
@ConditionalOnClass:当SpringIoc容器内存在指定Class的条件
@ConditionalOnExpression:基于SpEL表达式作为判断条件
@ConditionalOnJava:基于JVM版本作为判断条件
@ConditionalOnJndi:在JNDI存在时查找指定的位置
@ConditionalOnMissingBean:当SpringIoc容器内不存在指定Bean的条件
@ConditionalOnMissingClass:当SpringIoc容器内不存在指定Class的条件
@ConditionalOnNotWebApplication:当前项目不是Web项目的条件
@ConditionalOnProperty:指定的属性是否有指定的值
@ConditionalOnResource:类路径是否有指定的值
@ConditionalOnSingleCandidate:当指定Bean在SpringIoc容器内只有一个,或者虽然有多个但是指定首选的Bean
@ConditionalOnWebApplication:当前项目是Web项目的条件
以上注解都是元注解@Conditional演变而来的,根据不用的条件对应创建以上的具体条件注解。
四、启动流程
- 初始化各种属性
-环境属性(Environment)
-系统配置(spring.factories)
-参数(Arguments、application.properties) - 创建Spring容器对象ApplicationContext,加载各种配置。
五、事件监听机制
事件的定义:继承ApplicationEvent类即可。
事件监听器的两种方式:
- 实现 ApplicationListener 接口
- 根据接口泛型确定事件类型
- @EventListener 标注监听方法
- 根据监听器方法参数确定事件类型
- 解析时机:在 SmartInitializingSingleton(所有单例初始化完成后),解析每个单例 bean
实现 ApplicationListener 接口的方式
// 定义事件
class MyEvent extends ApplicationEvent { // 事件类需继承ApplicationEvent
public MyEvent(Object source) {
super(source);
}
}
@Component
class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
@Autowired
private ApplicationEventPublisher publisher; // 就是applicationContext
public void doBusiness() {
log.debug("主线业务");
// 主线业务完成后需要做一些支线业务(发布事件)
// 通过bean容器发布
publisher.publishEvent(new MyEvent("MyService.doBusiness()"));
}
}
// 监听器
@Component
class SmsApplicationListener implements ApplicationListener<MyEvent> { //实现ApplicationListener<T>,T是事件类型
private static final Logger log = LoggerFactory.getLogger(SmsApplicationListener.class);
// 主线业务发布事件时,监听器会收到事件,此方法会被触发调用
@Override
public void onApplicationEvent(MyEvent event) {
log.debug("发送短信");
}
}
@Component
class EmailApplicationListener implements ApplicationListener<MyEvent> {
private static final Logger log = LoggerFactory.getLogger(EmailApplicationListener.class);
@Override
public void onApplicationEvent(MyEvent event) {
log.debug("发送邮件");
}
}
使用@EventListener注解标注监听器
// 定义事件
class MyEvent extends ApplicationEvent { // 事件类需继承ApplicationEvent
public MyEvent(Object source) {
super(source);
}
}
@Component
class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
@Autowired
private ApplicationEventPublisher publisher; // 就是applicationContext
public void doBusiness() {
log.debug("主线业务");
// 主线业务完成后需要做一些支线业务(发布事件)
// 通过bean容器发布
publisher.publishEvent(new MyEvent("MyService.doBusiness()"));
}
}
@Component
class SmsService {
private static final Logger log = LoggerFactory.getLogger(SmsService.class);
// 监听器
@EventListener
public void listener(MyEvent myEvent) {
log.debug("发送短信");
}
}
@Component
class EmailService {
private static final Logger log = LoggerFactory.getLogger(EmailService.class);
@EventListener
public void listener(MyEvent myEvent) {
log.debug("发送邮件");
}
}
异步处理
// 把监听器交给线程池去执行
@Bean
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
return executor;
}
@Bean
public SimpleApplicationEventMulticaster applicationEventMulticaster(ThreadPoolTaskExecutor executor) {
SimpleApplicationEventMulticaster multicaster = new SimpleApplicationEventMulticaster();
multicaster.setTaskExecutor(executor);
return multicaster;
}