- SpringBoot自定配置
- SpringBoot事件监听
- SpringBoot流程分析
- SpringBoot监控
- SpringBoot部署
1. SpringBoot原理分析
1.1 SpringBoot自动配置
Condition(其实就是根据这个条件确定是否创建该Bean)
引入1
我们用Spring Initializer创建一个SpringBoot工程,什么依赖也不引入!此时自然只有spring-boot-starter-web、spring-boot-starter-test依赖和spring-boot-maven-plugin插件。
然后在启动类上这么操作:
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
//启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
//获取Bean,redisTemplate
Object redisTemplate = context.getBean("redisTemplate");
System.out.println(redisTemplate);
}
}
运行该项目,则报错!No Bean named “redisTemplate” available!
如果我们添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
再次测试:竟然就可以了!
那SpringBoot怎么知道我有没有导这个redis的坐标呢??
那么下面我们就讲一下这个Condition。SpringBoot就是当前环境中有没有创建redis的条件,有就创建!
引入2
创建User类和配置类,里面有User对象
public class User {
}
@Configuration
public class UserConfig {
@Bean
public User user(){
return new User();
}
}
获取User:显然可以获取到,没有什么问题!选择呢?是导入或者不导入Jedis坐标都能加载,这不满足我们的需求!
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
//启动SpringBoot的应用,返回Spring的IOC容器
ConfigurableApplicationContext context = SpringApplication.run(SpringbootConditionApplication.class, args);
Object user = context.getBean("user2");
System.out.println(user);
}
}
修改配置类,添加@Conditional
点击进入@Conditional,看到其需要的参数value必须是Condition的子类
@Configuration
public class UserConfig {
@Bean
@Conditional(ClassCondition.class) // 该类见下方
public User user(){
return new User();
}
}
// @Conditional
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {
Class<? extends Condition>[] value();
}
因此,我们定义一个子类,实现Condition接口(函数式接口,里面只要matches方法,返回true或者false)
public class ClassCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return false;
}
}
此时运行:则是No Bean named “user” available!
如果把上面返回值设置为true,则运行成功!
引入3
由上我们可以判断,我们是否要加载这个类,只需要在这个子类ClassCondition中判断即可。
这里我们导入坐标
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
那么这个类中怎么判断我们是否导入这个坐标呢?其实非常简单!我们引入了这个坐标,自然可以使用Jedis这个类,即这个类是存在的!
因此我们只需要通过反射看是否能拿到这个类的字节码文件即可!
这样需求就完成了。
如果有坐标,则成功!没有坐标,则No Bean named “user” available!
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象。用于获取环境,IOC容器,ClassLoader对象
* @param metadata 注解元对象。 可以用于获取注解定义的属性值
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//1.需求: 导入Jedis坐标后创建Bean
//思路:判断redis.clients.jedis.Jedis.class文件是否存在
boolean flag = true;
try {
Class<?> cls = Class.forName("redis.clients.jedis.Jedis");
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;
}
}
引入4
上面的字节码文件是我们写死的,那么我们是否可以动态指定哪个字节码文件存在呢?
我们自定义一个注解,完成和刚刚的@Conditional注解一样的功能!
@Target({ElementType.TYPE, ElementType.METHOD}) // 元注解
@Retention(RetentionPolicy.RUNTIME) // 元注解
@Documented // 元注解
@Conditional(ClassCondition.class) // 添加上这个注解,那么我们定义的注解就有了和它一样的能力
public @interface ConditionOnClass {
String[] value(); // 使用这个注解,我们可以设置value属性
}
在配置类上使用注解
@Configuration
public class UserConfig {
@ConditionOnClass("com.alibaba.fastjson.JSON")
public User user(){
return new User();
}
}
此时运行项目:如果注释Jedis坐标,则还是报错,如果没有注释,则成功!
那是不是需求完成了???当你不是,因为我们的ClassCondition根本就没有变化!
引入5
我们只需要获取自定义注解的属性即可完成上面需求
现在我们就需要知道match方法中两个参数的含义了!!
public class ClassCondition implements Condition {
/**
*
* @param context 上下文对象。用于获取环境(前面讲过,可以通过Environment获取配置文件属性),IOC容器,ClassLoader对象等等
* @param metadata 注解元对象。 可以用于获取注解定义的属性值,可以获得所有注解的属性信息
* @return
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
//2.需求: 导入通过注解属性值value指定坐标后创建Bean
//获取注解属性值 value
Map<String, Object> map = metadata.getAnnotationAttributes(ConditionOnClass.class.getName());
//System.out.println(map);
String[] value = (String[]) map.get("value"); // 自然还是数组
boolean flag = true;
try {
for (String className : value) {
Class<?> cls = Class.forName(className); // 只要我们定义的注解里面的值有一个不存在,就出错
}
} catch (ClassNotFoundException e) {
flag = false;
}
return flag;
}
}
当然,你选择也可以设置多个坐标了!!自己测试吧!!在UserConfig中配置即可。
引入6
再回过头看这个思考题,redis是如何知道是否要创建RedisTemplate的Bean??就是根据Condition注解判断你是由有其依赖!!
我们找到下图的jar包
可以看到里面有五个注解,其中这个ConditionOnClass就和上面我们自己定义的注解效果是一样的。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({OnClassCondition.class})
public @interface ConditionalOnClass {
Class<?>[] value() default {};
String[] name() default {};
}
其上面添加了@Conditional({OnClassCondition.class}),其中这个OnClassCondition.class自然就是和上面我们自己写的实现类是差不多的效果,里面是自动根据你添加的ConditionalOnClass注解的属性来确定是否创建该Bean。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure.condition;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.springframework.boot.autoconfigure.AutoConfigurationMetadata;
import org.springframework.boot.autoconfigure.condition.ConditionMessage.Style;
import org.springframework.boot.autoconfigure.condition.FilteringSpringBootCondition.ClassNameFilter;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.annotation.Order;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
@Order(-2147483648)
class OnClassCondition extends FilteringSpringBootCondition {
OnClassCondition() {
}
protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, AutoConfigurationMetadata autoConfigurationMetadata) {
int split = autoConfigurationClasses.length / 2;
OnClassCondition.OutcomesResolver firstHalfResolver = this.createOutcomesResolver(autoConfigurationClasses, 0, split, autoConfigurationMetadata);
OnClassCondition.OutcomesResolver secondHalfResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, split, autoConfigurationClasses.length, autoConfigurationMetadata, this.getBeanClassLoader());
ConditionOutcome[] secondHalf = secondHalfResolver.resolveOutcomes();
ConditionOutcome[] firstHalf = firstHalfResolver.resolveOutcomes();
ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
System.arraycopy(firstHalf, 0, outcomes, 0, firstHalf.length);
System.arraycopy(secondHalf, 0, outcomes, split, secondHalf.length);
return outcomes;
}
private OnClassCondition.OutcomesResolver createOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
OnClassCondition.StandardOutcomesResolver outcomesResolver = new OnClassCondition.StandardOutcomesResolver(autoConfigurationClasses, start, end, autoConfigurationMetadata, this.getBeanClassLoader());
try {
return new OnClassCondition.ThreadedOutcomesResolver(outcomesResolver);
} catch (AccessControlException var7) {
return outcomesResolver;
}
}
public ConditionOutcome getMatchOutcome(ConditionContext context, AnnotatedTypeMetadata metadata) {
ClassLoader classLoader = context.getClassLoader();
ConditionMessage matchMessage = ConditionMessage.empty();
List<String> onClasses = this.getCandidates(metadata, ConditionalOnClass.class);
List onMissingClasses;
if (onClasses != null) {
onMissingClasses = this.filter(onClasses, ClassNameFilter.MISSING, classLoader);
if (!onMissingClasses.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class", "required classes").items(Style.QUOTE, onMissingClasses));
}
matchMessage = matchMessage.andCondition(ConditionalOnClass.class, new Object[0]).found("required class", "required classes").items(Style.QUOTE, this.filter(onClasses, ClassNameFilter.PRESENT, classLoader));
}
onMissingClasses = this.getCandidates(metadata, ConditionalOnMissingClass.class);
if (onMissingClasses != null) {
List<String> present = this.filter(onMissingClasses, ClassNameFilter.PRESENT, classLoader);
if (!present.isEmpty()) {
return ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnMissingClass.class, new Object[0]).found("unwanted class", "unwanted classes").items(Style.QUOTE, present));
}
matchMessage = matchMessage.andCondition(ConditionalOnMissingClass.class, new Object[0]).didNotFind("unwanted class", "unwanted classes").items(Style.QUOTE, this.filter(onMissingClasses, ClassNameFilter.MISSING, classLoader));
}
return ConditionOutcome.match(matchMessage);
}
private List<String> getCandidates(AnnotatedTypeMetadata metadata, Class<?> annotationType) {
MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(annotationType.getName(), true);
if (attributes == null) {
return null;
} else {
List<String> candidates = new ArrayList();
this.addAll(candidates, (List)attributes.get("value"));
this.addAll(candidates, (List)attributes.get("name"));
return candidates;
}
}
private void addAll(List<String> list, List<Object> itemsToAdd) {
if (itemsToAdd != null) {
Iterator var3 = itemsToAdd.iterator();
while(var3.hasNext()) {
Object item = var3.next();
Collections.addAll(list, (String[])((String[])item));
}
}
}
private final class StandardOutcomesResolver implements OnClassCondition.OutcomesResolver {
private final String[] autoConfigurationClasses;
private final int start;
private final int end;
private final AutoConfigurationMetadata autoConfigurationMetadata;
private final ClassLoader beanClassLoader;
private StandardOutcomesResolver(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata, ClassLoader beanClassLoader) {
this.autoConfigurationClasses = autoConfigurationClasses;
this.start = start;
this.end = end;
this.autoConfigurationMetadata = autoConfigurationMetadata;
this.beanClassLoader = beanClassLoader;
}
public ConditionOutcome[] resolveOutcomes() {
return this.getOutcomes(this.autoConfigurationClasses, this.start, this.end, this.autoConfigurationMetadata);
}
private ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses, int start, int end, AutoConfigurationMetadata autoConfigurationMetadata) {
ConditionOutcome[] outcomes = new ConditionOutcome[end - start];
for(int i = start; i < end; ++i) {
String autoConfigurationClass = autoConfigurationClasses[i];
if (autoConfigurationClass != null) {
String candidates = autoConfigurationMetadata.get(autoConfigurationClass, "ConditionalOnClass");
if (candidates != null) {
outcomes[i - start] = this.getOutcome(candidates);
}
}
}
return outcomes;
}
private ConditionOutcome getOutcome(String candidates) {
try {
if (!candidates.contains(",")) {
return this.getOutcome(candidates, this.beanClassLoader);
}
String[] var2 = StringUtils.commaDelimitedListToStringArray(candidates);
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
String candidate = var2[var4];
ConditionOutcome outcome = this.getOutcome(candidate, this.beanClassLoader);
if (outcome != null) {
return outcome;
}
}
} catch (Exception var7) {
}
return null;
}
private ConditionOutcome getOutcome(String className, ClassLoader classLoader) {
return ClassNameFilter.MISSING.matches(className, classLoader) ? ConditionOutcome.noMatch(ConditionMessage.forCondition(ConditionalOnClass.class, new Object[0]).didNotFind("required class").items(Style.QUOTE, new Object[]{className})) : null;
}
}
private static final class ThreadedOutcomesResolver implements OnClassCondition.OutcomesResolver {
private final Thread thread;
private volatile ConditionOutcome[] outcomes;
private ThreadedOutcomesResolver(OnClassCondition.OutcomesResolver outcomesResolver) {
this.thread = new Thread(() -> {
this.outcomes = outcomesResolver.resolveOutcomes();
});
this.thread.start();
}
public ConditionOutcome[] resolveOutcomes() {
try {
this.thread.join();
} catch (InterruptedException var2) {
Thread.currentThread().interrupt();
}
return this.outcomes;
}
}
private interface OutcomesResolver {
ConditionOutcome[] resolveOutcomes();
}
}
那在哪里使用这个注解呢?自然是在配置类上面了
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure.data.redis;
import java.net.UnknownHostException;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
@Configuration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
此外,我们看到上面还有其它几个注解:
ConditionalOnBean:当有这个Bean是才创建
ConditionalOnClass:当有这个字节码文件时才创建
ConditionalOnMissingBean:当没有这个Bean是才创建
ConditionalOnMissingClass:当没有这个字节码文件时才创建
ConditionalOnProperty:当配置文件有某个属性才创建。
引入7
体验上面几个SpringBoot自己创建的注解:以ConditionalOnProperty为例
@Configuration
public class UserConfig {
@Bean
@ConditionalOnProperty(name = "itcast",havingValue = "itheima")
public User user2(){
return new User();
}
}
这个意思是配置文件中有itcast=itheima时才创建Bean。
小结(算引入8)
- 自定义条件:
- ①定义条件类:自定义类实现Condition接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。matches方法两个参数:
- context:上下文对象,可以获取属性值,获取类加载器,获取BeanFactory等。
- metadata:元数据对象,用于获取注解属性
- 判断条件:在数据对象,用于获取注解属性
- 但是只是通过Conditional注解,不能完成自定义坐标的实现,因为你要在Condition接口实现类中写死。因此我们上面学习了自定义注解@ConditionalOnCalss
- ①定义条件类:自定义类实现Condition接口,重写matches方法,在matches方法中进行逻辑判断,返回boolean值。matches方法两个参数:
- SpringBoot提供的常用条件注解
- ConditionalOnProperty
- ConditionalOnClass
- ConditionalOnMissingBean
以后我们自然不需要自己定义注解了,直接用它提供的注解即可,这些注解上面已经添了@Conditional({OnClassCondition.class})注解
而且其内部的实现类OnClassCondition.class自然是也有的,而且也是动态判断的。
因此我们以后使用只需要在配置为的Bean中直接使用这些注解即可,比如!!!!!!!!!!!!!!!!!!!!!!!!!
@Configuration
public class UserConfig {
@Bean
@ConditionalOnProperty(name = "itcast",havingValue = "itheima")
public User user2(){
return new User();
}
}
切换内置web服务器
SpringBoot的web环境中默认使用tomcat作为内置服务器,其实SpringBoot提供了4种内置服务器供我们选择,我们可以很方便的进行切换。
我们只需要引入web依赖即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
如果没有这个依赖:启动项目:也就没有web的日志信息
如果有该依赖,就会有tomcat信息
我们在哪里查看这个SpringBoot默认的容器呢?
还是在刚刚的autoconfigure包下,刚刚看的是condition包,现在找到web包,我们进入这个内部类
里面有四个静态内部类,分别对应四个服务器,对于每个服务器的Bean,都有@ConditionalOnClass注解,我们以Tomcat为例,其需要有字节码文件 @ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
我们引入web依赖后,里面包含了tomcat依赖,包含着两个字节码文件。如下图:
然后引入新的jetty依赖,如下,此时再次启动项目,发现是jetty日志
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<!--排除tomcat依赖-->
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--引入jetty的依赖-->
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.boot.autoconfigure.web.embedded;
import io.undertow.Undertow;
import org.apache.catalina.startup.Tomcat;
import org.apache.coyote.UpgradeProtocol;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.xnio.SslClientAuthMode;
import reactor.netty.http.server.HttpServer;
@Configuration
@ConditionalOnWebApplication
@EnableConfigurationProperties({ServerProperties.class})
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
public EmbeddedWebServerFactoryCustomizerAutoConfiguration() {
}
@Configuration
@ConditionalOnClass({HttpServer.class})
public static class NettyWebServerFactoryCustomizerConfiguration {
public NettyWebServerFactoryCustomizerConfiguration() {
}
@Bean
public NettyWebServerFactoryCustomizer nettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new NettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration
@ConditionalOnClass({Undertow.class, SslClientAuthMode.class})
public static class UndertowWebServerFactoryCustomizerConfiguration {
public UndertowWebServerFactoryCustomizerConfiguration() {
}
@Bean
public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration
@ConditionalOnClass({Server.class, Loader.class, WebAppContext.class})
public static class JettyWebServerFactoryCustomizerConfiguration {
public JettyWebServerFactoryCustomizerConfiguration() {
}
@Bean
public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new JettyWebServerFactoryCustomizer(environment, serverProperties);
}
}
@Configuration
@ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
public static class TomcatWebServerFactoryCustomizerConfiguration {
public TomcatWebServerFactoryCustomizerConfiguration() {
}
@Bean
public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
}
}
}
Enable*注解原理
引入1
对于启动类,上面有一个@SpringBootApplication注解,点进去;上面4个是元注解,不用管,@SpringBootConfiguration点进去,发现其即使一个@Configuration注解。这说明启动类是配置类,可以在里面定义Bean。
对于@EnableAutoConfiguration这种前面以Enable开头的注解,是本节课讨论的重点
对于@ComponentScan是扫描什么,暂时先这么理解。
@SpringBootApplication
public class SpringbootConditionApplication {
public static void main(String[] args) {
}
}
@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 {}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {}
引入2
@Enable*注解
SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启动某些功能的。而其底层原理是使用@Import注解导入一些配置类,实现Bean的动态加载。
思考题
答案自然是不可以,如果不可以,就很严重了,比如前面的redisTemplate的获取。
那为什么引入redis的启动依赖,就可以直接获取到呢??
引入3
创建一个新的Model,名为springboot-enable,用Spring Intializr创建,但是不导入任何依赖。此外,还要把测试依赖和maven插件取出。让这个项目越简单,越好。只有下依赖(其我猜测,连下你这个依赖都不需要,你只需要是Maven工程即可)
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
里面定义一个User类,和提个配置类,里面含有user的bean。途中其它类等先忽略。
public class User {
}
@Configuration
public class UserConfig {
@Bean
public User user() {
return new User();
}
}
然后再创建一个Model,名为springboot-enable,在这个Model的pom文件引入上面model的依赖,然后在测试类获得该user的bean。但是获取失败,为什么??
因为@ComponentScan扫描的是当前引导类所在包及其子包,你配置类的包是com.itheima.config
/**
* @ComponentScan 扫描范围:当前引导类所在包及其子包
*
* com.itheima.springbootenable
* com.itheima.config
* //1.使用@ComponentScan扫描com.itheima.config包
* //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
* //3.可以对Import注解进行封装。
*/
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
//获取Bean
Object user = context.getBean("user");
System.out.println(user);
}
}
引入4
解决方案,那怎么能获得这个user对象呢?即这个配置类怎么才被Spring容器加载呢?
三种方法:
/**
* @ComponentScan 扫描范围:当前引导类所在包及其子包
*
* com.itheima.springbootenable
* com.itheima.config
* //1.使用@ComponentScan扫描com.itheima.config包
* //2.可以使用@Import注解,加载类。这些类都会被Spring创建,并放入IOC容器
* //3.可以对Import注解进行封装。
*/
对于第一种方法:就是在这个启动类上添加这个包扫描@ComponentScan(“com.itheima.config”),但是这个方法有点low,你用别人的配置类,你还要写一下扫描
第二种方法:使用Impor注解,使用这个注解,则里面的类会被spring容器加载并创建。还是在启动类上加这个注解@Import(UserConfig.class),此时你依然可以获得user对象成功。这种也很low,和上面low的方式一样。
第三种:就是封装Import注解,封装的注解不是在这个启动类的模块里面,而是在引用的模块中
你要使用,则在启动类上加这个注解@EnableUser,这样显的就有点高大上了。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(UserConfig.class) // 这个就是第二种方式,用这个注解,相当于用了第二种方式
public @interface EnableUser {
}
我们现在重新审视启动类上的这个注解@EnableAutoConfiguration,这个注解:SpringBoot中提供了很多Enable开头的注解,这些注解都是用于动态启动某些功能的。而其底层原理是使用@Import注解导入一些配置类(当然也不一定是配置类),实现Bean的动态加载。
对于@EnableAutoConfiguration注解点进去,其里面也有Import注解,其实核心的不是@EnableAuto*注解怎么用,而是Import注解怎么用,下一个引用我们讲一下Import的不同使用方式!
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = “spring.boot.enableautoconfiguration”;
Class<?>[] exclude() default {};
String[] excludeName() default {};<br />}
@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 {}
引入5
@Import注解
@Enable底层依赖于@Import注解导入一些类,使用@Import导入的类会被Spring加载到IOC容器中。而@Import提供4中用法:
①导入Bean(这个很简单)
②导入配置类(上面演示过了,最终配置类的所有Bean都会被加载进容器)
③导入 ImportSelector 实现类。一般用于加载配置文件中的类(下面演示)
④导入 ImportBeanDefinitionRegistrar 实现类。(下面演示)
*演示一:导入Bean
配置类上直接导入:获取失败(这是为什么??)
@Import(User.class)
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
//获取Bean
Object user = context.getBean("user");
System.out.println(user);
}
}
我们修改下代码:用User.clas获取,这个获取成功了,然后获取其名字和类型的Map,发现其名字是com.itheima.domain.User。我们导入的User.class是domain包下的,名字不是user。
但不管怎么说,成功了。
@Import(User.class)
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
User user = context.getBean(User.class);
System.out.println(user);
Map<String, User> map = context.getBeansOfType(User.class);
System.out.println(map);
}
}
演示二:②导入配置类
我们重新更改下配置类所在的Model,其实也没啥改的,就是多加了一个类,方便看效果
public class Role {
}
public class User{
}
@Configuration
public class UserConfig {
@Bean
public User user() {
return new User();
}
@Bean
public Role role() {
return new Role();
}
}
测试:或无疑问,成功!此外需要注意的是,如果你用@Import(UserConfig.class)的话,配置类上的注解@Configuration是可以不加的,一样正确。
@Import(UserConfig.class)
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
User user = context.getBean(User.class);
System.out.println(user);
Role role = context.getBean(Role.class);
System.out.println(role);
}
}
演示三:③导入 ImportSelector 实现类。一般用于加载配置文件中的类
先看下这个接口,方法参数AnnotationMetadata是注解元数据,显然可以获得注解信息
返回值是String[]字符串数组,那么这个返回值的类都会被导入到容器中,因此这个数组放的是类的全限定名。
public interface ImportSelector {
/**
* Select and return the names of which class(es) should be imported based on
* the {@link AnnotationMetadata} of the importing @{@link Configuration} class.
*/
String[] selectImports(AnnotationMetadata importingClassMetadata);
}
在被引包中,创建实现类
public class MyImportSelector implements ImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{"com.itheima.domain.User", "com.itheima.domain.Role"};
}
}
在启动类上使用:自然也是成功的
@Import(MyImportSelector.class)
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
User user = context.getBean(User.class);
System.out.println(user);
Role role = context.getBean(Role.class);
System.out.println(role);
}
}
注意:这类实现类的类全限定名不是写死的,我们可以写在配置文件中去。
演示四:④导入 ImportBeanDefinitionRegistrar 实现类。
先看下这个ImportBeanDefinitionRegistrar接口
其中AnnotationMetadata可以获得注解信息
BeanDefinitionRegistry可以向IOC容器注入一些内容。
public interface ImportBeanDefinitionRegistrar {
/**
* Register bean definitions as necessary based on the given annotation metadata of
* the importing {@code @Configuration} class.
* <p>Note that {@link BeanDefinitionRegistryPostProcessor} types may <em>not</em> be
* registered here, due to lifecycle constraints related to {@code @Configuration}
* class processing.
* @param importingClassMetadata annotation metadata of the importing class
* @param registry current bean definition registry
*/
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
在被引用包创建实现类,这个相当于是在ioc容器创建了一个名字为user的bean。
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(User.class).getBeanDefinition();
registry.registerBeanDefinition("user", beanDefinition);
}
}
在启动类上使用,显然role成功,user出错!
@Import({MyImportBeanDefinitionRegistrar.class})
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
User user = context.getBean(User.class);
System.out.println(user);
Role role = context.getBean(Role.class);
System.out.println(role);
}
}
引入6
千万不要忘了,我们上面学习Import的目的,是为了研究Enable*注解的
现在我们回过头来看启动类
@SpringBootApplication // 进入
public class SpringbootEnableApplication {}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration // 进入 这个就是我们要研究的,其底层是import注解
@ComponentScan(
excludeFilters = {@Filter(
type = FilterType.CUSTOM,
classes = {TypeExcludeFilter.class}
), @Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class}) // 显然就到了底层,其使用的是第三种方式,导入ImportSelector接口的实现类
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
// 这个实现类实现了很多接口,其没有直接实现ImportSelector,而是先实现DeferredImportSelector,其又实现了ImportSelector接口
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {}
public interface DeferredImportSelector extends ImportSelector {}
那么我们下面就是重点研究一下这个AutoConfigurationImportSelector实现类到底做了什么事情,就导入了什么类。
@EnableAutoConfiguration注解
其底层是Import,其用第三种方式导入了AutoConfigurationImportSelector实现类。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
我们重点关注的是这个实现类中实现的方法selectImports,其返回的字符串数组,里面是全类名,会被容器加载。
其核心代码就是getAutoConfigurationEntry方法
@Override
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return NO_IMPORTS;
}
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
.loadMetadata(this.beanClassLoader);
// 返回AutoConfigurationEntry,然后最终直接返回字符串
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// 核心代码是下面这个,得到配置的集合 ,进去看一下
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 下面是排除一些
configurations = removeDuplicates(configurations);
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = filter(configurations, autoConfigurationMetadata);
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 通过SpringFactoriesLoader加载,得到配置类的List集合
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
getBeanClassLoader());
// 这个断言是判断:如果上面List为空,则没有在META-INF/spring.factories文件中发现自动配置类,或者不正确
// 因此可见META-INF/spring.factories的重要性,我们下面就去研究下这个文件。
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;
}
还是在自动配置依赖下
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnClassCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
// 我们别的先不管,就先看这里,key是EnableAutoConfiguration,value有很多。这些都会被加载吗?
// 自然不是的,我们点进去任意一个,这里以熟悉的RedisAutoConfiguration为例
# 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,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration,\
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration,\
org.springframework.boot.autoconfigure.cloud.CloudServiceConnectorsAutoConfiguration,\
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration,\
org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration,\
org.springframework.boot.autoconfigure.dao.PersistenceExceptionTranslationAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.cassandra.CassandraRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.couchbase.CouchbaseRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jdbc.JdbcRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.ldap.LdapRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoReactiveRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.mongo.MongoRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.rest.RestClientAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration,\
org.springframework.boot.autoconfigure.http.codec.CodecsAutoConfiguration,\
org.springframework.boot.autoconfigure.influx.InfluxDbAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration,\
org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration,\
org.springframework.boot.autoconfigure.jsonb.JsonbAutoConfiguration,\
org.springframework.boot.autoconfigure.kafka.KafkaAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.embedded.EmbeddedLdapAutoConfiguration,\
org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration,\
org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderAutoConfiguration,\
org.springframework.boot.autoconfigure.mail.MailSenderValidatorAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.embedded.EmbeddedMongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoAutoConfiguration,\
org.springframework.boot.autoconfigure.mongo.MongoReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.mustache.MustacheAutoConfiguration,\
org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,\
org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration,\
org.springframework.boot.autoconfigure.reactor.core.ReactorCoreAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveSecurityAutoConfiguration,\
org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration,\
org.springframework.boot.autoconfigure.sendgrid.SendGridAutoConfiguration,\
org.springframework.boot.autoconfigure.session.SessionAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.client.reactive.ReactiveOAuth2ClientAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.security.oauth2.resource.reactive.ReactiveOAuth2ResourceServerAutoConfiguration,\
org.springframework.boot.autoconfigure.solr.SolrAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration,\
org.springframework.boot.autoconfigure.task.TaskSchedulingAutoConfiguration,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.TransactionAutoConfiguration,\
org.springframework.boot.autoconfigure.transaction.jta.JtaAutoConfiguration,\
org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,\
org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.web.embedded.EmbeddedWebServerFactoryCustomizerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.HttpHandlerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.ReactiveWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.error.ErrorWebFluxAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.ClientHttpConnectorAutoConfiguration,\
org.springframework.boot.autoconfigure.web.reactive.function.client.WebClientAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.reactive.WebSocketReactiveAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketServletAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.DataSourceBeanCreationFailureAnalyzer,\
org.springframework.boot.autoconfigure.jdbc.HikariDriverConfigurationFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.mustache.MustacheTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.thymeleaf.ThymeleafTemplateAvailabilityProvider,\
org.springframework.boot.autoconfigure.web.servlet.JspTemplateAvailabilityProvider
可以发现,这些配置类最终被加载到容器是有条件的,就是我们前面学的Conditional注解
@ConditionalOnClass(RedisOperations.class)和@ConditionalOnMissingBean(name = “redisTemplate”)等等。
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
小结
- @EnableAutoConfiguration注解内部使用@Import(AutoconfigurationImportSelector.class)来加载配置类。
- 配置文件位置:META-INF/spring.factories,该配置文件中定义了大量的配置类,当SpringBoot应用启动时,会自动加载这些配置类,初始化Bean
- 并不是所有的Bean都会被初始化,在配置类中使用Condition来加载满足条件的Bean
自定义starter
我们上面学了SpringBoot自动配置,但是很多细节还很模糊,我们这里就用案例强化一下。
这个redis的starter其实已有了,我们这里自定义一个。
我们知道SpringBoot并没有定义所有的starter,比如MyBatis,就不是自己的,因此我们可以参考MyBatis的starter。引入1
我们看下mybatis的起步依赖;然后我们看一下这个起步依赖中包含的坐标,其它都可以不看,看这个mybatis-spring-boot-autoconfigure
mybatis自动配置的坐标<!--mybatis 起步依赖-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
其实这个starter把这个自动配置包含起来了,将来我们引入这个starter,那么自动配的坐标和代码就都引入了。<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
</dependency>
</dependencies>
我们看到这个jar包什么也没有,其作用只是将那些依赖包裹依赖,当然,包括自动配置。当然这个自动配置里面内容就多了。比如这个MybatisAutoConfiguration即MyBtai的自动配置类。
这个配置类里面自然定义了很多的Bean,将来这个自动配置类要能够被Spring识别,从而加这个配置中定义的Bea的话,它的做法就是定义了META-INF文件夹里面不出意的话,有一个文件spring.factories
/**
* Copyright 2015-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mybatis.spring.boot.autoconfigure;
import java.util.List;
import javax.annotation.PostConstruct;
import javax.sql.DataSource;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.ClassPathMapperScanner;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a
* {@link SqlSessionFactory} and a {@link SqlSessionTemplate}.
*
* If {@link org.mybatis.spring.annotation.MapperScan} is used, or a
* configuration file is specified as a property, those will be considered,
* otherwise this auto-configuration will attempt to register mappers based on
* the interface definitions in or under the root auto-configuration package.
*
* @author Eddú Meléndez
* @author Josh Long
* @author Kazuki Shimizu
* @author Eduardo Macarrón
*/
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);
private final MybatisProperties properties;
private final Interceptor[] interceptors;
private final ResourceLoader resourceLoader;
private final DatabaseIdProvider databaseIdProvider;
private final List<ConfigurationCustomizer> configurationCustomizers;
public MybatisAutoConfiguration(MybatisProperties properties,
ObjectProvider<Interceptor[]> interceptorsProvider,
ResourceLoader resourceLoader,
ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
this.interceptors = interceptorsProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
@PostConstruct
public void checkConfigFileExists() {
if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
Assert.state(resource.exists(), "Cannot find config location: " + resource
+ " (please add config file or check your Mybatis configuration)");
}
}
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
Configuration configuration = this.properties.getConfiguration();
if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
configuration = new Configuration();
}
if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
customizer.customize(configuration);
}
}
factory.setConfiguration(configuration);
if (this.properties.getConfigurationProperties() != null) {
factory.setConfigurationProperties(this.properties.getConfigurationProperties());
}
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
if (this.databaseIdProvider != null) {
factory.setDatabaseIdProvider(this.databaseIdProvider);
}
if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
}
if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
}
if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
factory.setMapperLocations(this.properties.resolveMapperLocations());
}
return factory.getObject();
}
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
/**
* This will just scan the same base package as Spring Boot does. If you want
* more power, you can explicitly use
* {@link org.mybatis.spring.annotation.MapperScan} but this will get typed
* mappers working correctly, out-of-the-box, similar to using Spring Data JPA
* repositories.
*/
public static class AutoConfiguredMapperScannerRegistrar
implements BeanFactoryAware, ImportBeanDefinitionRegistrar, ResourceLoaderAware {
private BeanFactory beanFactory;
private ResourceLoader resourceLoader;
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
logger.debug("Searching for mappers annotated with @Mapper");
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
try {
if (this.resourceLoader != null) {
scanner.setResourceLoader(this.resourceLoader);
}
List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
if (logger.isDebugEnabled()) {
for (String pkg : packages) {
logger.debug("Using auto-configuration base package '{}'", pkg);
}
}
scanner.setAnnotationClass(Mapper.class);
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(packages));
} catch (IllegalStateException ex) {
logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
}
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
}
/**
* {@link org.mybatis.spring.annotation.MapperScan} ultimately ends up
* creating instances of {@link MapperFactoryBean}. If
* {@link org.mybatis.spring.annotation.MapperScan} is used then this
* auto-configuration is not needed. If it is _not_ used, however, then this
* will bring in a bean registrar and automatically register components based
* on the same component-scanning path as Spring Boot itself.
*/
@org.springframework.context.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {
@PostConstruct
public void afterPropertiesSet() {
logger.debug("No {} found.", MapperFactoryBean.class.getName());
}
}
}
这个文的内容如下:很熟悉,key就是EnableAutoConfiguration
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
因此当项启动后,就会在Import注解下加载这个META-INF文件夹下的spring.factories文件,从而得到这个MyBatis的自动配置类,从而加载该配置类中定义的bean(当然这些bean的加载也有条件的,不一定加载)。
引入2
下就是实现的步骤:
用Spring Initializr创建一个模块autoconfigure,不需要引入依赖
同样的方式,再创建一个模块starter模块
然后清理下该模块
其内部pom文件中:只保留spring-boot-starter依赖,把测试依赖,和maven插件删除掉。同时引入自定义的配置模块redis-spring-boot-autoconfigure,完整pom如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>redis-spring-boot-starter</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-spring-boot-starter</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--引入configure-->
<dependency>
<groupId>com.itheima</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
那么这个模块就做完了,就是如此地简单。
下面处理配置模块
先清理一下:
在这里面我们将来要创建jedis的bean,因此导入进来,完整pom如下
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.itheima</groupId>
<artifactId>redis-spring-boot-autoconfigure</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>redis-spring-boot-autoconfigure</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--引入jedis依赖-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
</project>
此时把启动类和测试类删掉,同样,上面starter中的启动类和测试类也删掉。
接下来就写自动配置类RedisAutoConfiguration,加Configuration注解,还jedis的bean
就是这么简单,但是呢?这个是写死的,我们只能操作本机的redis,这不我们满意的。应该从用户定义的配置文件中获取
@Configuration
public class RedisAutoConfiguration {
/**
* 提供Jedis的bean
*/
@Bean
public Jedis jedis(RedisProperties redisProperties) {
return new Jedis("localhost", 6379);
}
}
修改:
我们定一个类,和配置文件绑定,我们在SpringBoot(上)中讲到过。以后配置文件中以redis开头的内容都会和这个RedisProperties中属性对应起来。
但是呢?还一个小问题,这各属性配置类不能加载到spring容器中,我们可以定义@Component注解,但是呢?还是不行,因为还记得启动的包扫描吗?包名未必是一样的(当然一都不一样)。怎么办?
@ConfigurationProperties(prefix = "redis") // 注意加前缀
public class RedisProperties {
private String host = "localhost"; // 如果不配置,则默认为本机
private int port = 6379; // 如果不配置,则默认为6379端口号
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
}
我们需在配置上加注解@EnableConfigurationProperties(RedisProperties.class),这样这个properties就会被Spring识别。那么将来容器一启动,Improt导入,读取META-INF下的spring.factories文件,读到这个配置了,这个配置类上面有这个注解,从而将属性配置类加载到容器中。
那我们在创建jedis的bean时,可以用参数的方式注入这个RedisProperties的bean。此时就可以动态指定了。
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
/**
* 提供Jedis的bean
*/
@Bean
// 通过方法参数,获得该bean
public Jedis jedis(RedisProperties redisProperties) {
return new Jedis(redisProperties.getHost(), redisProperties.getPort());
}
}
下面我们需要创建文件META-INF/spring.factories:创建这个文件的名字,要么我们参考其它jar包,要么看源码,我们一般还是看源码,这个一要会,上面讲过,非常简单。
创建文件:千万不要写错了。
下面就是写这个文件,键不会写,没关系,就是EnableAutoConfiguration,看启动类就知道,当然要全名称,值就是配置类的全名称。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.itheima.redis.config.RedisAutoConfiguration
我们看到它们的文件中有\
这个是什么??如果你觉得上面的长,就换行,不过要加\
代表换行。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.itheima.redis.config.RedisAutoConfiguration
那么下面我们就可以演示了,在任一模块下,引入这个依赖,原有的redis的starter要注释(如果有的话)
测试:
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
Jedis jedis = context.getBean(Jedis.class);
System.out.println(jedis);
}
}
如果我们启动redis,可以发现正常使用,此外,我们可以使用配置文件配置主机和端口号。
上面我们就完成了,下面进行一些小小的优化:
引入3
现在当我们程序启动,就会加载jedis这个bean,但是呢?我们可以加一些条件,比如Jedis.class在的时候,我们再加载这个bean。
加这个注解@ConditionalOnClass(Jedis.class)
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
@ConditionalOnClass(Jedis.class)
public class RedisAutoConfiguration {
/**
* 提供Jedis的bean
*/
@Bean
public Jedis jedis(RedisProperties redisProperties) {
System.out.println("RedisAutoConfiguration....");
return new Jedis(redisProperties.getHost(), redisProperties.getPort());
}
}
那么如果用户自己定义了一个jedis的bean呢?我们不用自己配置的了。就用自己定义的,就不需要加载了。
@Configuration
@EnableConfigurationProperties(RedisProperties.class)
@ConditionalOnClass(Jedis.class)
public class RedisAutoConfiguration {
/**
* 提供Jedis的bean
*/
@Bean
@ConditionalOnMissingBean(name = "jedis")
public Jedis jedis(RedisProperties redisProperties) {
System.out.println("RedisAutoConfiguration....");
return new Jedis(redisProperties.getHost(), redisProperties.getPort());
}
}
测试:此时发现RedisAutoConfiguration....
这句话没有打印,即我们自己定义的生效了。
@SpringBootApplication
public class SpringbootEnableApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(SpringbootEnableApplication.class, args);
Jedis jedis = context.getBean(Jedis.class);
System.out.println(jedis);
jedis.set("name","itcast");
String name = jedis.get("name");
System.out.println(name);
}
@Bean
public Jedis jedis(){
return new Jedis("localhost",6379);
}
}
引入4
我们现在重新审视一下,Spring是怎么做的。
我们找到data这包,发现其和我们定的一样。
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
1.2 SpringBoot监听机制
说到这个监听机制,相信大家并不陌生。 我们之前学习JavaScript的时候,就介绍过,我们可以定义一个按钮,按钮上绑定一个单击事件,单击事件一点击,就会触发一个函数,函数的代码逻辑是我们自己定义的。这就是JavaScript里面的事件监听机制。
一样的道理 我们在windows上面打开计算器,这上面有很多按钮,比如8,我点击一下,那么 那么一样的,这个按钮就是事件源,我们点击这个动作就是事件(单击事件),点击后这个8就输出了,那么输出之前肯定是由代码逻辑的,这个代码就是监听器。 这个按钮上绑定了监听器,用户操作就会执行监听器代码。并且一个按钮上可以绑定多个监听器。可以监听多个事件的发生。 我们这里的8按钮,最少就有两个监听器被绑定,1是移动(移动事件)到上面,颜色的变化;2是点击后(单击事件),输出8.
SpringBoot的监听机制也是类似的,当然不是按钮。
Java监听机制
SpringBoot的监听机制,其实是对Java提供的事件监听机制的封装。
Java中的事件监听机制定义了以下几个角色:
①事件:Event,继承 java.util.EventObject 类的对象
②事件源:Source ,任意对象Object(也就是说你可以监听任意一个对象的属性呀,创建呀,销毁呀,生命周期的变化情况)
③监听器:Listener,实现 java.util.EventListener 接口 的对象
我们用SpringBoot监听机制不需要这么麻烦,绑定事件等等。因为其封装好了。
SpringBoot监听机制
SpringBoot在项目启动时,会对几个监听器进行回调,我们可以实现这些监听器接口,在项目启动时完成一些操作。 下面是几个监听器的接口
- ApplicationContextInitializer、 容器初始化时调用
- SpringApplicationRunListener、
- CommandLineRunner、
- ApplicationRunner
它内部已经把注册监听的动作都给我们写好了,只把监听器的接口暴露给我们了,我们需要做的事情只是实现这些接口,并且实现其方法即可。
下面用Spring Initializr创建模块演示,不需加任何依赖。
下面就可以自己定义监听器了。
演示一
定义这四个监听器
@Component
public class MyApplicationContextInitializer implements ApplicationContextInitializer {
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
System.out.println("ApplicationContextInitializer....initialize");
}
}
package com.itheima.springbootlistener.listener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;
@Component
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
@Override
public void starting() {
System.out.println("starting...项目启动中");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("environmentPrepared...环境对象开始准备");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("contextPrepared...上下文对象开始准备");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("contextLoaded...上下文对象开始加载");
}
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("started...上下文对象加载完成");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("running...项目启动完成,开始运行");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("failed...项目启动失败");
}
}
@Component
public class MyCommandLineRunner implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
System.out.println("CommandLineRunner...run");
System.out.println(Arrays.asList(args));
}
}
/**
* 当项目启动后执行run方法。
*/
@Component
public class MyApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("ApplicationRunner...run");
System.out.println(Arrays.asList(args.getSourceArgs()));
}
}
启动项目:此时只会执行:自定义监听器的启动时机:MyApplicationRunner和MyCommandLineRunner都是当项目启动后执行,使用@Component放入容器即可使用
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=50987:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.6)
2021-10-25 19:56:54.178 INFO 9792 --- [ main] c.i.s.SpringbootListenerTestApplication : Starting SpringbootListenerTestApplication using Java 1.8.0_221 on LAPTOP-D3IH5DSG with PID 9792 (H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes started by junta in H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02)
2021-10-25 19:56:54.185 INFO 9792 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default
2021-10-25 19:56:55.930 INFO 9792 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 2.883 seconds (JVM running for 7.706)
ApplicationRunner...run
[]
CommandLineRunner...run
[]
Process finished with exit code 0
那么余下两个监听器没有执行,想要执行,我们需要在META-INF/application.properties下面配置,我们先配置ApplicationContextInitializer,此时再次启动
org.springframework.context.ApplicationContextInitializer=com.itheima.springbootlistenertest.listener.MyApplicationContextInitializer
# org.springframework.boot.SpringApplicationRunListener=com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener
结果:运行了这三个监听器
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=51807:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.6)
ApplicationContextInitializer....initialize
2021-10-25 20:05:20.507 INFO 23632 --- [ main] c.i.s.SpringbootListenerTestApplication : Starting SpringbootListenerTestApplication using Java 1.8.0_221 on LAPTOP-D3IH5DSG with PID 23632 (H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes started by junta in H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02)
2021-10-25 20:05:20.553 INFO 23632 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default
2021-10-25 20:05:22.144 INFO 23632 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 2.211 seconds (JVM running for 4.415)
ApplicationRunner...run
[]
CommandLineRunner...run
[]
Process finished with exit code 0
如果我们添加参数
再次运行:其实前两个监听器的作用是一样的,只是参数可能不同罢了!这里获得了传入的参数。
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=52817:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication name=itcast
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.6)
ApplicationContextInitializer....initialize
2021-10-25 20:11:12.791 INFO 4228 --- [ main] c.i.s.SpringbootListenerTestApplication : Starting SpringbootListenerTestApplication using Java 1.8.0_221 on LAPTOP-D3IH5DSG with PID 4228 (H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes started by junta in H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02)
2021-10-25 20:11:12.795 INFO 4228 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default
2021-10-25 20:11:13.625 INFO 4228 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 1.3 seconds (JVM running for 3.486)
ApplicationRunner...run
[name=itcast]
CommandLineRunner...run
[name=itcast]
Process finished with exit code 0
现在我们修改spring.factories文件
org.springframework.context.ApplicationContextInitializer=com.itheima.springbootlistenertest.listener.MyApplicationContextInitializer
org.springframework.boot.SpringApplicationRunListener=com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener
再次运行:发现报错,主要看Caused by,发现MySpringApplicationRunListener.
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=52936:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication name=itcast
Exception in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.boot.SpringApplicationRunListener : com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener
at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:475)
at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:457)
at org.springframework.boot.SpringApplication.getRunListeners(SpringApplication.java:445)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:328)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1332)
at com.itheima.springbootlistenertest.SpringbootListenerTestApplication.main(SpringbootListenerTestApplication.java:10)
Caused by: java.lang.NoSuchMethodException: com.itheima.springbootlistenertest.listener.MySpringApplicationRunListener.<init>(org.springframework.boot.SpringApplication, [Ljava.lang.String;)
at java.lang.Class.getConstructor0(Class.java:3082)
at java.lang.Class.getDeclaredConstructor(Class.java:2178)
at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:470)
... 6 more
Process finished with exit code 1
我们找到SpringApplicationRunListener接口,其有两个实现类,其中一个是我们自己的。
看官方的实现类,其有一个有参构造,我们也添加一下。
package com.itheima.springbootlistenertest.listener;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.stereotype.Component;
@Component
public class MySpringApplicationRunListener implements SpringApplicationRunListener {
// 这个构造必须有,否则报错
public MySpringApplicationRunListener(SpringApplication application, String[] args) {
}
@Override
public void starting() {
System.out.println("starting...项目启动中");
}
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
System.out.println("environmentPrepared...环境对象开始准备");
}
@Override
public void contextPrepared(ConfigurableApplicationContext context) {
System.out.println("contextPrepared...上下文对象开始准备");
}
@Override
public void contextLoaded(ConfigurableApplicationContext context) {
System.out.println("contextLoaded...上下文对象开始加载");
}
@Override
public void started(ConfigurableApplicationContext context) {
System.out.println("started...上下文对象加载完成");
}
@Override
public void running(ConfigurableApplicationContext context) {
System.out.println("running...项目启动完成,开始运行");
}
@Override
public void failed(ConfigurableApplicationContext context, Throwable exception) {
System.out.println("failed...项目启动失败");
}
}
发现:飘红,此时这个监听器的@Component不需要加,即可解决
再次运行:发现这个监听器的内容充斥整个项目启动的生命周期。
F:\java_developer\tools\jdk-8u221-64bit\bin\java.exe -XX:TieredStopAtLevel=1 -noverify -Dspring.output.ansi.enabled=always -javaagent:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\lib\idea_rt.jar=53488:C:\Users\junta\AppData\Local\JetBrains\Toolbox\apps\IDEA-U\ch-0\212.5080.55\bin -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true -Dfile.encoding=UTF-8 -classpath "F:\java_developer\tools\jdk-8u221-64bit\jre\lib\charsets.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\deploy.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\access-bridge-64.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\cldrdata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\dnsns.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jaccess.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\jfxrt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\localedata.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\nashorn.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunec.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunjce_provider.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunmscapi.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\sunpkcs11.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\ext\zipfs.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\javaws.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jce.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfr.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jfxswt.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\jsse.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\management-agent.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\plugin.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\resources.jar;F:\java_developer\tools\jdk-8u221-64bit\jre\lib\rt.jar;H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter\2.5.6\spring-boot-starter-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot\2.5.6\spring-boot-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\spring-context\5.3.12\spring-context-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-aop\5.3.12\spring-aop-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-beans\5.3.12\spring-beans-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-expression\5.3.12\spring-expression-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-autoconfigure\2.5.6\spring-boot-autoconfigure-2.5.6.jar;E:\Developer\tools\repmvn\org\springframework\boot\spring-boot-starter-logging\2.5.6\spring-boot-starter-logging-2.5.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-classic\1.2.6\logback-classic-1.2.6.jar;E:\Developer\tools\repmvn\ch\qos\logback\logback-core\1.2.6\logback-core-1.2.6.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\Developer\tools\repmvn\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\Developer\tools\repmvn\org\slf4j\jul-to-slf4j\1.7.32\jul-to-slf4j-1.7.32.jar;E:\Developer\tools\repmvn\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\Developer\tools\repmvn\org\springframework\spring-core\5.3.12\spring-core-5.3.12.jar;E:\Developer\tools\repmvn\org\springframework\spring-jcl\5.3.12\spring-jcl-5.3.12.jar;E:\Developer\tools\repmvn\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\Developer\tools\repmvn\org\slf4j\slf4j-api\1.7.32\slf4j-api-1.7.32.jar" com.itheima.springbootlistenertest.SpringbootListenerTestApplication name=itcast
starting...项目启动中
environmentPrepared...环境对象开始准备
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.5.6)
ApplicationContextInitializer....initialize
contextPrepared...上下文对象开始准备
2021-10-25 20:15:59.737 INFO 24596 --- [ main] c.i.s.SpringbootListenerTestApplication : Starting SpringbootListenerTestApplication using Java 1.8.0_221 on LAPTOP-D3IH5DSG with PID 24596 (H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02\springboot-listener-test\target\classes started by junta in H:\BaiduNetdiskWorkspace\Java学习资料\黑马程序员\后端\2.spring boot【海量资源尽在 】\1.spring boot\SpringBoot 第二天\代码\springboot02)
2021-10-25 20:15:59.752 INFO 24596 --- [ main] c.i.s.SpringbootListenerTestApplication : No active profile set, falling back to default profiles: default
contextLoaded...上下文对象开始加载
2021-10-25 20:16:01.640 INFO 24596 --- [ main] c.i.s.SpringbootListenerTestApplication : Started SpringbootListenerTestApplication in 3.076 seconds (JVM running for 6.492)
started...上下文对象加载完成
ApplicationRunner...run
[name=itcast]
CommandLineRunner...run
[name=itcast]
running...项目启动完成,开始运行
Process finished with exit code 0
事件
1.3 SpringBoot启动流程分析
下面是SpringBoot的启动流程图,现在这个图还看不懂!我们先放一下,后面看!
我们用debug方式一步一步的查看
@SpringBootApplication
public class SpringbootListenerTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootListenerTestApplication.class, args);
}
}
public class SpringApplication {
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
// SpringApplication是事件源对象,将来所有的事件对象都是在这上面产生的
// SpringApplication我们前面见过,在MySpringApplicationRunListener实现类的构造方法中传入
// 这里分两步:一步就是构造过程;另外一步是执行其run方法过程
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
return (new SpringApplication(primarySources)).run(args);
}
}
// 下面我们先说第一件事,构造SpringApplication(事件源对象)
public class SpringApplication {
public SpringApplication(Class<?>... primarySources) {
this((ResourceLoader)null, primarySources);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader; // resourceLoader就不说了
// 断言,primarySources其实就是启动类,启动类可以传多个,我们平时就传一个,不说了
Assert.notNull(primarySources, "PrimarySources must not be null");
// 定义一个空的LinkedHashSet集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 这里判断是不是外部条件。我们进去看一下,显然我们不是web环境。因此webApplicationType = "NONE"
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
// 判断是不是外部环境,看有没有SERVLET等class
static WebApplicationType deduceFromClasspath() {
if (ClassUtils.isPresent(WEBFLUX_INDICATOR_CLASS, null) && !ClassUtils.isPresent(WEBMVC_INDICATOR_CLASS, null)
&& !ClassUtils.isPresent(JERSEY_INDICATOR_CLASS, null)) {
return WebApplicationType.REACTIVE;
}
for (String className : SERVLET_INDICATOR_CLASSES) {
if (!ClassUtils.isPresent(className, null)) {
return WebApplicationType.NONE;
}
}
return WebApplicationType.SERVLET;
}
}
下面我们看接下来两步,设置监听器,都是来自SpringFactories。
// 下面我们先说第一件事,构造SpringApplication(事件源对象)
public class SpringApplication {
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader; // resourceLoader就不说了
// 断言,primarySources其实就是启动类,启动类可以传多个,我们平时就传一个,不说了
Assert.notNull(primarySources, "PrimarySources must not be null");
// 定义一个空的LinkedHashSet集合
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 这里判断是不是外部条件。我们进去看一下,显然我们不是web环境。因此webApplicationType = "NONE"
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 先忽略,老师版本不是这个,没有这行
this.bootstrapRegistryInitializers = getBootstrapRegistryInitializersFromSpringFactories();
// 设置初始化...,就是我们前面定义的MyApplicationContextInitializer,其和监听器差不多,不完全是监听器,前面我们和监听器放在一起说了
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
// 设置监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
}
因为我们自己定义的监听器和这里的还不是非常一样,其是对ApplicationListener的封装,即SpringApplicationRunListener
后面会讲。
回顾一下:其实就是董艳艳配置是否为空;看看是不是web环境;注册两个容器,一个是初始化容器(其实也是监听器,就是initializers),另一个就是监听器。只是这里只是获得其名字,没有运行,等触发了事件,才会运行。
这里对应上图中左上角的红色区域。这就是SpringBoot初始化过程!此时SpringApplication(事件源对象)就创建好了。
该执行run方法了。
下面是run方法的执行
public ConfigurableApplicationContext run(String... args) {
// 这个没啥用,就是计时器,计算项目启动事件
// 开始计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 先不管,老师的没有
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
// 容器
ConfigurableApplicationContext context = null;
// 注册一些东西
configureHeadlessProperty();
// getRunListeners,这里就看到我们熟悉的名字了,就是我们上面自己定义的MySpringApplicationRunListener
// SpringApplicationRunListeners是一个容器,里面装了很多的RunListeners,包含我们定义的
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
// 结束计时
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
此时我们切换到控制台,发现没有任何信息打印。
下面执行listeners.starting(bootstrapContext, this.mainApplicationClass);
,就是调用监听器说有的starting方法。点进去看这个方法很简单就是遍历所有的listeners执行starting方法,包括我们自己定义的。
此时控制台就打印了
下面紧接着是将参数信息封装成对象,很简单
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
DefaultBootstrapContext bootstrapContext = createBootstrapContext();
ConfigurableApplicationContext context = null;
configureHeadlessProperty();
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
// 下面紧接着是将参数信息封装成对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 下面是准备环境,我们看下这个方法
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, null);
throw new IllegalStateException(ex);
}
return context;
}
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
// 执行这个方法
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,
deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}
// 执行监听器的environmentPrepared方法。
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
doWithListeners("spring.boot.application.environment-prepared",
(listener) -> listener.environmentPrepared(bootstrapContext, environment));
}
控制台有了新的输出。
此时环境里面有没有信息呢?现在信息还比较少,等准备完后,信息是非常多的。
接下来:
其实这个Banner是可以自己定义的,我们点进去。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// 这个是打印Banner,这个提一下,其实就是看到的那个图标
Banner printedBanner = printBanner(environment);
context = createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
定义图标,找到这个方法,看不出啥!我们找到Banner接口,其有很多实现类,找到PrintedBanner,其是内部类,在SpringApplicationBannerPrinter类中。
private Banner printBanner(ConfigurableEnvironment environment) {
if (this.bannerMode == Banner.Mode.OFF) {
return null;
}
ResourceLoader resourceLoader = (this.resourceLoader != null) ? this.resourceLoader
: new DefaultResourceLoader(null);
SpringApplicationBannerPrinter bannerPrinter = new SpringApplicationBannerPrinter(resourceLoader, this.banner);
if (this.bannerMode == Mode.LOG) {
return bannerPrinter.print(environment, this.mainApplicationClass, logger);
}
return bannerPrinter.print(environment, this.mainApplicationClass, System.out);
}
class SpringApplicationBannerPrinter {
static final String BANNER_LOCATION_PROPERTY = "spring.banner.location";
static final String BANNER_IMAGE_LOCATION_PROPERTY = "spring.banner.image.location";
// 如果我们有banner.txt文件,则将替换掉这个图标。
static final String DEFAULT_BANNER_LOCATION = "banner.txt";
}
接着看,如果执行完prepareContext这个方法。会触发好几个东西。
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// 这个是打印Banner,这个提一下,其实就是看到的那个图标
Banner printedBanner = printBanner(environment);
// 创建容器,就是创建IOC容器,现在只是创建。
context = createApplicationContext();
// 加载一些instance
context.setApplicationStartup(this.applicationStartup);
// 准备context,我们把listeners传进去,因为前面我们有一个prepareContext
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
其实到这里我们的IOC容器就已经创建好了。只是bean还没有真正的加载进来,我们看下这个容器。 beanDefinitionMap算是真正的容器。
只有执行了refreshContext(context);
方法后,才真正的找需要创建哪些Bean,这个过程比较慢。这个不是web工程,如果是web工程,则需要创建约100多个Bean。
接下来:这里代码没什么了,计时结束
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, bootstrapContext, applicationArguments);
configureIgnoreBeanInfo(environment);
// 这个是打印Banner,这个提一下,其实就是看到的那个图标
Banner printedBanner = printBanner(environment);
// 创建容器,就是创建IOC容器,现在只是创建。
context = createApplicationContext();
// 加载一些instance
context.setApplicationStartup(this.applicationStartup);
// 准备context,我们把listeners传进去,因为前面我们有一个prepareContext
prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
refreshContext(context);
afterRefresh(context, applicationArguments);
stopWatch.stop();
接下来:
现在整个项目就执行完了。
接下来:就没什么了。就是执行启动完成的事件和返回容器。整个项目就启动完成了。
这里讲的就针对上面图片中的青色区域。