servlet容器要遵循servlet规范。如tomcat、netty
jdbc的接口Driver
,在用MySQL的时候,要有Driver的实现类。初始化驱动的时候,class.forName()
会加载驱动,mysql的话实现类是com.mysql.Driver
。他在工厂中 把接口作为文件名,里面写上实现类,tomcat就会读这个文件,这个是servlet规范
META-INF/services
META-INF/services/javax.servlet.ServletContainerInitializer
。DispatcherServlet是servletMETA-INF/spring.factories
SPI的全名为Service Provider Interface
。大多数开发人员可能不熟悉,因为这个是针对厂商或者插件的。在java.util.ServiceLoader的文档里有比较详细的介绍。
简单的总结下java SPI机制的思想。我们系统里抽象的各个模块,往往有很多不同的实现方案。面向的对象的设计里,我们一般推荐模块之间基于接口编程,模块之间不对实现类进行硬编码。一旦代码里涉及具体的实现类,就违反了可拔插的原则,如果需要替换一种实现,就需要修改代码。为了实现在模块装配的时候能不在程序里动态指明,这就需要一种服务发现机制。
java SPI就是提供这样的一个机制:为某个接口寻找服务实现的机制。有点类似IOC的思想,就是将装配的控制权移到程序之外,在模块化设计中这个机制尤其重要。
Java SPI 规范
要使用Java SPI,需要遵循如下约定:
META-INF/services
目录下创建一个以“接口全路径名”为命名的文件,内容为实现类的全限定名;java.util.ServiceLoder
动态装载实现模块,它通过扫描META-INF/services
目录下的配置文件找到实现类的全限定名,把类加载到JVM;// 对比下java-spi的源码 // Service.load(接口.class)
public final class ServiceLoader<S> implements Iterable<S>{
private static final String PREFIX = "META-INF/services/";
在Spring中也有一种类似与Java SPI的加载机制。它在META-INF/spring.factories
文件中配置接口的实现类名称,然后在程序中读取这些配置文件并实例化。
com.xxx.interface=com.xxx.A,com.xxx.B // 多个实现类可以用,分隔
// 在Spring Boot的很多包中都能够找到spring.factories文件
这种自定义的SPI机制是Spring Boot Starter实现的基础。
但是SpringFactoriesLoader.java并不是springboot的内容,而是spring的内容
Spring Factories实现原理:
spring-core包里定义了SpringFactoriesLoader
类,这个类实现了检索META-INF/spring.factories
文件,并获取指定接口的配置的功能。在这个类中定义了两个对外的方法:
spring.factories的是通过Properties解析得到的,所以我们在写文件中的内容都是安装下面这种方式配置的:
在Spring Boot中,使用的最多的就是starter。starter可以理解为一个可拔插式的插件,例如,你想使用JDBC插件,那么可以使用spring-boot-starter-jdbc;如果想使用MongoDB,可以使用spring-boot-starter-data-mongodb。
初学的同学可能会说:如果我要使用MongoDB,我直接引入驱动jar包就行了,何必要引入starter包?starter和普通jar包的区别在于,它能够实现自动配置,和Spring Boot无缝衔接,从而节省我们大量开发时间。
@EnableAutoConfiguration自动配置:从classpath中搜索所有META-INF/spring.factories配置文件,并将其中org.springframework.boot.aotoconfigure.EnableAutoConfiguration对应的配置项通过反射实例化为对应的标注了@Configuration的配置类,然后汇总为一个并加载到IOC容器。
配置文件到底能写什么?怎么写?自动配置原理;
配置文件能配置的属性参照https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#common-application-properties
1、程序从main方法开始运行
2、使用SpringApplication.run()加载主程序类
3、主程序类需要标注@SpringBootApplication
@SpringBootApplication //标注在主程序类上,表明是一个springboot应用
public class HelloWorldMainApplication {
public static void main(String[] args) {
SpringApplication.run(HelloWorldMainApplication.class,args);
}
}
SpringApplication.run(HelloWorldMainApplication.class,args);
//重载为
SpringApplication.run(new Class<?>[] { HelloWorldMainApplication.class }, args);
//再重载为
new SpringApplication(HelloWorldMainApplication.class).run(args);
/* 构建 SpringApplication 并运行,创建并且刷新一个新的 ApplicationContext */
加载spring.factories文件形参一个map
先实例化ApplicationContextInitializer、ApplicationListener的实现类,然后赋值给 SpringApplication属性
// 构造器
public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}//重载构造器
public SpringApplication(ResourceLoader resourceLoader,
Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判断是否能够成功加载一些关键的类来确认 web 应用类型,这个类型后面会用到
this.webApplicationType = WebApplicationType.deduceFromClasspath();
// 关注下面两行
/* 获取并设置 Spring 上下文初始化器。先调用getSpringFactoriesInstances()
拿到工厂instances后赋值给SpringApplication.initializers
注意这里只是从spring.factories中拿到ApplicationContextInitializer这个接口的kv对
*/
// 设置初始化器 // set之前先get,get的时候就实例化了
setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// List> initializers;
// 设置容器的监听器
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// List> listeners;
// 追述到应用主类,也就是 main 方法所在的类
this.mainApplicationClass = deduceMainApplicationClass();
}
// 先简单看下set的部分,我们可以拿到就是拿到之后赋值给属性而已,而如何拿到的,我们要看getSpringFactoriesInstances()
public void setInitializers(Collection<? extends ApplicationContextInitializer<?>> initializers) {
this.initializers = new ArrayList<>(initializers);
}
public void setListeners(Collection<? extends ApplicationListener<?>> listeners) {
this.listeners = new ArrayList<>(listeners);
}
实例化spring.factories中的对象
我们要实例化其中的对象,首先得加载文件的内容,然后再根据文件的内容调用构造器实例化
这也是SPI思想。
要提醒一下,其实不是在这里进行自动配置的,这是我们在这里提前了解一下这个方法什么意思。
在new SpringApplication构造函数只是用到了ApplicationContextInitializer、ApplicationListener这两个kv对,而自动配置要到后面的run方法里用
// get , 从这就开始要联想java-spi的思想了 // 获取spring.factories里面的实例对象
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
}
//重载
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, //接口 ApplicationContextInitializer、ApplicationListener
Class<?>[] parameterTypes,
Object... args) {
ClassLoader classLoader = getClassLoader
// SpringFactoriesLoader用于加载spring.factories文件中的内容,返回指定接口的实现类全类名 // Set保证不重复
Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 创建上面找到的类实例。 // 方法名字为工厂实例
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
// 构造完了 根据 @Orde r和 @Priority 进行排序
AnnotationAwareOrderComparator.sort(instances);// list【instances】.sort(new AnnotationAwareOrderComparator(););
return instances;
}
加载每个jar包路径下的META-INF/spring.factories文件,把里面的内容处理成map的kv对,注意该map是多值map,一个接口可以对应多个实现类
获取传入的类所对应的值,意思是说看看properties有没有以factoryType类名为key的键值对
SpringFactoriesLoader:
// 加载spring工厂,返回要文件中要注册bean的键值对,是一次性加载全部kv对
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
/* 加载工厂配置,根据传入的 factoryClass 获取工厂名称集合 */
return loadSpringFactories(classLoader).
getOrDefault(factoryClassName, // key // //getOrDefault()获取我们自定义的值或直接调用默认值
Collections.emptyList());// 第二个参数为默认值
}
// SpringFactoriesLoader.java
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
// 尝试直接从缓存中拿 // cache的key是类加载器 // 而result是多值map,key是接口,value是List
MultiValueMap<String, String> result = cache.get(classLoader);
if (result != null) { return result; }
try {
// 加载资源, META-INF/spring.factories // //得到urls //因为每个jar包下都有类路径,我们有多个jar包,所以可能得到一个list的url
Enumeration<URL> urls = (classLoader != null ?
classLoader.getResources(FACTORIES_RESOURCE_LOCATION) : // "META-INF/spring.factories";
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
// 保存结果的map
result = new LinkedMultiValueMap<>();
//遍历url,因为有多个spring.factories文件
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
UrlResource resource = new UrlResource(url);
//把扫描到的文件的内容包装成成一个properties对象
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
//遍历properties对象里的每个键值对拿到
for (Map.Entry<?, ?> entry : properties.entrySet()) {
// value 用逗号分隔组成集合
List<String> factoryClassNames = Arrays.asList(
StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));// 切割字符串
// 添加 key 和集合的映射
result.addAll((String) entry.getKey(), factoryClassNames);
}
}
// 结果缓存
cache.put(classLoader, result);
return result;
}catch (IOException ex) { 报错从"META-INF/spring.factories"加载factories失败 }
}
注意,什么new这个里面其实并没有我们要的自动注入的内容,上面的实例化只是实例化ApplicationContextInitializer、ApplicationListener这两个接口的。我们的自动注入是在run里的
// createSpringFactoriesInstances
private <T> List<T> createSpringFactoriesInstances(Class<T> type, // 接口// 如ApplicationListener.class
Class<?>[] parameterTypes,
ClassLoader classLoader, Object[] args,
Set<String> names) {// 接口对应的实现类
List<T> instances = new ArrayList<>(names.size());
// 遍历 实例化该接口对应的实现类
for (String name : names) {
Class<?> instanceClass = ClassUtils.forName(name, classLoader);
Assert.isAssignable(type, instanceClass);
// 获取构造器
Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
// 用构造器实例化
T instance = (T) BeanUtils.instantiateClass(constructor, args);
instances.add(instance);
// ...如果有异常,报错无法实例化type + " : " + name
}
return instances;
}
我们之前new SpringApplication,里面基本什么都没有
而我们从spring.factories也只是拿出了2个接口的实现类赋值给SpringApplication对象,还有99%的接口实现类还没用
接下来我们需要关注:
- createApplicationContext();
- refreshContext(context);
// ApplicationContext.java
public ConfigurableApplicationContext run(String... args) {
// StopWatch 是一个简单的秒表,允许多个任务的计时,暴露每个命名任务的总运行时间和运行时间
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 获取 SpringApplicationRunListener 集合,同样是从上面加载的配置中获取
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
//将main函数传入的args参数构造成ApplicationArguments列表
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 配置应用环境信息
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 根据环境信息配置要忽略的bean信息 ,spring.beaninfo.ignore
configureIgnoreBeanInfo(environment);
// 打印 banner,就是我们在控制台看到的那个 Spring 的 logo
Banner printedBanner = printBanner(environment);
// 根据不同的 webApplicationType 返回不同的应用上下文实例
// DEFAULT_SERVLET_WEB_CONTEXT_CLASS BeanUtils.instantiateClass(contextClass);。
// 创建了 AnnatationConfigServletWebServerAC
context = createApplicationContext();
// 从spring.factories中获取接口的实现类,实例化、赋值属性
exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 完成整个容器的创建与启动以及bean的注入功能
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 刷新容器,完成初始化 // applicationContext.refresh(); // OnRefresh()方法是个钩子方法
// 通过ServletWebServerApplicationContext(继承AbstractApplicationContext)创建及启动web容器
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, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
这里会创建
AnnotationConfigServletWebServerApplicationContext
类。
而该类继承了ServletWebServerApplicationContext
,而这个类是最终集成了AbstractApplicationContext
。
//创建上下文
protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
switch(this.webApplicationType) {
case SERVLET:
//创建AnnotationConfigServletWebServerApplicationContext
contextClass = Class.forName("...boot.web.servlet.AnnotationConfigServletWebServerApplicationContext");
break;
case REACTIVE:
contextClass = Class.forName("...boot.web.reactive.AnnotationConfigReactiveWebServerApplicationContext");
break;
default:
contextClass = Class.forName("...annotation.AnnotationConfigApplicationContext");
}
// ...switch过程中可能抛异常"无法创建默认ApplicationContext,请指定ApplicationContextClass", var3);
}
return (ConfigurableApplicationContext)BeanUtils.instantiateClass(contextClass);
}
context = createApplicationContext();已经创建了
AnnotationConfigServletWebServerApplicationContext
然后在onRefresh里createWebServer();
//SpringApplication.java
//刷新上下文
private void refreshContext(ConfigurableApplicationContext context) {//传入new极端的
this.refresh(context);//进入
if (this.registerShutdownHook) {
context.registerShutdownHook();
}
}
//这里直接调用最终父类AbstractApplicationContext.refresh()方法
protected void refresh(ApplicationContext applicationContext) {
// 调用new 的ac.refresh();
((AbstractApplicationContext)applicationContext).refresh();
}
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//调用各个子类的onRefresh()方法,也就说这里要回到子类:ServletWebServerApplicationContext,调用该类的onRefresh()方法
// 钩子方法,正常是不做事情的
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
resetCommonCaches();
}
}
}
// 钩子方法,正常是不做事情的
public class ServletWebServerApplicationContext
extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
@Override // 在这个方法里看到了熟悉的面孔,this.createWebServer,神秘的面纱就要揭开了。
protected void onRefresh() {
super.onRefresh();
//
createWebServer();
}
@SpringBootApplication
【三】的注册时机是【二3.3.1实例化工厂后置处理器】
@SpringBootApplication: Spring Boot应用标注在某个类上说明这个类是SpringBoot的主配置类,SpringBoot就应该运行这个类的main方法来启动SpringBoot应用;
源码
@Target(ElementType.TYPE)//自定义注解的使用地方,TYPE表示是类上的
@Retention(RetentionPolicy.RUNTIME)// 自定义注解会在编码source(编译后丢弃)、class(运行时丢弃)、runtime执行阶段都有效
@Documented // 生成帮助文档,比如属性放上面就会出现注释。
@Inherited // 自定义注解可以被子类继承,该类的子类也有了自定义注解
@SpringBootConfiguration //配置类
@EnableAutoConfiguration //自动配置
@ComponentScan(excludeFilters = { //注解扫描 //排除
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
/*
public enum FilterType {
ANNOTATION,//按照注解方式
ASSIGNABLE_TYPE,//按照指定类型的方式
ASPECTJ,//使用ASPECTJ表达式的方式
REGEX,//利用正则表达式进行指定
CUSTOM//自己实现TypeFilter接口进行自定义规则
}
*/
public @interface SpringBootApplication {
//AliasFor这个注解的意思是说这个exclude属性在@SpringBootApplication注解上标注的时候就会传递给@EnableAutoConfiguration注解
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};//不想加载的东西
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
Spring Boot的配置类:和@Configuration完全一样
源码如下,@SpringBootConfiguration注解继承了@Configuration注解,此外就定义了一个属性proxyBeanMethods而已,
@AliasFor的作用代表属性proxyBeanMethods的值会同步到Configuration的属性proxyBeanMethods上
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
@AliasFor(annotation = Configuration.class)
boolean proxyBeanMethods() default true;
}
而@Configuration就是一个Component会自动注入而已(@Configuration可以保证@Bean单例)
@Component
public @interface Configuration {
@AliasFor(
annotation = Component.class
)
String value() default "";
boolean proxyBeanMethods() default true;//CGLIB
}
// 在@Configuration注解的类中有两个@Bean,返回一样的对象,那么是单例的。而@Component注释的不是单例的
@EnableXXX都是借助@Import的支持,收集和注册特定场景相关的bean定义
@EnableAutoConfiguration:开启自动配置功能;以前我们需要配置的东西,Spring Boot帮我们自动配置;
/*
@AutoConfigurationPackage原理也是利用@Import给容器中导入组件,用的是上面第三种情况
@AutoConfigurationPackage的作用是将主配置类(@SpringBootApplication标注的类)的所在包及下面所有子包里面的所有组件扫描到Spring容器;
*/
@AutoConfigurationPackage//自动配置包(扫描配置类包下的所有组件) ,注册
//这个@Import导入的类是上面第二种情况,里面的方法返回要注册的bean的数组
@Import(AutoConfigurationImportSelector.class)// 该类会注入spring.factories中的
public @interface EnableAutoConfiguration {
...
}
@Import(要导入到容器中的组件.class)
:容器中就会自动注册这个组件@Import(ImportSelector)
:会自动调用这个类里的一个方法返回将要自动注册的类数组@Import(ImportBeanDefinitionRegistrar)
:通过AnnotationMetadata手工注册bean到容器中我们先记住几个概念再开始分析,免得分析不下去
先要记住的概念:
在run方法的createApplicationContext();
创建了个AnnotationConfigServletWebServerApplicationContext对象
AnnotationConfigServletWebServerApplicationContext类有两个重要的属性:
创建AnnotationConfigServletWebServerApplicationContext对象时, 调用构造方法,会初始化该对象的上面两个属性。
public AnnotationConfigServletWebServerApplicationContext() {
// 传入this工厂 // 重点
this.reader = new AnnotatedBeanDefinitionReader(this);//里面有个AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
this.scanner = new ClassPathBeanDefinitionScanner(this);
}
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry,Environment environment) {
this.registry = registry;
this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
// 其中会对spring内置管理的几个特殊类封装成BeanDefinition,缓存到map中;
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
public static Set registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
。。。;
if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
// 注入ConfigurationClassPostProcessor类的BeanDefinition
RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
def.setSource(source);
beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}
。。。;
return beanDefs;
}
ConfigurationClassPostProcessor extends BeanDefinitionRegistryPostProcessor;
BeanDefinitionRegistryPostProcessor extends BeanFactoryPostProcessor;// 是个工厂后置处理器
重写了 postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry)方法;
重写了 postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)方法;
然后调用run方法的refreshContext(context);
时,会实例化各种bean
在实例化ConfigurationClassPostProcessor
这个bd注册器时(也是个工厂处理器),调用它的postProcessBeanDefinitionRegistry()
方法,所以需要看看这个方法。该方法又调用processConfigBeanDefinitions(registry);
然后看下面的调用栈吧
processConfigBeanDefinitions(registry);
内部会构建一个ConfigurationClassParser
对象 Parse each @Configuration class 通过parser.parse(candidates);
定位到processConfigurationClass(ConfigurationClass configClass)
;再到doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass) ;这里会处理
@PropertySource
,@ComponentScan
,@Import
,@ImportResource
,@Bean
processImports
(configClass, sourceClass, getImports(sourceClass), true); 该方法里会有对@Import
注解3种方式ImportSelector实现类和ImportBeanDefinitionRegistrar实现类以及未实现这些接口的类的处理;public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {
// 遍历上面的candidateNames,排除掉作为 处理过的configuration配置类bd。没处理的放到configCandidates中
List<BeanDefinitionHolder> configCandidates = new ArrayList<>();
// 获取已经注册的bean定义名称
String[] candidateNames = registry.getBeanDefinitionNames();
// 这里处理的是手动注入的bd,不是注解,可以跳过
for (String beanName : candidateNames) {
BeanDefinition beanDef = registry.getBeanDefinition(beanName);
if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {
// 1.1. 如果BeanDefinition 中的configurationClass 属性为full 或者lite ,则意味着已经处理过了,直接跳过
}
// 1.2. 判断对应bean是否为配置类,如果是,则加入到configCandidates
else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {
configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));
}
}
// 2. 对configCandidates 进行 排序,按照@Order 配置的值进行排序
Collections.sort(configCandidates, new Comparator<BeanDefinitionHolder>() {
@Override
public int compare(BeanDefinitionHolder bd1, BeanDefinitionHolder bd2) {
int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());
int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());
return (i1 < i2) ? -1 : (i1 > i2) ? 1 : 0;
}
});
// Detect any custom bean name generation strategy supplied through the enclosing application context
// 3. 如果BeanDefinitionRegistry 是SingletonBeanRegistry 子类的话
SingletonBeanRegistry sbr = null;
if (registry instanceof SingletonBeanRegistry) {//DefaultListableBeanFactory是SingletonBeanRegistry
sbr = (SingletonBeanRegistry) registry;// 强转
if (!this.localBeanNameGeneratorSet) {
// 如果localBeanNameGeneratorSet 等于false
// 并且SingletonBeanRegistry 中有 id 为internalConfigurationBeanNameGenerator的bean ,
// 则将componentScanBeanNameGenerator,importBeanNameGenerator 赋值为 该bean.
BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);
if (generator != null) {
this.componentScanBeanNameGenerator = generator;
this.importBeanNameGenerator = generator;
}
}
}
if (this.environment == null) {
this.environment = new StandardEnvironment();
}
// 4 构建一个ConfigurationClassParser对象,解析每个配置类@Configuration
ConfigurationClassParser parser = new ConfigurationClassParser(
this.metadataReaderFactory, this.problemReporter, this.environment,
this.resourceLoader, this.componentScanBeanNameGenerator, registry);
// 实例化2个set,candidates 用于将之前加入的configCandidates 进行去重
// alreadyParsed 用于判断是否处理过
Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);
Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());
// 5. 进行解析
do {
// ====关键步骤1
// 通过他定位到processConfigurationClass(ConfigurationClass configClass)
// 解析配置类,在此处会解析配置类上的注解(ComponentScan扫描出的类,@Import注册的类,以及@Bean方法定义的类)
// 注意:这一步只会将加了@Configuration注解以及通过@ComponentScan注解扫描的类才会加入到BeanDefinitionMap中
// 通过其他注解(例如@Import、@Bean)的方式,在parse()方法这一步并不会将其解析为BeanDefinition放入到BeanDefinitionMap中,而是先解析成ConfigurationClass类
// 真正放入到map中是在下面的this.reader.loadBeanDefinitions()方法中实现的
parser.parse(candidates);
parser.validate();
Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());
configClasses.removeAll(alreadyParsed);
// Read the model and create bean definitions based on its content
if (this.reader == null) {
this.reader = new ConfigurationClassBeanDefinitionReader(
registry, this.sourceExtractor, this.resourceLoader, this.environment,
this.importBeanNameGenerator, parser.getImportRegistry());
}
/*===关键步骤2
将上一步parser解析出的Configuration配置类加载成BeanDefinition
实际上经过上一步的parse()后,解析出来的bean已经放入到BeanDefinition中了,
但是由于这些bean可能会引入新的bean,例如实现了ImportBeanDefinitionRegistrar或者ImportSelector接口的bean,
或者bean中存在被@Bean注解的方法
因此需要执行一次loadBeanDefinition(),这样就会执行ImportBeanDefinitionRegistrar或者ImportSelector接口的方法或者@Bean注释的方法
*/
this.reader.loadBeanDefinitions(configClasses);
alreadyParsed.addAll(configClasses);
candidates.clear();
// 这里判断registry.getBeanDefinitionCount() > candidateNames.length的目的是为了知道reader.loadBeanDefinitions(configClasses)这一步有没有向BeanDefinitionMap中添加新的BeanDefinition
// 实际上就是看配置类(例如AppConfig类会向BeanDefinitionMap中添加bean)
// 如果有,registry.getBeanDefinitionCount()就会大于candidateNames.length
// 这样就需要再次遍历新加入的BeanDefinition,并判断这些bean是否已经被解析过了,如果未解析,需要重新进行解析
if (registry.getBeanDefinitionCount() > candidateNames.length) {
String[] newCandidateNames = registry.getBeanDefinitionNames();
Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));
Set<String> alreadyParsedClasses = new HashSet<>();
for (ConfigurationClass configurationClass : alreadyParsed) {
alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());
}
// 如果有未解析的类,则将其添加到candidates中,这样candidates不为空,就会进入到下一次的while的循环中
for (String candidateName : newCandidateNames) {
if (!oldCandidateNames.contains(candidateName)) {
BeanDefinition bd = registry.getBeanDefinition(candidateName);
if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&
!alreadyParsedClasses.contains(bd.getBeanClassName())) {
candidates.add(new BeanDefinitionHolder(bd, candidateName));
}
}
}
candidateNames = newCandidateNames;
}
} while (!candidates.isEmpty());
// 6 如果SingletonBeanRegistry 不包含org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry,则注册一个,bean 为 ImportRegistry. 一般都会进行注册的
// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes
if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {
sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());
}
// 7情况缓存
if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {
// Clear cache in externally provided MetadataReaderFactory; this is a no-op
// for a shared cache since it'll be cleared by the ApplicationContext.
((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();
}
}
https://blog.csdn.net/qq_26000415/article/details/78917682
public void parse(Set<BeanDefinitionHolder> configCandidates) {
this.deferredImportSelectors = new LinkedList<DeferredImportSelectorHolder>();
实例化deferredImportSelectors
遍历configCandidates ,进行处理.根据BeanDefinition 的类型 做不同的处理,一般都会调用ConfigurationClassParser#parse 进行解析
处理ImportSelect;
for (BeanDefinitionHolder holder : configCandidates) {
BeanDefinition bd = holder.getBeanDefinition();
if (bd instanceof AnnotatedBeanDefinition) {
parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());
}
else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {
parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());
}
else {
parse(bd.getBeanClassName(), holder.getBeanName());
}
}
processDeferredImportSelectors();
}
protected final void parse(AnnotationMetadata metadata, String beanName) throws IOException {
processConfigurationClass(new ConfigurationClass(metadata, beanName));
}
doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass)
这里会处理 @PropertySource, @ComponentScan,@Import,@ImportResource,@Bean 及 Process default methods on interfaces;
public void processGroupImports() {
// 遍历
for (DeferredImportSelectorGrouping grouping : this.groupings.values()) {
Predicate<String> exclusionFilter = grouping.getCandidateFilter();
//
grouping.getImports().forEach(entry -> {
ConfigurationClass configurationClass = this.configurationClasses.get(entry.getMetadata());
processImports(configurationClass, asSourceClass(configurationClass, exclusionFilter),
Collections.singleton(asSourceClass(entry.getImportClassName(), exclusionFilter)),
exclusionFilter, false);
});
}
}
在@EnableAutoConfiguration注解上有一个@AutoConfigurationPackage,把扫描的包放入bd,有get方法可以提供给spring-data-jpa用
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited // 继承
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
@AutoConfigurationPackage上有一个@Import(AutoConfigurationPackages.Registrar.class)
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry,
new PackageImports(metadata).getPackageNames().toArray(new String[0]));//拿到自动扫描的包(没指定的话默认是配置类的包)
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
只指定主类并没有什么用,我们要扫描主类下所有层次的包,而不是主扫描主类那个一层包
PackageImports(AnnotationMetadata metadata) {// 构造函数
// 获取AutoConfigurationPackage类的全路径,获取AutoConfigurationPackage注解的属性
// getAnnotationAttributes是获取注解里的value值
AnnotationAttributes attributes = AnnotationAttributes
.fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
List<String> packageNames = new ArrayList<>();
// 如果指定了basePackages
for (String basePackage : attributes.getStringArray("basePackages")) {
packageNames.add(basePackage);
}
// 如果指定了basePackageClasses
for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
packageNames.add(basePackageClass.getPackage().getName());
}
// 如果没有指定,添加配置类的包(从这里看出指定了就不扫描主类了)
if (packageNames.isEmpty()) {//metadata.getClassName()获取被注解的类
packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
}
// 封装为不可变的集合
this.packageNames = Collections.unmodifiableList(packageNames);
}
拿到了类下所有层次的包,返回了一个数组[]
// 扫描包下注解的bean,形成bd,放入ioc
public static void register(BeanDefinitionRegistry registry,
String... packageNames) {//可以是数组
// BEAN 就是 AutoConfigurationPackages,用于存储自动配置包以供稍后引用
if (registry.containsBeanDefinition(BEAN)) {
BeanDefinition beanDefinition = registry.getBeanDefinition(BEAN);
ConstructorArgumentValues constructorArguments = beanDefinition.getConstructorArgumentValues();
// 将构造函数的第一个参数设置为包名列表
constructorArguments.addIndexedArgumentValue(0,
addBasePackages(constructorArguments, packageNames));
}
else {
GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
// 将beanClass设置为BasePackages,他的类型
beanDefinition.setBeanClass(BasePackages.class);
// 将构造函数的第一个参数设置为包名列表,也就是BasePackages的构造函数
beanDefinition.getConstructorArgumentValues().addIndexedArgumentValue(0,packageNames);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 注册beanDefinition
registry.registerBeanDefinition(BEAN, beanDefinition);
}
}
上面就创建了一个BasePackages类型的bean定义
由来:
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}
怎么获得我们需要的类数组呢?是从@EnableAutoConfiguration上获取的,AnnotationMetadata这个类就是获取注解信息的类。
https://blog.csdn.net/hancoder/article/details/111413351 ConfigurationClassPostProcessor工厂后置处理器
个人认为进行完了扫描主类包后才能找到这个注解
解释:@Import(AutoConfigurationImportSelector.class)
可以帮助springboot应用将所有符合条件的@Configuration配置都加载到当前IOC容器
根据@Import的用法,我们从名字就能推断出来他实现了ImportSelector接口,这个接口的selectImports()方法会返回我们要注册的类数组。
前面自动配置包中扫描了主类所在的包所有类
但是没有扫描到spring的包,这个部分就是去找spring的包下的类
@Condition注解是在这里生效的,在filter这个函数中
public class AutoConfigurationImportSelector implements DeferredImportSelector, // ImportSelector
BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
@Override // 给容器中注册组件 //方法栈1
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!isEnabled(annotationMetadata)) {//如果没有注册元数据
return NO_IMPORTS;//返回空{}
}
// 找到要注册的组件
AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);//会返回这个//获取候选的配置
// 返回要注册的组件
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
//---------AutoConfigurationImportSelector.java---------------
// selectImports()中调用了此函数 //方法栈2
/*
根据相关的AnnotationMetadata返回【自动配置集合AutoConfigurationEntry】
参数:配置类的注解元数据annotation metadata
返回值:需要导入的自动配置auto-configurations
*/
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {//只获取AutoConfiguration的集合
if (!isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
}
// 获取@EnableAutoConfiguration的属性
AnnotationAttributes attributes = getAttributes(annotationMetadata);
// *******
//从spring.factories中获取EnableAutoConfiguration所对应的所有的值,如AopAutoConfiguration、RedisAutoConfiguration AutoConfigurationImportSelector
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
// 已经拿到了
// 去掉重复的值,如类似AopAutoConfiguration、AopAutoConfiguration
configurations = removeDuplicates(configurations);//return new ArrayList<>(new LinkedHashSet<>(list));
// 提取exclude和excludeName配置,可以用来排除我们不需要的配置
Set<String> exclusions = getExclusions(annotationMetadata, attributes);
// 检查需要移除的配置是否在候选配置列表中,如果有不存在的则抛出异常
checkExcludedClasses(configurations, exclusions);
// 从候选配置中移除所有需要排除的配置
configurations.removeAll(exclusions);
/* 过滤掉不需要的配置,这里过滤掉@Condition要求的 */
configurations = getConfigurationClassFilter().filter(configurations);
/* 将自动配置导入事件通知监听器 */
fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationEntry(configurations, exclusions);
}
//------------------------
// 方法栈3 //获取候选的自动配置,即spring.factories里对应的EnableAutoConfiguration
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
//从类路径下spring.factories文件中获取 EnableAutoConfiguration所对应的所有的values值
List<String> configurations =
SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),// 即EnableAutoConfiguration.class;
getBeanClassLoader());
Assert.notEmpty(configurations, " META-INF/spring.factories中无自动配置类. ");
return configurations;//返回所有要注册的小组件,如AopAutoConfiguration...
}
}
过滤@Condition注解不符合的候选自动配置配置
List<String> filter(List<String> configurations) {
long startTime = System.nanoTime();
String[] candidates = StringUtils.toStringArray(configurations);
boolean skipped = false;
for (AutoConfigurationImportFilter filter : this.filters) {
// 这里是拿到了所有的自动配置的filter,这个filter也是spring.factories中的
boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
for (int i = 0; i < match.length; i++) {
if (!match[i]) {// 跟过滤器没有匹配搭配,就丢掉
candidates[i] = null;
skipped = true;
}
}
}
if (!skipped) { // 发生改变了,就返回新的
return configurations;
}
// 否则返回原有的,没经过过滤的
List<String> result = new ArrayList<>(candidates.length);
for (String candidate : candidates) {
if (candidate != null) {
result.add(candidate);
}
}
return result;
}
这个过滤器在spring.factories中写过了
# 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
onfresh作为【二3.3.3】中,在容器刷新基本完成时回调
public class ServletWebServerApplicationContext
extends GenericWebApplicationContext
implements ConfigurableWebServerApplicationContext {
@Override // 在这个方法里看到了熟悉的面孔,this.createWebServer,神秘的面纱就要揭开了。
protected void onRefresh() {
super.onRefresh();
//
createWebServer();
}
createWebServer
createWebServer用于创建webServer
- 先从spring ServletWebServerApplicationContext容器中获得ServletWebServerFactory类型的bean。按理说只有一个
- getSelfInitializer()创建
// ServletWebServerApplicationContext
//这里是创建webServer
private void createWebServer() {
WebServer webServer = this.webServer;//webServer有tomcat、netty、undertow
ServletContext servletContext = getServletContext();
if (webServer == null && servletContext == null) {
// 从工厂里拿ServletWebServerFactory。在refresh方法中创建的,内嵌tomact中的@Bean
ServletWebServerFactory factory = getWebServerFactory();
// 根据工厂获取服务器,里面是new Tomcat()然后设置
// 之前在TomcatServletWebServerFactory设置了Initializer。
// 把servlet注入到了webServer中
// 这里【不会调用】dispatchServlet、字符串编码过滤器、请求过滤器 等.onStartup
this.webServer = factory.getWebServer(getSelfInitializer());
getBeanFactory().registerSingleton("webServerGracefulShutdown",
new WebServerGracefulShutdownLifecycle(this.webServer));
getBeanFactory().registerSingleton("webServerStartStop",
new WebServerStartStopLifecycle(this, this.webServer));
}
else if (servletContext != null) {
getSelfInitializer().onStartup(servletContext);
}
initPropertySources();
}
//接口
public interface ServletWebServerFactory {
WebServer getWebServer(ServletContextInitializer... initializers);
}
//实现
AbstractServletWebServerFactory
JettyServletWebServerFactory
TomcatServletWebServerFactory
UndertowServletWebServerFactory
protected ServletWebServerFactory getWebServerFactory() {
// 从ac工厂中找ServletWebServerFactory类的bean。ac工厂是我们【二】中new的
// 在工厂后置处理器的时候肯定已经创建好了
// Use bean names so that we don't consider the hierarchy
String[] beanNames = getBeanFactory().getBeanNamesForType(ServletWebServerFactory.class);
if (beanNames.length == 0) {
报错启动ServletWebServerApplicationContext时找不到ServletWebServerFactory bean
}
if (beanNames.length > 1) {
报错找到了不只一个ServletWebServerFactory beans :beanNames
}
// 返回ac容器中的ServletWebServerFactory
return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
}
getSelfInitializer()用于创建一个ServletContextInitializer对象
这里用到了lambda的知识
::
的意思函数方法的引用,而在函数式接口中,如@FunctionalInterface public interface ServletContextInitializer { /** * Configure the given {@link ServletContext} with any servlets, filters, listeners * context-params and attributes necessary for initialization. * @param servletContext the {@code ServletContext} to initialize */ void onStartup(ServletContext servletContext) throws ServletException; }
我们观察到onStartup和下面的selfInitialize方法方法签名是一样的
lambda规定,既然返回值是ServletContextInitializer接口的类型,那么我new一个实现类对应的对象即可
那么先要有实现类,实现方法在哪?不自己写了,别的方法中有现成的了,虽然方法名不同,也可以拿过来直接用
那么getSelfInitializer()的作用就是先用现有方法作为实现类的方法,然后new一个对应的实现类对象
private ServletContextInitializer getSelfInitializer() {
// 返回ServletContextInitializer接口的实现类
return this::selfInitialize;
}
// ServletWebServerApplicationContext.onStartup(ACFacade)
private void selfInitialize(ServletContext servletContext) {//传进来的是ACFacade
prepareWebApplicationContext(servletContext);
// 使用给定BeanFactory注册特定于Web的作用域(“request”,“session”,“globalSession”,“application”)
registerApplicationScope(servletContext);
// 使用给定BeanFactory注册特定于Web的环境bean(“contextParameters”,“contextAttributes”)
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// getServletContextInitializerBeans()拿到4个servlet和filter的注册bean。
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
// 把servlet注册到web容器(servletContext)中,就是把servlet注册到tomcat
// 显示执行disRegistrationBean的onStartup
beans.onStartup(servletContext);
}
}
最终的效果类似于
public class MyServletContextInitializer implements ServletContextInitializer{
@Override
private void onStartup(ServletContext servletContext) {//传进来的是ACFacade
prepareWebApplicationContext(servletContext);
// 使用给定BeanFactory注册特定于Web的作用域(“request”,“session”,“globalSession”,“application”)
registerApplicationScope(servletContext);
// 使用给定BeanFactory注册特定于Web的环境bean(“contextParameters”,“contextAttributes”)
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// getServletContextInitializerBeans()拿到4个servlet和filter的注册bean。
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
// 把servlet注册到web容器(servletContext)中,就是把servlet注册到tomcat
// 显示执行disRegistrationBean的onStartup
beans.onStartup(servletContext);
}
}
}
我们在上面
- 拿到了ServletWebServerFactory,
- 还拿到了initializer对象
factory不是我们的spring容器,而是TomcatServletWebServerFactory。
从哪里来:从spring容器中可以getBean
spring容器从何而来:ServletWebServerFactoryAutoConfiguration自动配置中,根据EmbeddedTomcat、EmbeddedJetty、EmbeddedUndertow等配置@Import注册一个。
知道工厂是什么了:那么该工厂就是
new 内嵌tomcat
前面已经通过调用onStartup方法得到了servlet,接下来把他们注册到tomcat中
public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
//TomcatServletWebServerFactory
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
// 创建tomcat对象
Tomcat tomcat = new Tomcat();// Server 1级
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// 创建Connector对象
Connector connector = new Connector(this.protocol);//connector
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);//host 3级 webapp
configureEngine(tomcat.getEngine());//engine 2级
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
// 把刚才new的ServletContextInitializer对象关联到tomcat
prepareContext(tomcat.getHost(), initializers);
// 去里面创建tomcatServer
return getTomcatWebServer(tomcat);
}
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
return new TomcatWebServer(tomcat, getPort() >= 0, getShutdown());
}
private void configureEngine(Engine engine) {
engine.setBackgroundProcessorDelay(this.backgroundProcessorDelay);
for (Valve valve : this.engineValves) {
engine.getPipeline().addValve(valve);
}
}
protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
File documentRoot = getValidDocumentRoot();
// new TomcatEmbeddedContext
TomcatEmbeddedContext context = new TomcatEmbeddedContext();
if (documentRoot != null) {
context.setResources(new LoaderHidingResourceRoot(context));
}
context.setName(getContextPath());
context.setDisplayName(getDisplayName());
context.setPath(getContextPath());
File docBase = (documentRoot != null) ? documentRoot : createTempDir("tomcat-docbase");
context.setDocBase(docBase.getAbsolutePath());
context.addLifecycleListener(new FixContextListener());
context.setParentClassLoader((this.resourceLoader != null) ? this.resourceLoader.getClassLoader()
: ClassUtils.getDefaultClassLoader());
resetDefaultLocaleMapping(context);
addLocaleMappings(context);
context.setCreateUploadTargets(true);
configureTldSkipPatterns(context);
WebappLoader loader = new WebappLoader();
loader.setLoaderClass(TomcatEmbeddedWebappClassLoader.class.getName());
loader.setDelegate(true);
context.setLoader(loader);
if (isRegisterDefaultServlet()) {
addDefaultServlet(context);
}
if (shouldRegisterJspServlet()) {
addJspServlet(context);
addJasperInitializer(context);
}
context.addLifecycleListener(new StaticResourceConfigurer(context));
ServletContextInitializer[] initializersToUse = mergeInitializers(initializers);
host.addChild(context);
configureContext(context, initializersToUse);
postProcessContext(context);
}
//Tomcat.java
//返回Engine容器,看到这里,如果熟悉tomcat源码的话,对engine不会感到陌生。
public Engine getEngine() {
Service service = getServer().findServices()[0];
if (service.getContainer() != null) {
return service.getContainer();
}
Engine engine = new StandardEngine();
engine.setName( "Tomcat" );
engine.setDefaultHost(hostname);
engine.setRealm(createDefaultRealm());
service.setContainer(engine);
return engine;
}
//Engine是最高级别容器,Host是Engine的子容器,Context是Host的子容器,Wrapper是Context的子容器
getWebServer这个方法创建了Tomcat对象,并且做了两件重要的事情:把Connector对象添加到tomcat中,configureEngine(tomcat.getEngine());
getWebServer方法返回的是TomcatWebServer。
//TomcatWebServer.java
//这里调用构造函数实例化TomcatWebServer
public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
this.tomcat = tomcat;
this.autoStart = autoStart;
initialize();
}
//TomcatWebServer.java
private void initialize() throws WebServerException {
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
Context context = findContext();
context.addLifecycleListener((event) -> {
if (context.equals(event.getSource()) && Lifecycle.START_EVENT.equals(event.getType())) {
removeServiceConnectors();
}
});
//===启动tomcat服务===
this.tomcat.start();
rethrowDeferredStartupExceptions();
ContextBindings.bindClassLoader(context, context.getNamingToken(), getClass().getClassLoader());
//开启阻塞非守护进程
startDaemonAwaitThread();
}
catch (Exception ex) {
stopSilently();
destroySilently();
throw new WebServerException("Unable to start embedded Tomcat", ex);
}
}
}
tomcat四大容器:Engine、host、context、wrapper
//Tomcat.start()就是调用
// Tomcat.java
public void start() throws LifecycleException {
getServer();//new个server。复制给属性tomcat.server,该server里有个service
server.start();
}
public Server getServer() {
// 如果已经有了,直接return现有的
if (server != null) { return server; }
// 设置环境变量
System.setProperty("catalina.useNaming", "false");
// 标准的StandardServer
server = new StandardServer();
initBaseDir();
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(new File(basedir), null));
server.setPort( -1 );
// 给server里添加一个service
Service service = new StandardService();
service.setName("Tomcat");
server.addService(service);
return server;
}
public final class StandardServer
extends LifecycleMBeanBase // 又继承了LifecycleBase,而start()方法在其中
implements Server {
@Override // LifecycleBase
public final synchronized void start() throws LifecycleException {
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
// 回调StandardServer的方法
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
// StandardServer
protected void startInternal() throws LifecycleException {
fireLifecycleEvent(CONFIGURE_START_EVENT, null);
setState(LifecycleState.STARTING);
globalNamingResources.start();
// Start our defined Services
synchronized (servicesLock) {
for (Service service : services) {
// 又去调用service.start()
service.start();
}
}
if (periodicEventDelay > 0) {
monitorFuture = getUtilityExecutor().scheduleWithFixedDelay(
new Runnable() {
@Override
public void run() {
startPeriodicLifecycleEvent();
}
}, 0, 60, TimeUnit.SECONDS);
}
}
public class StandardService
extends LifecycleMBeanBase // 又继承了LifecycleBase,而start()方法在其中
implements Service {
@Override // LifecycleBase
public final synchronized void start() throws LifecycleException {
if (state.equals(LifecycleState.NEW)) {
init();
} else if (state.equals(LifecycleState.FAILED)) {
stop();
} else if (!state.equals(LifecycleState.INITIALIZED) &&
!state.equals(LifecycleState.STOPPED)) {
invalidTransition(Lifecycle.BEFORE_START_EVENT);
}
try {
setStateInternal(LifecycleState.STARTING_PREP, null, false);
// 回调StandardService的方法
startInternal();
if (state.equals(LifecycleState.FAILED)) {
// This is a 'controlled' failure. The component put itself into the
// FAILED state so call stop() to complete the clean-up.
stop();
} else if (!state.equals(LifecycleState.STARTING)) {
// Shouldn't be necessary but acts as a check that sub-classes are
// doing what they are supposed to.
invalidTransition(Lifecycle.AFTER_START_EVENT);
} else {
setStateInternal(LifecycleState.STARTED, null, false);
}
} catch (Throwable t) {
// This is an 'uncontrolled' failure so put the component into the
// FAILED state and throw an exception.
handleSubClassException(t, "lifecycleBase.startFail", toString());
}
}
// StandardService
protected void startInternal() throws LifecycleException {
setState(LifecycleState.STARTING);
// Start our defined Container first
if (engine != null) {
synchronized (engine) {
// 又去调用
engine.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
mapperListener.start();
// Start our defined Connectors second
synchronized (connectorsLock) {
for (Connector connector: connectors) {
// If it has already failed, don't try and start it
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
}
}
public class StandardEngine
extends ContainerBase // 不一样了
implements Engine {
跟上面一样start里又会调用engine重写的startInternal
protected synchronized void startInternal() {
// Standard container startup
super.startInternal();
}
public abstract class ContainerBase extends LifecycleMBeanBase
implements Container {
@Override
protected synchronized void startInternal() throws LifecycleException {
// Start our subordinate components, if any
logger = null;
getLogger();
Cluster cluster = getClusterInternal();
if (cluster instanceof Lifecycle) {
((Lifecycle) cluster).start();
}
Realm realm = getRealmInternal();
if (realm instanceof Lifecycle) {
((Lifecycle) realm).start();
}
// Start our child containers, if any
Container children[] = findChildren();
List<Future<Void>> results = new ArrayList<>();
for (Container child : children) {
// 添加任务到线程池执行
results.add(startStopExecutor.submit(new StartChild(child)));
}
MultiThrowable multiThrowable = null;
for (Future<Void> result : results) {
try {
result.get();
} catch (Throwable e) {
log.error(sm.getString("containerBase.threadedStartFailed"), e);
if (multiThrowable == null) {
multiThrowable = new MultiThrowable();
}
multiThrowable.add(e);
}
}
if (multiThrowable != null) {
throw new LifecycleException(sm.getString("containerBase.threadedStartFailed"),
multiThrowable.getThrowable());
}
// Start the Valves in our pipeline (including the basic), if any
if (pipeline instanceof Lifecycle) {
((Lifecycle) pipeline).start();
}
setState(LifecycleState.STARTING);
// Start our thread
if (backgroundProcessorDelay > 0) {
monitorFuture = Container.getService(ContainerBase.this).getServer()
.getUtilityExecutor().scheduleWithFixedDelay(
new ContainerBackgroundProcessorMonitor(), 0, 60, TimeUnit.SECONDS);
}
}
public abstract class ContainerBase extends LifecycleMBeanBase
implements Container {
private static class StartChild
implements Callable<Void> {
private Container child;
public StartChild(Container child) {
this.child = child;
}
@Override
public Void call() throws LifecycleException {
child.start();// 又去找startInternal();
return null;
}
}
然后找到了,StandardContext
public class StandardContext extends ContainerBase
implements Context, NotificationEmitter {
StandardContext.startInternal()
// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
// 拿到TomcatStarter,去执行他的onStartup
entry.getKey()// 拿到TomcatStarter,他是个servlet容器
.onStartup(entry.getValue(),
getServletContext());// 获取到的是ACFacade
}
执行到了TomcatStarter.onStartup(
classes为null
servletContext为ACFacade
initializer3个
@Override // TomcatStarter
public void onStartup(Set<Class<?>> classes,
ServletContext servletContext){//ACFacade
// initializers是1.2中用lambda创建出来的ServletWebServerApplicationContext
for (ServletContextInitializer initializer : this.initializers) {
// 重点是这个onStartup怎么执行的,是执行的selfInitialize方法
// 其实他就是执行我们ac容器的方法
initializer.onStartup(servletContext);//传入的servletContext是ACFacade
}
}
然后去执行ServletWebServerApplicationContext的onStartup
ServletWebServerApplicationContext.onStartup
RegistrationBean是spring扫描进去的
// ServletWebServerApplicationContext.onStartup(ACFacade)
private void selfInitialize(ServletContext servletContext) {//传进来的是ACFacade
prepareWebApplicationContext(servletContext);
// 使用给定BeanFactory注册特定于Web的作用域(“request”,“session”,“globalSession”,“application”)
registerApplicationScope(servletContext);
// 使用给定BeanFactory注册特定于Web的环境bean(“contextParameters”,“contextAttributes”)
WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
// getServletContextInitializerBeans()拿到4个 servlet和filter 的RegistrationBean
for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
// 把servlet注册到web容器(servletContext)中,就是把servlet注册到tomcat
// 显示执行disRegistrationBean的onStartup,注册servlet到tomcat中
beans.onStartup(servletContext);
}
}
如下图4个注册RegistrationBean
在for之前先要执行getServletContextInitializerBeans(),然后
获得的是RegistrationBean
RegistrationBean在spring容器中,在自动配置的时候已经扫描进去了,如
dispatcherServletRegistrationBean
(我简写为disRegistrationBean)
// ServletWebServerApplicationContext // 用抽象父类接收
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
// 标准工厂里的servlet
return new ServletContextInitializerBeans(getBeanFactory());
}
//
public class ServletContextInitializerBeans
extends AbstractCollection<ServletContextInitializer> { // tomcat感兴趣的类
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
// 多值map,3个key分别为serlvet、filter、listener
this.initializers = new LinkedMultiValueMap<>();
// 要从容器中找的类是ServletContextInitializer.class,RegistrationBean实现了它
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
// dispatcherServletRegistrationBean
addServletContextInitializerBeans(beanFactory);
// 执行完上句后,我们发现initializers从null变为有个`dispatcherServletRegistrationBean` 。key为Servlet,v为dispatcherServletRegistrationBean
addAdaptableBeans(beanFactory);
// 执行完上句,initializers中又多个了个filter的kv对,k为Filter,v对应有3个FilterRegistrationBean值
List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);
}
protected void addAdaptableBeans(ListableBeanFactory beanFactory) {
MultipartConfigElement multipartConfig = getMultipartConfig(beanFactory);
// servlet
addAsRegistrationBean(beanFactory, Servlet.class, new ServletRegistrationBeanAdapter(multipartConfig));
// Filter
addAsRegistrationBean(beanFactory, Filter.class, new FilterRegistrationBeanAdapter());
for (Class<?> listenerType : ServletListenerRegistrationBean.getSupportedTypes()) {
// EventListener
addAsRegistrationBean(beanFactory, EventListener.class, (Class<EventListener>) listenerType,
new ServletListenerRegistrationBeanAdapter());
}
}
执行完后getServletContextInitializerBeans后,initializers这个map中才放入了值。下图是执行完的状态
// dispatcherServletRegistrationBean // 其实是调用父类的
// RegistrationBean.JAVA 实现了ServletContextInitializer, Ordered
public final void onStartup(ServletContext servletContext) { // 传入的是ACFacade
String description = getDescription();
// 从容器内把响应的bean获取到后,注册到tomcat容器
register(description, servletContext);
}
@Override // DynamicRegistrationBean.java
protected final void register(String description, ServletContext servletContext) {
// 添加之后返回一个D,D是Dynamic,
D registration = addRegistration(description, servletContext);
// 还需要继续配置,如果addMapping setLoadOnStartup
configure(registration);
}
@Override
protected ServletRegistration.Dynamic addRegistration(String description,
ServletContext servletContext) {// ApplicaitonContextFacade //里面有个属性是context,TomcatEmbededContext
String name = getServletName();
// 往tomcat里添加servlet
return servletContext.addServlet(name, this.servlet);//
}
public class ServletRegistrationBean<T extends Servlet>
extends DynamicRegistrationBean<ServletRegistration.Dynamic> {
@Override
protected void configure(ServletRegistration.Dynamic registration) {
super.configure(registration);
String[] urlMapping = StringUtils.toStringArray(this.urlMappings);
registration.addMapping(urlMapping);
registration.setLoadOnStartup(this.loadOnStartup);
}
public abstract class DynamicRegistrationBean<D extends Registration.Dynamic>
extends RegistrationBean {
protected final void register(String description, ServletContext servletContext) {
D registration = addRegistration(description, servletContext);
configure(registration);
}
public abstract class RegistrationBean
implements ServletContextInitializer, Ordered { // 实现了ServletContextInitializer,
public final void onStartup(ServletContext servletContext) {//注册servlet到tomcat中
String description = getDescription();
register(description, servletContext);//
}
// 简陋代码,删去次要代码
public class DispatcherServletRegistrationBean
extends ServletRegistrationBean<DispatcherServlet>
implements DispatcherServletPath {
public DispatcherServletRegistrationBean(DispatcherServlet servlet, String path) {
super(servlet);
this.path = path;
super.addUrlMappings(getServletUrlMapping());
}
我们自定义的时候就可以new ServletRegistrationBean 。因为我们要注册到tomcat中,但得先在spring配置类中放
ServletRegistrationBean继承了RegistrationBean,实现了ServletContextInitializer
DynamicRegistrationBean
RegistrationBean
ServletContextInitializer
Ordered
protected Collection<ServletContextInitializer> getServletContextInitializerBeans() {
// 构造函数创建
return new ServletContextInitializerBeans(getBeanFactory());// getBeanFactory()的结果是DefaultListableBeanFactory
}
public ServletContextInitializerBeans(ListableBeanFactory beanFactory,
Class<? extends ServletContextInitializer>... initializerTypes) {
// 刚开始只有一个Servlet集合,value为DispatcherServlet
this.initializers = new LinkedMultiValueMap<>();
this.initializerTypes = (initializerTypes.length != 0) ? Arrays.asList(initializerTypes)
: Collections.singletonList(ServletContextInitializer.class);
addServletContextInitializerBeans(beanFactory);
// 遍历容器的bean看哪些与ServletContextInitializer适配,也就是实现了的。原来只有servlet,执行完后增加了filter
addAdaptableBeans(beanFactory);
List<ServletContextInitializer> sortedInitializers = this.initializers.values().stream()
.flatMap((value) -> value.stream().sorted(AnnotationAwareOrderComparator.INSTANCE))
.collect(Collectors.toList());
this.sortedList = Collections.unmodifiableList(sortedInitializers);
logMappings(this.initializers);
}
@HandlesTypes(WebApplicationInitializer.class)//找到感兴趣的类
public class SpringServletContainerInitializer
implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses,
ServletContext servletContext) {
List<WebApplicationInitializer> initializers = new LinkedList<>();
for (Class<?> waiClass : webAppInitializerClasses) {
//如果是非抽象bean的话就加入
initializers.add( (WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);// 调用初始化器的.onStartup
}
}
上面我们知道了通过ServletRegistrationBean可以注册组件,那么我们就可以在spring容器中注入这个类型的bean,spring扫描到注册后,启动tomcat时就能发现了。
加上注解@WebServlet()
然后扫描@ServletComponentScan(basePackages=“com.aa”)。
没有经过spring的拦截器:
我们应该使用ServletRegistrationBean
@Configuration
public class BootConfig {
@Bean // 单例的,在@Configuration中不加proxyBeanMethod=false
public ServletRegistrationBean myServlet(){//第一次访问才初始化
// 会保证new MyServlet 只创建一次
ServletRegistrationBean srb = new ServletRegistrationBean(new MyServlet(), "/myServlet");
return srb;
}
@Bean
public FilterRegistrationBean myFilter(){
FilterRegistrationBean frb = new FilterRegistrationBean();
frb.setFilter(new MyFilter());
frb.setUrlPatterns(Arrays.asList("/myServlet", "/myFilter"));
return frb;
}
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean slrb = new ServletListenerRegistrationBean(new MyListener());
return slrb;
}
}
servlet
public class MyServlet extends HttpServlet {
@Override
public void init() throws ServletException {
super.init();
System.out.println("MyServlet被初始化");
}
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
System.out.println("发生请求");
}
@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse resp) throws ServletException, IOException {
}
}
过滤器
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("过滤器被初始化");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("发生过滤拦截");
chain.doFilter(request, response);
}
@Override
public void destroy() {
System.out.println("过滤器被销毁");
}
}
监听器
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("Servlet容器被初始化");
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
System.out.println("Servlet容器被销毁");
}
}
内置tomcat
spring-boot-starter-web有pom依赖
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<version>2.3.1.RELEASEversion>
<scope>compilescope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webartifactId>
<scope>compilescope>
dependency>
run----createApplicationContext()—AnnotationConfigServletWebServerApplicationContext
run----
java语言的servlet容器
要遵循servlet规范
。
自动配置里有个ServletWebServerFactoryAutoConfiguration
在工厂后置处理器阶段会把自动配置的bd注册进工厂
@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class) // CP属性
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
ServletWebServerFactoryConfiguration.EmbeddedTomcat.class, // 内嵌tomcat // TomcatServletWebServerFactory
ServletWebServerFactoryConfiguration.EmbeddedJetty.class, // 内嵌netty
ServletWebServerFactoryConfiguration.EmbeddedUndertow.class }) // 内嵌Undertow
public class ServletWebServerFactoryAutoConfiguration { // servlet web工厂
@Bean
public ServletWebServerFactoryCustomizer servletWebServerFactoryCustomizer(ServerProperties serverProperties) {
// 给serverProperties赋值。把顺序文件中的内容拿到设置到map-factory中
return new ServletWebServerFactoryCustomizer(serverProperties);
// map.from(this.serverProperties::getPort).to(factory::setPort);
}
@Bean
@ConditionalOnClass(name = "org.apache.catalina.startup.Tomcat")
public TomcatServletWebServerFactoryCustomizer tomcatServletWebServerFactoryCustomizer(
ServerProperties serverProperties) {
// 把属性里的tomcat设置拷贝到tomcat中
return new TomcatServletWebServerFactoryCustomizer(serverProperties);
}
@Bean
@ConditionalOnMissingFilterBean(ForwardedHeaderFilter.class)
@ConditionalOnProperty(value = "server.forward-headers-strategy", havingValue = "framework")
public FilterRegistrationBean<ForwardedHeaderFilter> forwardedHeaderFilter() {
ForwardedHeaderFilter filter = new ForwardedHeaderFilter();
FilterRegistrationBean<ForwardedHeaderFilter> registration = new FilterRegistrationBean<>(filter);
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC, DispatcherType.ERROR);
registration.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registration;
}
最后剩下两个WebServerFactoryCustomizer
类型的bean,任何WebServerFactoryCustomizer
类型的bean都可以收到WebServerFactory的回调,所以我们可以实现WebServerFactoryCustomizer
来定义WebServerFactory
。ServletWebServerFactoryCustomizer
的作用是将ServerProperties的属性配置设置到ConfigurableServletWebServerFactory
的对应属性上,这里主要设置的是一些通用的属性,上面注册的TomcatServletWebServerFactory
就实现了ConfigurableServletWebServerFactory
。而TomcatServletWebServerFactoryCustomizer
则是为TomcatServletWebServerFactory
设置了一些它需要的特有属性。
@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
// 内嵌的tomcat。被import
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
static class EmbeddedTomcat {
@Bean // 因为可以启动多个tomcat,所以创建个tomcat工厂
TomcatServletWebServerFactory tomcatServletWebServerFactory( // 自动传入和tomcat相关的3个Customizers
ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
ObjectProvider<TomcatContextCustomizer> contextCustomizers,
ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
// 里面有个重要的方法getWebServer
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers()
.addAll(connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers()
.addAll(contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers()
.addAll(protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
发布的时候,目前大多数的做法还是排除内置的tomcat,打瓦包(war)然后部署在生产的tomcat中
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.1.0version>
<scope>providedscope>
dependency>
更新main函数,主要是继承SpringBootServletInitializer,并重写configure()方法。
@SpringBootApplication
public class MySpringbootTomcatStarter extends SpringBootServletInitializer {
public static void main(String[] args) {
Long time=System.currentTimeMillis();
SpringApplication.run(MySpringbootTomcatStarter.class);
System.out.println("===应用启动耗时:"+(System.currentTimeMillis()-time)+"===");
}
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(this.getClass());
}
}
@Bean
@ConditionalOnBean(MultipartResolver.class) //容器中有这个类型组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) //容器中没有这个名字 multipartResolver 的组件
public MultipartResolver multipartResolver(MultipartResolver resolver) {
//给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
//SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
SpringBoot默认会在底层配好所有的组件。但是如果用户自己配置了以用户的优先
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {}
总结:
SpringBoot先加载所有的自动配置类 xxxxxAutoConfiguration
每个自动配置类按照条件进行生效,默认都会绑定配置文件指定的值。xxxxProperties里面拿。xxxProperties和配置文件进行了绑定
生效的配置类就会给容器中装配很多组件
只要容器中有这些组件,相当于这些功能就有了
定制化配置
xxxxxAutoConfiguration —> 组件 —> xxxxProperties里面拿值 ----> application.properties
引入场景依赖
查看自动配置了哪些(选做)
是否需要修改
@Conditional
派生注解(Spring注解版原生的@Conditional作用)
作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean; |
@ConditionalOnMissingBean | 容器中不存在指定Bean; |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
每一个这样的 xxxAutoConfiguration类都是容器中的一个组件,都加入到容器中;用他们来做自动配置;
4)、以**HttpEncodingAutoConfiguration(Http编码自动配置)**为例解释自动配置原理;
@Configuration(proxyBeanMethods = false) //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(ServerProperties.class)//启动指定类的ConfigurationProperties功能 并把ServerProperties加入到容器中
/*
@EnableConfigurationProperties的作用是@Import(EnableConfigurationPropertiesRegistrar.class)
而EnableConfigurationPropertiesRegistrar的父类是ImportBeanDefinitionRegistrar,
也就是说,这是@Import的第三种用法,自己重写他里面的registerBeanDefinitions()方法,自己去注册bean
//AnnotationMetadata,它可以获取当前类(被@Import标记)的注解信息,它使用标准的反射来获取制定类的内部注解信息。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerInfrastructureBeans(registry);
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
//拿到注解上的ServerProperties.class
getTypes(metadata).forEach(beanRegistrar::register);
}
而ServerProperties.class
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties { //
把配置里的server的内容赋给了ServerProperties实例的属性
从配置文件中获取指定的值和bean属性进行绑定
同时server后面能跟上面也在这个类里的属性固定着。配置文件能配置什么就可以参照对应的属性类
XXXProperties.class类都是这个作用
*/
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)//Spring底层@Conditional注解(Spring注解版),根据不同的条件,如果满足指定的条件,整个配置类里面的配置就会生效; 判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnClass(CharacterEncodingFilter.class)//判断当前项目有没有这个类CharacterEncodingFilter:SpringMVC中进行乱码解决的过滤器;
@ConditionalOnProperty(prefix = "server.servlet.encoding", value = "enabled", matchIfMissing = true)
//判断配置文件中是否存在某个配置 spring.http.encoding.enabled;
//matchIfMissing如果不存在,判断也是成立的
//即使我们配置文件中不配置pring.http.encoding.enabled=true,也是默认生效的;
public class HttpEncodingAutoConfiguration {
// 他已经和springboot的配置文件映射了
private final Encoding properties;
// 只有一个有参构造器的情况下,参数的值就会从容器中拿,怎么拿到呢?就是@EnableConfigurationProperties的作用,@EnableConfigurationProperties通过ServerProperties把配置文件绑定起来
public HttpEncodingAutoConfiguration(ServerProperties properties) {
this.properties = properties.getServlet().getEncoding();//映射配置文件中的值
}
@Bean //如果这个类生效了,给容器中添加一个组件 //这个组件中的某些值需要从properties中获取
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());//从properties中获取字符串编码
filter.setForceRequestEncoding(this.properties.shouldForce(Encoding.Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Encoding.Type.RESPONSE));
return filter;
}
}
要点:
@Configuration //表示这是一个配置类,以前编写的配置文件一样,也可以给容器中添加组件
@EnableConfigurationProperties(HttpEncodingProperties.class) //启动指定类的ConfigurationProperties功能;将配置文件中对应的值和HttpEncodingProperties绑定起来;并把HttpEncodingProperties加入到ioc容器中
@ConditionalOnWebApplication
@ConditionalOnClass(CharacterEncodingFilter.class)
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
//他已经和SpringBoot的配置文件映射了
private final HttpEncodingProperties properties;
//只有一个有参构造器的情况下,参数的值就会从容器中拿
public HttpEncodingAutoConfiguration(HttpEncodingProperties properties) {
this.properties = properties;
}
@Bean //给容器中添加一个组件,这个组件的某些值需要从properties中获取
@ConditionalOnMissingBean(CharacterEncodingFilter.class) //判断容器没有这个组件?
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
根据当前不同的条件判断,决定这个配置类是否生效?
一但这个配置类生效;这个配置类就会给容器中添加各种组件;这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
@EnableConfigurationProperties注解的作用是:使 使用 @ConfigurationProperties 注解的类生效。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EnableConfigurationPropertiesRegistrar.class)
public @interface EnableConfigurationProperties {
/*** The bean name of the configuration properties validator. */
String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
Class<?>[] value() default {};
}
/*
@EnableConfigurationProperties的作用是@Import(EnableConfigurationPropertiesRegistrar.class)
而EnableConfigurationPropertiesRegistrar的父类是ImportBeanDefinitionRegistrar,
也就是说,这是@Import的第三种用法,自己重写他里面的registerBeanDefinitions()方法,自己去注册bean
//AnnotationMetadata,它可以获取当前类(被@Import标记)的注解信息,它使用标准的反射来获取制定类的内部注解信息。
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerInfrastructureBeans(registry);
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
//拿到注解上的ServerProperties.class
getTypes(metadata).forEach(beanRegistrar::register);
}
而ServerProperties.class
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties { //
把配置里的server的内容赋给了ServerProperties实例的属性
从配置文件中获取指定的值和bean属性进行绑定
同时server后面能跟上面也在这个类里的属性固定着。配置文件能配置什么就可以参照对应的属性类
XXXProperties.class类都是这个作用
*/
class EnableConfigurationPropertiesRegistrar implements ImportBeanDefinitionRegistrar {//bean定义注册器
// 关键步骤 // 相当于手动注册bean到容器中
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
registerInfrastructureBeans(registry);
ConfigurationPropertiesBeanRegistrar beanRegistrar = new ConfigurationPropertiesBeanRegistrar(registry);
getTypes(metadata).forEach(beanRegistrar::register);
}
private Set<Class<?>> getTypes(AnnotationMetadata metadata) {
return metadata.getAnnotations().stream(EnableConfigurationProperties.class)
.flatMap((annotation) -> Arrays.stream(annotation.getClassArray(MergedAnnotation.VALUE)))
.filter((type) -> void.class != type).collect(Collectors.toSet());
}
@SuppressWarnings("deprecation")
static void registerInfrastructureBeans(BeanDefinitionRegistry registry) {
ConfigurationPropertiesBindingPostProcessor.register(registry);
BoundConfigurationProperties.register(registry);
ConfigurationPropertiesBeanDefinitionValidator.register(registry);
ConfigurationBeanFactoryMetadata.register(registry);
}
}
final class ConfigurationPropertiesBeanRegistrar {
private final BeanDefinitionRegistry registry;
private final BeanFactory beanFactory;
ConfigurationPropertiesBeanRegistrar(BeanDefinitionRegistry registry) {
this.registry = registry;
this.beanFactory = (BeanFactory) this.registry;
}
void register(Class<?> type) {
MergedAnnotation<ConfigurationProperties> annotation = MergedAnnotations
.from(type, SearchStrategy.TYPE_HIERARCHY).get(ConfigurationProperties.class);
register(type, annotation);
}
void register(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
String name = getName(type, annotation);
if (!containsBeanDefinition(name)) {
registerBeanDefinition(name, type, annotation);
}
}
private String getName(Class<?> type, MergedAnnotation<ConfigurationProperties> annotation) {
String prefix = annotation.isPresent() ? annotation.getString("prefix") : "";
return (StringUtils.hasText(prefix) ? prefix + "-" + type.getName() : type.getName());
}
private boolean containsBeanDefinition(String name) {
return containsBeanDefinition(this.beanFactory, name);
}
private boolean containsBeanDefinition(BeanFactory beanFactory, String name) {
if (beanFactory instanceof ListableBeanFactory
&& ((ListableBeanFactory) beanFactory).containsBeanDefinition(name)) {
return true;
}
if (beanFactory instanceof HierarchicalBeanFactory) {
return containsBeanDefinition(((HierarchicalBeanFactory) beanFactory).getParentBeanFactory(), name);
}
return false;
}
private void registerBeanDefinition(String beanName, Class<?> type,
MergedAnnotation<ConfigurationProperties> annotation) {
Assert.state(annotation.isPresent(), () -> "No " + ConfigurationProperties.class.getSimpleName()
+ " annotation found on '" + type.getName() + "'.");
this.registry.registerBeanDefinition(beanName, createBeanDefinition(beanName, type));
}
private BeanDefinition createBeanDefinition(String beanName, Class<?> type) {
if (BindMethod.forType(type) == BindMethod.VALUE_OBJECT) {
return new ConfigurationPropertiesValueObjectBeanDefinition(this.beanFactory, beanName, type);
}
GenericBeanDefinition definition = new GenericBeanDefinition();
definition.setBeanClass(type);
return definition;
}
}
注意:如果一个配置类只配置@ConfigurationProperties注解,而没有使用@Component,那么在IOC容器中是获取不到properties 配置文件转化的bean。说白了 @EnableConfigurationProperties 相当于把使用 @ConfigurationProperties 的类进行了一次注入。但是为什么源码里为什么无需使用@Component呢?因为在@EnableAutoConfiguarion里已经把这些类都import了
@ConfigurationProperties
与 @EnableConfigurationProperties
区别:
@EnableConfigurationProperties
注解应用到你的@Configuration
时, 任何被@ConfigurationProperties
注解的beans将自动被Environment属性配置。 这种风格的配置特别适合与SpringApplication的外部YAML配置进行配合使用。5)、所有在配置文件中能配置的属性都是在xxxxProperties类中封装者‘;配置文件能配置什么就可以参照某个功能对应的这个属性类
@ConfigurationProperties(prefix = "spring.http.encoding") //从配置文件中获取指定的值和bean的属性进行绑定
public class HttpEncodingProperties {
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
注册了2个bean
默认支持的webServer
Tomcat
, Jetty
, or Undertow
ServletWebServerApplicationContext 容器启动寻找ServletWebServerFactory 并引导创建服务器
原理
ServletWebServerApplicationContext
ServletWebServerApplicationContext
启动的时候寻找 ServletWebServerFactory
(Servlet 的web服务器工厂—> Servlet 的web服务器)TomcatServletWebServerFactory
, JettyServletWebServerFactory
, or UndertowServletWebServerFactory
ServletWebServerFactoryAutoConfiguration
ServletWebServerFactoryConfiguration
(配置类)TomcatServletWebServerFactory
Tomcat
服务器并启动;TomcatWebServer 的构造器拥有初始化方法initialize()
—>this.tomcat.start();
多个servlet都能处理到同一层路径,精确优先原则,所以有原生servlet走的是原生servlet
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
//还要加载到tomcat上下文
/** The bean name for a DispatcherServlet that will be mapped to the root URL "/" */
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
/** The bean name for a ServletRegistrationBean for the DispatcherServlet "/"*/
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {//静态类:如果你在这个类里面需要外面类的引用,就不要用static。反之就尽量用static,这样可以提高性能。
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
}
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,// 自动注入dispatcherServlet
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
// 包装dispatcherServlet为RegistrationBean
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
我们所知道static是不能用来修饰类的,但是成员内部类可以看做外部类中的一个成员,所以可以用static修饰,这种用static修饰的内部类我们称作静态内部类,也称作嵌套内部类.
非静态内部类编译后会默认的保存一个指向外部类的引用,而静态类却没有。
即使没有外部类对象,也可以创建静态内部类对象,而外部类的非static成员必须依赖于对象的调用,静态成员则可以直接使用类调用,不必依赖于外部类的对象,所以静态内部类只能访问静态的外部属性和方法。
非静态内部类有一个很大的优点:可以自由使用外部类的所有变量和方法
WebMvcConfigurer配置类其实是Spring内部的一种配置方式,采用JavaBean的形式来代替传统的xml配置文件形式进行针对框架个性化定制,可以自定义一些Handler,Interceptor,ViewResolver,MessageConverter。基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口;
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)// 没有其他的mvc配置才生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
public static final String DEFAULT_PREFIX = "";
public static final String DEFAULT_SUFFIX = "";
private static final String[] SERVLET_LOCATIONS = { "/" };
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@Bean
@ConditionalOnMissingBean(FormContentFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.formcontent.filter", name = "enabled", matchIfMissing = true)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}
static String[] getResourceLocations(String[] staticLocations) {
String[] locations = new String[staticLocations.length + SERVLET_LOCATIONS.length];
System.arraycopy(staticLocations, 0, locations, 0, staticLocations.length);
System.arraycopy(SERVLET_LOCATIONS, 0, locations, staticLocations.length, SERVLET_LOCATIONS.length);
return locations;
}
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)//注入class
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {
private final ResourceProperties resourceProperties;
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
private final ObjectProvider<HttpMessageConverters> messageConvertersProvider;
private final ObjectProvider<DispatcherServletPath> dispatcherServletPath;
final ResourceHandlerRegistrationCustomizer resourceHandlerRegistrationCustomizer;
public WebMvcAutoConfigurationAdapter(ResourceProperties resourceProperties,
WebMvcProperties mvcProperties,
ListableBeanFactory beanFactory,
ObjectProvider<HttpMessageConverters> messageConvertersProvider,
ObjectProvider<ResourceHandlerRegistrationCustomizer> resourceHandlerRegistrationCustomizerProvider,
ObjectProvider<DispatcherServletPath> dispatcherServletPath) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcProperties;
this.beanFactory = beanFactory;
this.messageConvertersProvider = messageConvertersProvider;
this.resourceHandlerRegistrationCustomizer = resourceHandlerRegistrationCustomizerProvider.getIfAvailable();
this.dispatcherServletPath = dispatcherServletPath;
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
this.messageConvertersProvider
.ifAvailable((customConverters) -> converters.addAll(customConverters.getConverters()));
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
if (this.beanFactory.containsBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME)) {
Object taskExecutor = this.beanFactory
.getBean(TaskExecutionAutoConfiguration.APPLICATION_TASK_EXECUTOR_BEAN_NAME);
if (taskExecutor instanceof AsyncTaskExecutor) {
configurer.setTaskExecutor(((AsyncTaskExecutor) taskExecutor));
}
}
Duration timeout = this.mvcProperties.getAsync().getRequestTimeout();
if (timeout != null) {
configurer.setDefaultTimeout(timeout.toMillis());
}
}
@Override
@SuppressWarnings("deprecation")
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(this.mvcProperties.getPathmatch().isUseSuffixPattern());
configurer.setUseRegisteredSuffixPatternMatch(
this.mvcProperties.getPathmatch().isUseRegisteredSuffixPattern());
this.dispatcherServletPath.ifAvailable((dispatcherPath) -> {
String servletUrlMapping = dispatcherPath.getServletUrlMapping();
if (servletUrlMapping.equals("/")) {
UrlPathHelper urlPathHelper = new UrlPathHelper();
urlPathHelper.setAlwaysUseFullPath(true);
configurer.setUrlPathHelper(urlPathHelper);
}
});
}
@Override
@SuppressWarnings("deprecation")
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
WebMvcProperties.Contentnegotiation contentnegotiation = this.mvcProperties.getContentnegotiation();
configurer.favorPathExtension(contentnegotiation.isFavorPathExtension());
configurer.favorParameter(contentnegotiation.isFavorParameter());
if (contentnegotiation.getParameterName() != null) {
configurer.parameterName(contentnegotiation.getParameterName());
}
Map<String, MediaType> mediaTypes = this.mvcProperties.getContentnegotiation().getMediaTypes();
mediaTypes.forEach(configurer::mediaType);
}
@Bean
@ConditionalOnMissingBean
public InternalResourceViewResolver defaultViewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(this.mvcProperties.getView().getPrefix());
resolver.setSuffix(this.mvcProperties.getView().getSuffix());
return resolver;
}
@Bean
@ConditionalOnBean(View.class)
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver uses all the other view resolvers to locate
// a view so it should have a high precedence
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
@Override
public MessageCodesResolver getMessageCodesResolver() {
if (this.mvcProperties.getMessageCodesResolverFormat() != null) {
DefaultMessageCodesResolver resolver = new DefaultMessageCodesResolver();
resolver.setMessageCodeFormatter(this.mvcProperties.getMessageCodesResolverFormat());
return resolver;
}
return null;
}
@Override
public void addFormatters(FormatterRegistry registry) {
ApplicationConversionService.addBeans(registry, this.beanFactory);
}
@Override// 静态资源映射
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {// resourceProperties是和yaml匹配的配置,设置为false就禁用了静态资源映射
logger.debug("Default resource handling disabled");
return;
}
// 浏览器resourceProperties态资源有效时间
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")//逻辑路径
.addResourceLocations("classpath:/META-INF/resources/webjars/")//物理路径
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));//过期时间控制。浏览器能看到
}
// 静态资源映射。默认值是/**
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))// 这个值就是那四个
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
private Integer getSeconds(Duration cachePeriod) {
return (cachePeriod != null) ? (int) cachePeriod.getSeconds() : null;
}
private void customizeResourceHandlerRegistration(ResourceHandlerRegistration registration) {
if (this.resourceHandlerRegistrationCustomizer != null) {
this.resourceHandlerRegistrationCustomizer.customize(registration);
}
}
@Bean
@ConditionalOnMissingBean({ RequestContextListener.class, RequestContextFilter.class })
@ConditionalOnMissingFilterBean(RequestContextFilter.class)
public static RequestContextFilter requestContextFilter() {
return new OrderedRequestContextFilter();
}
}
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {}
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {}
// 而WebMvcAutoConfiguration类上有WebMvcConfigurationSupport这个,有了会导致mvc自动配置里的全部失效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
public class WebMvcAutoConfiguration {}
所以我们自己写的@EnableWebMvc
是和WebMvcAutoConfiguration
相矛盾的。所以想使用mvc自动配置,就不要自己写注解@EnableWebMvc
spring容器开始启动(工厂后置处理器时),先扫描主类下的注解,已经得到了主类的@Configuration
这时去执行他里面的@Import,他发现了扫描主类包的那个组件,所以扫描了主类包
@Import也帮我们要自动注入spring的一些主键,spring.factories文件下的实现类,但是会经过过滤器过滤@Condition那些组件。所以这个阶段的时候,我们的
@EnableWebMvc
优先级高,那就会屏蔽一些自动配置类
然后我们理解一下WebMvcAutoConfiguration做了什么
我们没有自己写@EnableWebMvc
注解的时候,springboot扫描自动配置扫描到了WebMvcAutoConfiguration,判断spring中没有WebMvcConfigurationSupport类型的bean定义,所以WebMvcAutoConfiguration被正式注册到spring容器中
WebMvcAutoConfiguration是个配置类,所以还会看他里面的@Bean和@Import
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)// 只有mvc自己的配置时,生效
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
// 注入WebMvcAutoConfigurationAdapter
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)//注意,他里面实现了WebMvcConfigurationSupport,但是我们的配置类以及生效过了,所以不影响
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
@Order(0)//静态类
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer {}
我们看看WebMvcAutoConfigurationAdapter是什么,他上面有个import,这个import他会收集容器中的WebMvcConfigurer对象
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration
extends DelegatingWebMvcConfiguration// 我们看看这个类
implements ResourceLoaderAware {}
public class DelegatingWebMvcConfiguration
extends WebMvcConfigurationSupport {
// 注意属性有s,是集合
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)// 自动注入 容器中所有的webmvcConfigurer,在后面会让他们共享其中的组件
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);//设置到s那个属性中
}
}
}
好了WebMvcConfigurer收集好了,那么WebMvcConfigurer中的方法,比如要注入转换器等内容,是怎么加入的?谁执行他的方法
在实例化单实例bean时,开始实例化
requestMappingHandlerAdapter
,在createBeanInstance这步,还没有进行属性注入的时候,发现构造器参数都还没全呢,或者说需要在构造函数里依赖的对象还没实例化呢@Bean @Override public RequestMappingHandlerAdapter requestMappingHandlerAdapter( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService,//这个 @Qualifier("mvcValidator") Validator validator) { RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager, conversionService, validator); adapter.setIgnoreDefaultModelOnRedirect( this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect()); return adapter; }
比如还缺少依赖mvcConversionService,于是又处理这个组件的@Bean
@Bean @Override public FormattingConversionService mvcConversionService() { Format format = this.mvcProperties.getFormat(); WebConversionService conversionService = new WebConversionService(new DateTimeFormatters() .dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime())); addFormatters(conversionService); return conversionService;//这个name }
至于@Bean和getBean(mvcConversionService),的顺序问题,spring肯定处理好了,容器中有@Bean这个组件的信息
@Bean
@Override
public FormattingConversionService mvcConversionService() {
Format format = this.mvcProperties.getFormat();
WebConversionService conversionService = new WebConversionService(new DateTimeFormatters()
.dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime()));
// 执行方法,
addFormatters(conversionService);
return conversionService;
}
@Override // DelegatingWebMvcConfiguration类
protected void addFormatters(FormatterRegistry registry) {//每个配置中都添加
// 他会遍历添加
this.configurers.addFormatters(registry);
}
@Override// 静态资源映射
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {// resourceProperties是和yaml匹配的配置,设置为false就禁用了静态资源映射
logger.debug("Default resource handling disabled");
return;
}
// 浏览器resourceProperties态资源有效时间
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(registry.addResourceHandler("/webjars/**")//逻辑路径
.addResourceLocations("classpath:/META-INF/resources/webjars/")//物理路径
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));//过期时间控制。浏览器能看到
}
// 静态资源映射。默认值是/**
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(registry.addResourceHandler(staticPathPattern)
.addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations()))// 这个值就是那四个
.setCachePeriod(getSeconds(cachePeriod)).setCacheControl(cacheControl));
}
}
mvc的自动配置类`WebMvcAutoConfiguration`里有
- @Configuration注入`WebMvcAutoConfigurationAdapter`,
- @Import(EnableWebMvcConfiguration.class),该类继承了DelegatingWebMvcConfiguration
- DelegatingWebMvcConfiguration继承了WebMvcConfigurationSupport,维护WebMvcConfigurerComposite configurers
- mvc自动配置要求在DispatcherServlet的自动配置之后,而DispatcherServlet的自动配置要求在ServlerWebServerFactory自动配置之后。也就是说,先tomcat,再servlet,再mvc
- ```java
@AutoConfigureAfter(DispatcherServletAutoConfiguration.class)
WebMvcAutoConfiguration
@AutoConfigureAfter(ServlerWebServerFactoryAutoConfiguration.class)他里面会注册内嵌tomcat等
DispatcherServletAutoConfiguration
-
```java
@Configuration(proxyBeanMethods = false)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
private final ResourceProperties resourceProperties;
private final WebMvcProperties mvcProperties;
private final ListableBeanFactory beanFactory;
private final WebMvcRegistrations mvcRegistrations;
private ResourceLoader resourceLoader;
public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
ObjectProvider mvcPropertiesProvider,
ObjectProvider mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
this.resourceProperties = resourceProperties;
this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
this.beanFactory = beanFactory;
}
@Bean
@Override
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
conversionService, validator);
adapter.setIgnoreDefaultModelOnRedirect(
this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
return adapter;
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(HttpMessageConverter.class)
@Conditional(NotReactiveWebApplicationCondition.class)
@AutoConfigureAfter({ GsonAutoConfiguration.class, JacksonAutoConfiguration.class, JsonbAutoConfiguration.class })
@Import({ JacksonHttpMessageConvertersConfiguration.class, // Jackson消息转换器
GsonHttpMessageConvertersConfiguration.class, //Gson
JsonbHttpMessageConvertersConfiguration.class }) // Jsonb
public class HttpMessageConvertersAutoConfiguration {
@Bean //
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(
ObjectProvider<HttpMessageConverter<?>> converters) { // ObjectProvider帮我们在容器内找到HttpMessageConverter
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass(StringHttpMessageConverter.class)
protected static class StringHttpMessageConverterConfiguration {
@Bean
@ConditionalOnMissingBean // 构造注入 json的,加上默认的8个,共10个消息转换器
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
@Bean
@ConditionalOnMissingBean
public StringHttpMessageConverter stringHttpMessageConverter(Environment environment) {
Encoding encoding = Binder.get(environment).bindOrCreate("server.servlet.encoding", Encoding.class);
StringHttpMessageConverter converter = new StringHttpMessageConverter(encoding.getCharset());
converter.setWriteAcceptCharset(false);
return converter;
}
}
// 构造函数
public HttpMessageConverters(boolean addDefaultConverters,
Collection<HttpMessageConverter<?>> converters) {
// 默认转换器如ByteArray、String、Resource、ResourceRegion等8个默认的
// 再加上spring中找到的json等2个,共10个
List<HttpMessageConverter<?>> combined = getCombinedConverters(converters,
addDefaultConverters ? getDefaultConverters() : Collections.emptyList());
combined = postProcessConverters(combined);
this.converters = Collections.unmodifiableList(combined);
}
我们知道消息转换器自动注入到spring容器中了,是HttpMessageConverters
这个bean管理,那么如何交给mvc?就是这么给WebMvcAutoConfiguration里的configurationMessageConverters(converters)
是谁调用的?
父类WebMvcConfigurationSupport
里注入了一些bean,如RequestMappingHandlerAdapter
。他在注入的时候,
会new RequestMappingHandlerAdapter
然后会调用getMessageConverters()方法注入到适配器里,也就是说这时候就需要以及准备好消息转换器了。也就是说得在工厂后置处理器阶段就处理好
我们在debug过程中发现,发现先调用了适配器的内容,还没找消息转换器bean呢,在创建适配器的时候已经自己找到了10个消息转换器了,他会发现需要消息转换器的依赖,直接去@Bean执行
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<scope>runtimescope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
dependency>
默认Hikari
、可用, Springboot将使用它
点开starter-jdbc XML我们可以看到Hikari是默认的数据源
让我们使用yaml方式配置,创建application.yaml
在默认情况下, 数据库连接可以使用DataSource池进行自动配置
默认Hikari可用, Springboot将使用它。
我们可以自己指定数据源配置,通过type来选取使用哪种数据源
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/boot_demo
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# type: com.zaxxer.hikari.HikariDataSource
# type: org.apache.commons.dbcp2.BasicDataSource
配置druid数据源:
修改spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
在application.yaml中加入
spring:
datasource:
username: root
password: root
url: jdbc:mysql://localhost:3306/boot_demo
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
创建数据源注册类
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource dataSource(){
return new DruidDataSource();
}
}
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `tx_user`;
CREATE TABLE `tx_user` (
`username` varchar(10) DEFAULT NULL,
`userId` int(10) NOT NULL,
`password` varchar(10) DEFAULT NULL,
PRIMARY KEY (`userId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@Controller
public class TestController {
@Autowired
JdbcTemplate jdbcTemplate;
@ResponseBody
@RequestMapping("/query")
public List<Map<String, Object>> query(){
List<Map<String, Object>> maps = jdbcTemplate.queryForList("SELECT * FROM tx_user");
return maps;
}
}
启动springboot访问
http://localhost:8080/query
Springboot中提供了JdbcTemplateAutoConfiguration的自动配置
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,
JdbcTemplateAutoConfiguration源码:
注意:url后面加上时区
useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
引入mysql-connector-java 和 druid包
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.1
步骤:
1)、配置数据源相关属性(见上一节Druid)
2)、给数据库建表
DROP TABLE IF EXISTS `person`;
CREATE TABLE `person` (
`pid` int(20) NOT NULL AUTO_INCREMENT,
`pname` varchar(50) DEFAULT NULL,
`addr` varchar(50) DEFAULT NULL,
`gender` int(2) DEFAULT NULL,
`birth` date DEFAULT NULL,
PRIMARY KEY (`pid`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
INSERT INTO `person` VALUES ('1', '亮哥', '北京', '1', '2020-06-02');
INSERT INTO `person` VALUES ('2', 'zhangsan', 'shanghai', '1', '2022-12-11');
3)、创建JavaBean
public class TxPerson {
private int pid;
private String pname;
private String addr;
private int gender;
private Date birth;
@Mapper
public interface TxPersonMapper {
@Select("select * from tx_person")
public List<TxPerson> getPersons();
@Select("select * from tx_person t where t.pid = #{id}")
public TxPerson getPersonById(int id);
@Options(useGeneratedKeys =true, keyProperty = "pid")
@Insert("insert into tx_person(pid, pname, addr,gender, birth)" +
" values(#{pid}, #{pname}, #{addr},#{gender}, #{birth})")
public void insert(TxPerson person);
@Delete("delete from tx_person where pid = #{id}")
public void update(int id);
}
Mapper上可以不加@Mapper注解,通过@MapperScan扫描器来扫描mapper包下。不要两个注解都用,那就在容器中注册多个了。
@SpringBootApplication
@MapperScan("cn.tx.sboot.mapper")
public class FirstSpringApplication {
public static void main(String[] args) {
SpringApplication.run(FirstSpringApplication.class, args);
}
}
解决驼峰模式和数据库中下划线不能映射的问题:数据库是下划线,javaBean中是大写
@Configuration
public class MybatisConfig {
@Bean // 解决驼峰模式和数据库中下划线不能映射的问题。
public ConfigurationCustomizer getCustomizer(){
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.setMapUnderscoreToCamelCase(true);
}
};
}
}
实际上在application.yml中直接配置即可,不用单独写bean
查询俄国
TxPerson{pid=1, pname='张三', pAddr='北京', gender=1, birth=Thu Jun 14 00:00:00 CST 2018}
<mapper namespace="cn.tx.sboot.mapper.PersonMapper">
<select id="selectById" resultType="Person">
select * from person t where t.pid = #{pid}
select>
<select id="selectAll" resultType="Person">
select * from person
select>
<insert id="insert" parameterType="person">
<selectKey keyProperty="pid" resultType="int" order="BEFORE">
select last_insert_id()
selectKey>
insert into person(pid, pname, addr, gender, birth)values(#{pid},#{pname},#{addr},#{gender},#{birth} )
insert>
<delete id="delete" parameterType="int" >
delete from person where pid = #{pid}
delete>
mapper>
在application.yaml中配置mybatis的信息
spring:
datasource:
url: jdbc:mysql://localhost:3306/txjava?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
mybatis:
type-aliases-package: cn.tx.sboot.model # java类的别名
configuration:
map-underscore-to-camel-case: true
mapper-locations: classpath:mapper/*.xml
@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;
@PostConstruct // 检查xml配置文件是否存在,为了加载内容
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)");
}
}
如果没有配置mapperScan的话,会扫描主启动类所在包下所有的@Mapper
@Configuration
@MapperScan("cn.tx.springboot.mapper")
public class TxMvcConfig implements WebMvcConfigurer{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/toLogin").setViewName("login");
registry.addViewController("/header").setViewName("header");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/menu").setViewName("menu");
registry.addViewController("/add").setViewName("add");
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
List<String> excludePatterns = new ArrayList<String>();
excludePatterns.add("/css/**");
excludePatterns.add("/images/**");
excludePatterns.add("/toLogin");
excludePatterns.add("/login");
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**")
.excludePathPatterns(excludePatterns);
}
}
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource dataSource(){
return new DruidDataSource();
}
}
@Configuration
public class MybatisConfig {
@Bean
public ConfigurationCustomizer getCustomizer(){
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.setMapUnderscoreToCamelCase(true);
}
};
}
}