目录
自动配置工作流程
bean的加载方式
bean的加载控制
bean的依赖属性配置管理
自动配置原理
自定义starter开发
springboot启动流程
自动配置工作流程
bean的加载方式
关于bean的加载方式,spring提供了各种各样的形式。spring管理bean整体上来说就是由spring维护对象的生命周期。
方式一:提供bean的类名,然后spring就可以根据配置文件管理了,内部就是反射机制加载成class
<!--xml方式声明自己开发的bean-->
<bean id="cat" class="Cat"/>
<bean class="Dog"/>
<!--xml方式声明第三方开发的bean-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"/>
方式二:使用@Component以及三个衍生注解@Service、@Controller、@Repository。如果是第三方bean,则使用@Bean定义在一个方法上方,当前方法的返回值就可以作为bean交给spring管控,记得这个方法所在的类一定要定义在@Component修饰的类中
@Component
public class DbConfig {
@Bean
public DruidDataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
return ds;
}
}
<!--指定扫描加载bean的位置-->
<context:component-scan base-package="com.itheima.bean,com.itheima.config"/>
</beans>
方式三:全注解方式声明配置类,替代原始xml配置中的包扫描,还可以使用FactroyBean接口声明bean,好处是可以在对象初始化前做一些事情
@ComponentScan({"com.itheima.bean","com.itheima.config"})
public class SpringConfig3 {
@Bean
public DogFactoryBean dog(){
return new DogFactoryBean();
}
}
public class DogFactoryBean implements FactoryBean<Dog> {
@Override
public Dog getObject() throws Exception {
Dog d = new Dog();
//.........
return d;
}
@Override
public Class<?> getObjectType() {
return Dog.class;
}
@Override
public boolean isSingleton() {
return true;
}
}
如需导入XML格式配置的bean,在配置类上直接写上@ImportResource注解
@Configuration
@ImportResource("applicationContext1.xml")
public class SpringConfig32 {
}
方式四:使用@Import注解精准制导加载bean和配置类
@Import({Dog.class,DbConfig.class})
public class SpringConfig4 {
}
方式五:编程形式注册bean
public class App5 {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
//上下文容器对象已经初始化完毕后,手工加载bean
ctx.register(Mouse.class);
}
}
方式六:导入实现了ImportSelector接口的类,在容器初始化过程中进行控制
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata metadata) {
//各种条件的判定,判定完毕后,决定是否装载指定的bean
boolean flag = metadata.hasAnnotation("org.springframework.context.annotation.Configuration");
if(flag){
return new String[]{"com.itheima.bean.Dog"};
}
return new String[]{"com.itheima.bean.Cat"};
}
}
方式七:导入实现了ImportBeanDefinitionRegistrar接口的类
public class MyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
BeanDefinition beanDefinition =
BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl2.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
}
方式八:导入实现了BeanDefinitionRegistryPostProcessor接口的类
public class MyPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
BeanDefinition beanDefinition =
BeanDefinitionBuilder.rootBeanDefinition(BookServiceImpl4.class).getBeanDefinition();
registry.registerBeanDefinition("bookService",beanDefinition);
}
}
bean的加载控制
后面四种可编程控制的bean的初始化方式,通过分支语句由固定的加载bean转成了可以选择bean是否加载或者选择加载哪一种bean,在spring容器中,通过判定是否加载了某个类来控制某些bean的加载是一种常见操作。
//当虚拟机中加载了com.itheima.bean.Wolf类且没有加载mouse类时加载对应的bean
@Bean
@ConditionalOnClass(name = "com.itheima.bean.Wolf")
@ConditionalOnMissingClass("com.itheima.bean.Mouse")
public Cat tom(){
return new Cat();
}
bean的依赖属性配置管理
bean在运行的时候,实现对应的业务逻辑时有可能需要开发者提供一些设置值,我们通过yml配置文件,设置bean运行需要使用的配置信息。
cartoon:
cat:
name: "图多盖洛"
age: 5
mouse:
name: "泰菲"
age: 1
然后定义一个封装属性的专用类,加载配置属性,读取对应前缀相关的属性值。
@ConfigurationProperties(prefix = "cartoon")
@Data
public class CartoonProperties {
private Cat cat;
private Mouse mouse;
}
最后在使用的位置注入对应的配置即可。建议在业务类上使用@EnableConfigurationProperties声明bean,这样在不使用这个类的时候,也不会无故加载专用的属性配置类CartoonProperties,减少spring管控的资源数量。
@EnableConfigurationProperties(CartoonProperties.class)
public class CartoonCatAndMouse{
@Autowired
private CartoonProperties cartoonProperties;
}
自动配置原理
springboot看你导入了什么类猜测你要做什么事情,然后把你要用的bean都给你准备好。你提供必要的参数后直接使用就行了,自动配置的意义就是加速开发效率。自动配置工作流程如下:
- springboot启动时先加载spring.factories文件中的org.springframework.boot.autoconfigure.EnableAutoConfiguration配置项,将其中配置的所有的类都加载成bean
- 在加载bean的时候,bean对应的类定义上都设置有加载条件,因此有可能加载成功,也可能条件检测失败不加载bean
- 对于可以正常加载成bean的类,通常会通过@EnableConfigurationProperties注解初始化对应的配置属性类并加载对应的配置
- 配置属性类上通常会通过@ConfigurationProperties加载指定前缀的配置,当然这些配置通常都有默认值。如果没有默认值,就强制你必须配置后使用了
自定义starter开发
自定义starter实现自定义功能的快捷添加,功能要与原始项目完全解耦,需求是当用户访问网站时,对用户的访问行为进行统计,在后台每10秒输出一次监控信息(格式:IP+访问次数)。
先定义一个业务类,声明一个Map对象,用于记录ip访问次数,key是ip地址,value是访问次数
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
}
制作统计功能
@Autowired
//当前的request对象的注入工作由使用当前starter的工程提供自动装配
private HttpServletRequest httpServletRequest;
public void count(){
//每次调用当前操作,就记录当前访问的IP,然后累加访问次数
//1.获取当前操作的IP地址
String ip = httpServletRequest.getRemoteAddr();
//2.根据IP地址从Map取值,并递增
Integer count = ipCountMap.get(ip);
if(count == null){
ipCountMap.put(ip,1);
}else{
ipCountMap.put(ip,count + 1);
}
}
我们需要做到的效果是导入当前模块即开启此功能,因此使用自动配置实现功能的自动装载,需要开发自动配置类在启动项目时加载当前功能。
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}
自动配置类需要在spring.factories文件中做配置方可自动运行。
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.itcast.autoconfig.IpAutoConfiguration
原始调用项目中导入当前开发的starter,并在分页查询中调用
<dependency>
<groupId>cn.itcast</groupId>
<artifactId>ip_spring_boot_starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
@RestController
@RequestMapping("/books")
public class BookController {
@Autowired
private IpCountService ipCountService;
@GetMapping("{currentPage}/{pageSize}")
public R getPage(@PathVariable int currentPage,@PathVariable int pageSize,Book book){
ipCountService.count();
IPage<Book> page = bookService.getPage(currentPage, pageSize,book);
if( currentPage > page.getPages()){
page = bookService.getPage((int)page.getPages(), pageSize,book);
}
return new R(true, page);
}
}
当前功能的总配置中设置定时任务功能开启
@EnableScheduling
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}
定义显示统计功能的操作print(),并设置定时任务,当前设置每5秒运行一次统计数据
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
@Scheduled(cron = "0/5 * * * * ?")
public void print(){
System.out.println(" IP访问监控");
System.out.println("+-----ip-address-----+--num--+");
for (Map.Entry<String, Integer> entry : ipCountMap.entrySet()) {
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(String.format("|%18s |%5d |",key,value));
}
System.out.println("+--------------------+-------+");
}
}
使用属性配置设置功能参数,设置3个属性,分别用来控制显示周期(cycle),阶段数据是否清空(cycleReset),数据显示格式(model)
tools:
ip:
cycle: 10
cycleReset: false
model: "detail"
定义封装参数的属性类,读取参数
@ConfigurationProperties(prefix = "tools.ip")
@Component("ipProperties")
public class IpProperties {
/**
* 日志显示周期
*/
private Long cycle = 5L;
/**
* 是否周期内重置数据
*/
private Boolean cycleReset = false;
/**
* 日志输出模式 detail:详细模式 simple:极简模式
*/
private String model = LogModel.DETAIL.value;
public enum LogModel{
DETAIL("detail"),
SIMPLE("simple");
private String value;
LogModel(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}
加载属性类
@EnableScheduling
//@EnableConfigurationProperties(IpProperties.class)
@Import(IpProperties.class)
public class IpAutoConfiguration {
@Bean
public IpCountService ipCountService(){
return new IpCountService();
}
}
在功能类中使用自动装配加载对应的配置bean,然后使用配置信息做分支处理。
public class IpCountService {
private Map<String,Integer> ipCountMap = new HashMap<String,Integer>();
@Autowired
private IpProperties ipProperties;
@Scheduled(cron = "0/#{ipProperties.cycle} * * * * ?")
public void print(){
if(ipProperties.getModel().equals(IpProperties.LogModel.DETAIL.getValue())){
...
}else if(ipProperties.getModel().equals(IpProperties.LogModel.SIMPLE.getValue())){
...
}
//阶段内统计数据归零
if(ipProperties.getCycleReset()){
ipCountMap.clear();
}
}
}
springboot启动流程
启动过程本质上都是在做容器的初始化,并将对应的bean初始化出来放入容器。于其说是springboot程序的启动流程,不如说是springboot对spring程序的启动流程做了哪些更改。springboot启动流程是先初始化容器需要的各种配置,并加载成各种对象,初始化容器时读取这些对象,整体流程采用事件监听的机制进行过程控制,开发者可以根据需要自行扩展,添加对应的监听器绑定具体事件,就可以在事件触发位置执行开发者的业务代码。创建容器springboot提供参数设置的环节划分为如下3类:
- 环境属性(Environment)
- 系统配置(spring.factories)
- 参数(Arguments、application.properties)
//设定监听器,在应用启动开始事件时进行功能追加
public class MyListener implements ApplicationListener<ApplicationStartingEvent> {
public void onApplicationEvent(ApplicationStartingEvent event) {
//自定义事件处理逻辑
}
}
Springboot30StartupApplication【10】->SpringApplication.run(Springboot30StartupApplication.class, args);
SpringApplication【1332】->return run(new Class<?>[] { primarySource }, args);
SpringApplication【1343】->return new SpringApplication(primarySources).run(args);
SpringApplication【1343】->SpringApplication(primarySources)
# 加载各种配置信息,初始化各种配置对象
SpringApplication【266】->this(null, primarySources);
SpringApplication【280】->public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources)
SpringApplication【281】->this.resourceLoader = resourceLoader;
# 初始化资源加载器
SpringApplication【283】->this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
# 初始化配置类的类名信息(格式转换)
SpringApplication【284】->this.webApplicationType = WebApplicationType.deduceFromClasspath();
# 确认当前容器加载的类型
SpringApplication【285】->this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
# 获取系统配置引导信息
SpringApplication【286】->setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
# 获取ApplicationContextInitializer.class对应的实例
SpringApplication【287】->setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
# 初始化监听器,对初始化过程及运行过程进行干预
SpringApplication【288】->this.mainApplicationClass = deduceMainApplicationClass();
# 初始化了引导类类名信息,备用
SpringApplication【1343】->new SpringApplication(primarySources).run(args)
# 初始化容器,得到ApplicationContext对象
SpringApplication【323】->StopWatch stopWatch = new StopWatch();
# 设置计时器
SpringApplication【324】->stopWatch.start();
# 计时开始
SpringApplication【325】->DefaultBootstrapContext bootstrapContext = createBootstrapContext();
# 系统引导信息对应的上下文对象
SpringApplication【327】->configureHeadlessProperty();
# 模拟输入输出信号,避免出现因缺少外设导致的信号传输失败,进而引发错误(模拟显示器,键盘,鼠标...)
java.awt.headless=true
SpringApplication【328】->SpringApplicationRunListeners listeners = getRunListeners(args);
# 获取当前注册的所有监听器
SpringApplication【329】->listeners.starting(bootstrapContext, this.mainApplicationClass);
# 监听器执行了对应的操作步骤
SpringApplication【331】->ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
# 获取参数
SpringApplication【333】->ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
# 将前期读取的数据加载成了一个环境对象,用来描述信息
SpringApplication【333】->configureIgnoreBeanInfo(environment);
# 做了一个配置,备用
SpringApplication【334】->Banner printedBanner = printBanner(environment);
# 初始化logo
SpringApplication【335】->context = createApplicationContext();
# 创建容器对象,根据前期配置的容器类型进行判定并创建
SpringApplication【363】->context.setApplicationStartup(this.applicationStartup);
# 设置启动模式
SpringApplication【337】->prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
# 对容器进行设置,参数来源于前期的设定
SpringApplication【338】->refreshContext(context);
# 刷新容器环境
SpringApplication【339】->afterRefresh(context, applicationArguments);
# 刷新完毕后做后处理
SpringApplication【340】->stopWatch.stop();
# 计时结束
SpringApplication【341】->if (this.logStartupInfo) {
# 判定是否记录启动时间的日志
SpringApplication【342】-> new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
# 创建日志对应的对象,输出日志信息,包含启动时间
SpringApplication【344】->listeners.started(context);
# 监听器执行了对应的操作步骤
SpringApplication【345】->callRunners(context, applicationArguments);
# 调用运行器
SpringApplication【353】->listeners.running(context);
# 监听器执行了对应的操作步骤