Spring框架默认是JCL, SpringBoot选用SLF4j + logback
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0hCXb3z3-1588083234244)(SpringBoot2.assets/image-20200426211024111.png)]
用logback还可以少一层
导入slf4j的jar和logback实现的jar
官方helloworld
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
Spring(commons-logging) + Hibernate(jboss-logging) + MyBatis(xxx)
老系统不同组件可能不同日志工具
要统一日志, 即使别的框架也要统一使用slf4j
官方提供了replace的方案, 如下图. 用jcl-over-slf4j.jar来对commons logging API偷天换日
“中间包"偷天换日的"先锋队”: jcl-over-slf4j.jar | log4j-over-slf4j.jar | jul-to-slf4j.jar
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uZhEp8wX-1588083234247)(SpringBoot2.assets/image-20200426225343662.png)]
具体如何统一到slf4j?
SpringBoot能自动适配所有日志工具 (通过各种"中间包"), 自己底层用的是slf4j + logback的日志组合. 如果引入别的组件或者开发框架, 只需要把这个框架底层依赖的日志排除掉即可!
SpringBoot日志依赖关系图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G4UG1wwv-1588083234248)(SpringBoot2.assets/image-20200426231806424.png)]
总结:
利用"中间包", “偷梁换柱”
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OVzHds3m-1588083234251)(SpringBoot2.assets/image-20200426232753893.png)]
为什么称之为中间包, 偷梁换柱包? 代码片段演示
@SuppressWarnings("rawtypes")
public abstract class LogFactory {
static String UNSUPPORTED_OPERATION_IN_JCL_OVER_SLF4J = "http://www.slf4j.org/codes.html#unsupported_operation_in_jcl_over_slf4j";
// 它们底层有这一类的操作...把明明是slf4j的偷偷交给你用
static LogFactory logFactory = new SLF4JLogFactory();
//.....
一定要把这个框架底层默认集成的日志依赖排除掉, Spring会自动也是借鉴了SpringBoot底层的操作:
...
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-loggingartifactId>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<exclusions>
<exclusion>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
exclusion>
exclusions>
dependency>
...
- 排除
注: commons logging是Spring默认的日志工具
SpringBoot默认已经配置好了日志, 并且有一个默认日志级别, 称之为 root
package org.zhangcl.springboot;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; // 注意这个包容易导错
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class SpringBoot03LoggingApplicationTests {
// 日志"记录器"
Logger logger = LoggerFactory.getLogger(this.getClass());
@Test
void contextLoads() {
/*
日志的"级别", 由低到高!
可以调整需要输出的日志级别, 可以按级别选择性输出"某个级别以及更高级别"
配置文件可以设置级别
*/
logger.trace("这是trace日志");
logger.debug("这是debug日志");
logger.info("这是info日志");
logger.warn("这是warn日志");
logger.error("这是error日志");
/**
* 但是我这边更改配置没有用...
*/
}
}
也可以手动配置, 不过我这边按照视频讲解配置没有效果
# 日志级别
logging.level.org.zhangcl=error
# 如果不指定路径, 则在当前项目根目录下
logging.file=springboot.log
# 如果指定完整路径+文件名, 则按照执行 (我的只有重新运行项目主启动类才能)
logging.file=D:/springboot.log
#在当前磁盘的根路径下创建spirng文件夹和默认spring.log文件, linux就是绝对路径, win是c盘根目录
logging.path=/spring/log
# 控制台输出的格式
logging.pattern.console=
# 指定文件中日志输出格式
logging.pattern.file=
file 和 path的配合
logging.file | logging.path | Example | description |
---|---|---|---|
n | n | 只在控制台输出 | |
指定 | n | my.log | 项目根目录输出my.log |
n | 指定 | /var/log | 指定位置: spring.log |
给类路径下放上每个日志框架各自的规定的配置文件即可
规则
Logging System | Customization |
---|---|
Logback | logback-spring.xml , logback-spring.groovy , logback.xml or logback.groovy |
Log4j2 | log4j2-spring.xml or log4j2.xml |
JDK (Java Util Logging) | logging.properties |
logback.xml 日志框架可以直接识别
logback-spring.xml 日志框架不直接识别, 而是由SpringBoot识别
<springProfile name="dev">...springProfile>
根据boot官网, 需要和默认的starter-logging
做二选一, 直接替换掉logging, 转而用starter-log4j2
之前学的项目都是打jar包, 怎样web呢?
要解决的问题: (这一块内容可以删掉)
SpringMVC的相关配置都在WebMvcAutoconfiguration类中
ResourceProperties类
可以设置与静态资源有关的属性, 比如缓存时间
@ConfigurationProperties(prefix = "spring.resources", ignoreUnknownFields = false)
public class ResourceProperties implements ResourceLoaderAware { // 这个类可以设置与静态资源有关的参数
// ...
}
webjars是什么
Web Libraries in Jars
是一种把主流静态资源封装成jar形式的技术, 比如jQuery.js文件一般是以静态资源导入, 但这里以依赖jar的方式封装一下给我们用,的一种技术
官网给出最流行的几个例子:
源码分析
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
return;
}
Integer cachePeriod = this.resourceProperties.getCachePeriod();
if (!registry.hasMappingForPattern("/webjars/**")) {
customizeResourceHandlerRegistration(
registry.addResourceHandler("/webjars/**")
.addResourceLocations(
"classpath:/META-INF/resources/webjars/")
.setCachePeriod(cachePeriod));
}
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
customizeResourceHandlerRegistration(
registry.addResourceHandler(staticPathPattern)
.addResourceLocations(
this.resourceProperties.getStaticLocations())
.setCachePeriod(cachePeriod));
}
}
addResourceLocations("classpath:/META-INF/resources/webjars/"
例子
我们以jQuery为例, 在webjars官网选择版本, maven方式, 通过maven引入后, 在依赖列表:
意味着: 我们的classpath:/webjars下有东西了
localhost:8080/webjars/jquery/3.3.1/jquery.js
, 可以打开.注: 正常情况下静态资源是不允许外部直接访问的!
classpath:/META-INF/resources/
classpath:/resources/
classpath:/static/
classpath:/public/
当前项目根路径(个人自己测试不行, 反正也是优先级最低, 不管了)
localhost:8080/
默认指定文件名: index.html
默认位置: (如规则2)
默认url:localhost:8080/
, 第层已经映射好了, 记住这个就行
所有的: **/favicon.ico 都在静态资源文件下找
(个人不重视 不熟练)
原则
如果自定义, 则默认的会失效
如何定义?
操作application.properties中设置:
spring.resources.static-locations=classpath:/mylocation1/,classpath:/mylocation2/deep1/
/mylocation2/deep1
中, 个人测试了mylocation2目录, 无效市面上: JSP | Velocity | Freemarker | Thymeleaf
重点讲SB推荐的Thymeleaf
如果要识别在template目录下静态资源, 除了Controller, 额外还需要模板引擎支持, 比如Thymeleaf
依赖 (引用为准)
<properties>
<thymeleaf.version>3.0.2.RELEASEthymeleaf.version>
<thymeleaf-layout-dialect.version>2.1.1thymeleaf-layout-dialect.version>
properties>
...
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
识别位置和文件类型
classpath: /templates/*.html
约束
<html lang="en" xmlns:th="http://www.thymeleaf.org">
跳转
classpath: /templates/success.html
controller中添加跳转语句
@RequestMapping("/success")
public String success(){
return "success";
}
启动, 测试: http://localhost:8080/success
成功
html文件头引入语法提示
<html lang="en" xmlns:th="http://www.thymeleaf.org">
获取后台数据, 后端model传来msg
<h1 th:text="${msg}">h1>
引入css静态资源
<link th:href="@{/css/bootstrap.min.css}" rel="stylesheet">
<link th:href="@{/css/signin.css}" rel="stylesheet">
语法规则
th:任意html标签属性
- 可以替换原生属性的值[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X1b1pdDV-1588083234253)(SpringBoot2.assets/image-20200427185758538.png)]
解释:
Simple expressions:
Variable Expressions: ${...}
Selection Variable Expressions: *{...}
Message Expressions: #{...}
Link URL Expressions: @{...}
Fragment Expressions: ~{...}
Literals
Text literals: 'one text', 'Another one!',…
Number literals: 0, 34, 3.0, 12.3,…
Boolean literals: true, false
Null literal: null
Literal tokens: one, sometext, main,…
Text operations:
String concatenation: +
Literal substitutions: |The name is ${name}|
Arithmetic operations:
Binary operators: +, -, *, /, %
Minus sign (unary operator): -
Boolean operations:
Binary operators: and, or
Boolean negation (unary operator): !, not
Comparisons and equality:
Comparators: >, <, >=, <= (gt, lt, ge, le)
Equality operators: ==, != (eq, ne)
Conditional operators:
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:
No-Operation: _
(后边的课程忽略了, 暂时不算自己的重心)
怎样获取以上某些信息?
从idea左边项目列表的External Libraries中找到starter-autoconfigure
从其中的org目录找下来, 找到thymeleaf, 其中可以发现ThymeleafAutoconfiguration
和ThymeleafProperties
, 可以查看源码:
截取properties代码:
@ConfigurationProperties(prefix = "spring.thymeleaf")
public class ThymeleafProperties {
private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8");
private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html");
public static final String DEFAULT_PREFIX = "classpath:/templates/";
public static final String DEFAULT_SUFFIX = ".html";
提取信息:
配置文件中信息结构: spring.thymeleaf.xxx
默认从classpath:/templates/
中找.html
文件
springboot该版本下默认的, 但官网文档说如果想从2版本到3可以设置
<properties>
<thymeleaf.version>3.0.2.RELEASEthymeleaf.version>
<thymeleaf-layout-dialect.version>2.1.1thymeleaf-layout-dialect.version>
properties>
...
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-thymeleafartifactId>
dependency>
为什么layout版本也要变呢? 图来自layout的github, 有佐证:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sgmxNj9v-1588083234254)(SpringBoot2.assets/image-20200427171233509.png)]
需要手动关闭, 否则页面的改动不能及时表现出来. application.properties中
# thymeleaf缓存关闭
spring.thymeleaf.cache=false
官方文档地址: https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/#boot-features-developing-web-applications
截取: (中文为跟着教程写的 不是官网内容)
27.1.1 Spring MVC auto-configuration
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
The auto-configuration adds the following features on top of Spring’s defaults:
Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.自动配置了ViewResolver 视图解析器 根据方法的返回值得到视图对象View, 决定如何渲染(转发or重定向)
ContentNegotiatingViewResolver: 组合所有的视图解析器
如何定制? : 我们可以自己给容器中添加一个视图解析器; 自动将其组合进来
Support for serving static resources, including support for WebJars (see below).静态资源文件夹路径 webjars
Static
index.html
support. 静态首页访问Custom
Favicon
support (see below).Automatic registration ofConverter
,GenericConverter
,Formatter
beans. 自动注册Converter FormatterConverter 转换器 字段 属性 对象属性 比较接近类型的转换功能使用的组件
Formatter 格式化器 不同日期格式的转换
它们都自动注册了, 注册条件是
@Bean @ConditionalOnProperty(prefix = "spring.mvc", name = "date-format") // 在文件中配置日期格式化的规则 public Formatter<Date> dateFormatter() { return new DateFormatter(this.mvcProperties.getDateFormat());// 日期格式化组件 }
- 在配置中配置了以上配置就是条件
- 自己添加的formatter和converter只需要放入容器即可
Support for
HttpMessageConverters
(see below).消息转换器: SpringMVC用来转换http请求和响应的: User–>JSON
MessageConverters 说从容器中确定的; 获取所有的HttpMessageConverter
如果自己给容器中添加, 只需要将自己的组件注册进容器即可, @Bean@Component都行
Automatic registration of
MessageCodesResolver
(see below). //定义错误代码生成规则Automatic use of a
ConfigurableWebBindingInitializer
bean (see below).
我们也可以配置一个ConfigurableWebBindingInitializer来代替默认的, (添加到容器)
初始化WebDataBinder 请求数据======JavaBean
If you want to keep Spring Boot MVC features, and you just want to add additional MVC configuration (interceptors, formatters, view controllers etc.) you can add your own
@Configuration
class of typeWebMvcConfigurerAdapter
, but without@EnableWebMvc
.If you wish to provide custom instances of
RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
orExceptionHandlerExceptionResolver
you can declare aWebMvcRegistrationsAdapter
instance providing such components.
完全接管
If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
.
如果想扩展MVC的配置, 比如增加 拦截器 格式化器 视图解析器等等, 可以这样做:
特点是既保留默认配置, 又增加一些扩展
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter{
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("testconfig").setViewName("/testconfig");
/*
测试通过
相当于@RequestMapping("/testconfig")
controller方法return "testconfig";
*/
}
}
分析WebMvcAutoConfiguration类, 它是 SpringMVC的自动配置类
其中有个静态内部类WebMvcAutoConfigurationAdapter
也是上边WebMvcConfigurerAdapter
的子类, 它头上有个注解@Import(EnableWebMvcConfiguration.class)
这个注解导入的.class是同文件下的另一个静态内部类
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration {
// ...
}
从中注意到它的父类DelegatingWebMvcConfiguration类
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false) //此方法的参数需要从容器中获取
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
//...
有值了, 再去看本类的其他方法, 很多方法都是以这个成员变量为指针的, 例如:
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
进入this.configurers.addViewControllers(registry)
@Override
public void addViewControllers(ViewControllerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addViewControllers(registry);
}
}
这个方法是把容器中所有WebMvcConfigurer拿来, 调用他们的addViewControllers()方法
效果:
测试效果
添加之前可以直接访问静态资源 (因为有自动配置的静态资源映射)
但是添加之后不能直接访问静态资源了
说明确实会导致自动配置失效
原理
查看@EnableWebMvc
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class) // 注意1
public @interface EnableWebMvc {
}
查看DelegatingWebMvcConfiguration
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { // 注意2 父类
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
再看
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
// 要求容器中没有这个Bean, 此整个类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
此类要求没有Support类, 但是步骤1中"注意1"和"注意2"偏偏导入了它
所以@EnableWebMvc存在的话就会导致自动配置失效
很妙
是一个WebMvcConfiguration接口的实现类
但是, 实现的方法全留空.
别的类来继承, 可以有选择性的实现!
模式
SpringBoot自动配置很多组件时会优先使用容器中有没有用户定制配置的(@Bean @Component)
如果有些组件是多个共存, 比如ViewResolver, 则会将用户配置的+系统默认的组合使用
在SpringBoot中有非常多的xxxConfigurer, 帮助我们完成扩展配置, 遇见需要多留意!
在SpringBoot中会很多的xxxCustomizer 帮助我们进行定制配置
除了自动配置的, 如果想自定义\定制化一些功能, 可以在自己的WebMvcConfigurer
实现类中定义具体子类\实现类, 然后通过@Configuration + @Bean交给Spring管理即可! 这里有一个例子:
自定义一个view resolver
/*例如: 自定义一个view resolver
* 写在同一个文件中只是为了方便展示*/
public class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
// ...
return null;
}
}
交给Spring管理
/*这里用来自定义组件并交给Spring*/
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
// 把我们自己定制的vs交给Spring
@Bean
public ViewResolver getMyViewResolver(){
return new MyViewResolver();
}
}
断点debug 底层: getCandidateViews(), 获取所有候选视图时, 也会存在这个, 证明有效.
View Controller
属于扩展MVC配置的一种
作用是页面跳转, 代替controller一些代码
示例:
/*这里用来自定义组件并交给Spring*/
@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {
/**
* Configure simple automated controllers pre-configured with the response
* status code and/or a view to render the response body. This is useful in
* cases where there is no need for custom controller logic -- e.g. render a
* home page, perform simple site URL redirects, return a 404 status with
* HTML content, a 204 with no content, and more.
*
* @param registry
*/
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
/*拦截"/"和"/index"的请求, 转给index*/
}
}
SpringBoot默认使用嵌入式Servlet容器: Tomcat
如何定制修改Servlet容器的相关配置?
方式1: 修改server有关配置, 例如:
servet.port=8081
server.context-path=/crud
# 通用Servlet容器设置
server.xxx
# Tomcat的设置
server.tomcat.xxx
# 例如
server.tomcat.uri-encoding=UTF-8
方式2: 在一个@Configuration类中编写EmbeddedServletContainerCustomizer, 通过它来修改配置
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer(){
/*定制嵌入式的Servlet容器的相关规则*/
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8082);
}
};
}
SB是jar包方式启动嵌入式Servlet容器来启动应用, 项目中没有WEB-INF/web.xml之类的文件, 让三大组件可以注册到其中, 那怎样才能注册?
其实最好的实例是SpringBoot自动注册DispatcherServlet
SpringBoot中有以下方式
例子 - 注册Servlet
MyServlet.java
public class Myservlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.getWriter().print("Hello, MyServlet");
}
}
注册ServletRegistrationBean
@Configuration
public class MyServerConfig {
/*Servlet三大组件: Servlet*/
@Bean
public ServletRegistrationBean myServletRegistrationBean(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(
new Myservlet(),"/myservlet"); // 这里用了这个类的有参构造
return servletRegistrationBean;
}
}
请求localhost:8082/myservlet, 注意这里不小心手动改过端口号, 差点忘了.
结果: 访问成功, 网页打印出Hello MyServlet.
例子 - Filter
MyFilter.java
public class MyFilter implements Filter { // javax.servlet
@Override
public void init(FilterConfig filterConfig) throws ServletException {}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("MyFilter() process 生效了...");
filterChain.doFilter(servletRequest, servletResponse); // 假如不写这个, 就不能继续正常访问.
}
@Override
public void destroy() {}
}
注册在配置类中
/*Servlet三大组件: Filter*/
@Bean
public FilterRegistrationBean myFilterRegistrationBean(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(new MyFilter());
filterRegistrationBean.setUrlPatterns(Arrays.asList("/filtertest","/myservlet")); // 需要传入Collection
return filterRegistrationBean;
}
例子 - Listener
package org.zhangcl.springboot.listener;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
public class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
System.out.println("Servlet容器创建, 被我监听到了");
}
@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
System.out.println("Servlet容器销毁, 被我监听到了");
}
}
/*Servlet三大组件: Listener*/
@Bean
public ServletListenerRegistrationBean myListener(){
ServletListenerRegistrationBean<MyListener> myListenerServletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
// myListenerServletListenerRegistrationBean
return myListenerServletListenerRegistrationBean;
}
DispatcherServlet在SpringBoot中注册的过程是一个非常好的学习样本
源码
在DispatcherServletAutoConfiguration类中
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
// 拦截: "/" 包括静态资源, 但是不拦截.jsp请求 (/*会拦截.jsp)
// 可以通过server.contextPath配置DS默认的请求路径
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}
SpringBoot默认支持什么?
怎么知道的?
查看ConfigurableEmbeddeServletContainer的实现类 (Ctrl + T)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7pfFSJuO-1588083234255)(SpringBoot2.assets/image-20200428151149472.png)]
怎样切换?
在POM依赖图中,exclude排除掉 starter-tomcat的依赖, pom中效果如下
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcatartifactId>
<groupId>org.springframework.bootgroupId>
exclusion>
exclusions>
dependency>
POM中引入跟Tomcat依赖命名规律相同
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jettyartifactId>
dependency>
切换之后之前设置的端口号仍然有效. 启动
[main] .s.b.c.e.j.JettyEmbeddedServletContainer : Jetty started on port(s) 8082 (http/1.1)
查看EmbeddedServletContainerAutoConfiguration类
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)
// BeanPostProcessorsRegistrar: registrar用来给容器中导入一些组件
// 导入了EmbeddedServletContainerCustomizerBeanPostProcessor 后置处理器
// 后置处理器: bean初始化前后, 创建完了对象, 但还没有赋值, 执行一些初始化工作!
public class EmbeddedServletContainerAutoConfiguration {
/**
* Nested configuration if Tomcat is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })// 判断是否已经引入Tomcat依赖
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
// 条件生效: 没有用户自己定义的嵌入式s容器工厂 (生产这种容器的)
public static class EmbeddedTomcat {
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Jetty is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
/**
* Nested configuration if Undertow is being used.
*/
@Configuration
@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class, search = SearchStrategy.CURRENT)
public static class EmbeddedUndertow {
@Bean
public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
return new UndertowEmbeddedServletContainerFactory();
}
}
}
EmbeddedServletContainerFactory (嵌入式S容器工厂)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tKT6uTJV-1588083234256)(SpringBoot2.assets/image-20200428154903562.png)]
public interface EmbeddedServletContainerFactory {
/**
* Gets a new fully configured but paused {@link EmbeddedServletContainer} instance.
* Clients should not be able to connect to the returned server until
* {@link EmbeddedServletContainer#start()} is called (which happens when the
* {@link ApplicationContext} has been fully refreshed).
* @param initializers {@link ServletContextInitializer}s that should be applied as
* the container starts
* @return a fully configured and started {@link EmbeddedServletContainer}
* @see EmbeddedServletContainer#stop()
*/
EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers);
}
EmbeddedServletContainer (…容器)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KmapyrMl-1588083234256)(SpringBoot2.assets/image-20200428155011414.png)]
以Tomcat工厂为例
类
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
// new 一个实例
Tomcat tomcat = new Tomcat();
// 配置tomcat基本配置
File baseDir = (this.baseDirectory != null ? this.baseDirectory
: createTempDir("tomcat"));
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol); // 连接器
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
// 配置好的实例返回
return getTomcatEmbeddedServletContainer(tomcat);
}
getTomcatEmbeddedServletContainer(tomcat);
/**
* Factory method called to create the {@link TomcatEmbeddedServletContainer}.
* Subclasses can override this method to return a different
* {@link TomcatEmbeddedServletContainer} or apply additional processing to the Tomcat
* server.
* @param tomcat the Tomcat server.
* @return a new {@link TomcatEmbeddedServletContainer} instance
*/
protected TomcatEmbeddedServletContainer getTomcatEmbeddedServletContainer(
Tomcat tomcat) {
return new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0); // 端口号若大于0则生效
}
配置是怎样生效的?
嵌入式Servlet容器定制器 工作原理
这个定制器有一个定制器对应的BeanPostProcessor(后置处理器)
后置处理器在AutoConfiguration类被导入进来
........
@Import(BeanPostProcessorsRegistrar.class)
// BeanPostProcessorsRegistrar: registrar用来给容器中导入一些组件
// 导入了EmbeddedServletContainerCustomizerBeanPostProcessor 后置处理器
// 后置处理器: bean初始化前后, 创建完了对象, 但还没有赋值, 执行一些初始化工作!
public class EmbeddedServletContainerAutoConfiguration {
.......
postProcessBeforeinitialization
// 初始化之前
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
// 如果当前初始化的是一个ConfigurableEmbeddedServletContainer, 就调用下边的方法
if (bean instanceof ConfigurableEmbeddedServletContainer) {
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
进入postProcessBeforeInitialization
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
// 获取所有定制器, 调用每一个定制器的customize方法, 给servlet容器进行属性赋值
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
getCustomizers()
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Look up does not include the parent context
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
// 核心, 从容器中获取所有指定type的定制器
// 定制S容器, 给容器中可以添加一个嵌入式s容器定制器类型的组件
this.beanFactory
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
核心, 从容器中获取所有指定type的定制器
从配置文件的server.进入源码, 发现ServerProperties也是一个嵌入式容器定制器的实现类
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered {
步骤
SpringBoot根据导入的依赖情况, 给容器中添加相应的EmbeddedServletContainerFactory [比如TomcatEmbeddedServletContainerFactory]
容器中某个组件要创建对象, 就会惊动后置处理器, 使其工作
EmbeddedServletContainerCustomizerBeanPostProcessor
只要是嵌入式的S容器工厂, 后置处理器就会工作
后置处理器工作: 从容器中获取所有的EmServlerContainerCustomizers
然后设置tomcat的基本配置
人话:
什么时候创建嵌入式的Servlet容器工厂?
什么时候获取容器并启动Tomcat?
获取嵌入式S容器工厂 (打断点获知, 屏蔽其他细节)
SpringBoot应用启动, 运行run()
refreshContext(context), SpringBoot刷新IoC容器 (实际就是创建IoC容器, 并初始化, 创建容器中的每一个组件 bean)
refresh(context); 刷新刚才创建好的IoC容器
@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();
// 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();
}
}
}
onRefresh(); Web的IoC容器重写了onRefresh()方法
webioc容器会创建嵌入式的Servlet容器 createEmbeddedServletContainer();
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
获取嵌入式的Servlet容器工厂
从IoC容器中获取EmbeddedServletContainerFactory组件:
TomcatEmbeddedServletContainerFactory 创建对象, 后置处理器一看是这个对象, 就获取所有的customizers, 来定制Servlet容器的相关配置
使用容器工厂获取嵌入式的Servlet容器: this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer());
嵌入式的Servlet容器创建对象并启动Servlet容器
先启动嵌入式的Servlet容器, 再将IoC容器中其他未创建出的对象获取出来
IoC容器启动, 会创建嵌入式的Servlet容器
嵌入式
外置式 - 外部安装Tomcat, 应用打包方式war
创建一个war打包项目
嵌入式的Tomcat需要指定为provided: 目标环境已经提供S容器, 打包时不需要打进来了
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-tomcatartifactId>
<scope>providedscope>
dependency>
必须编写一个SpringBootServletInitializer实现类(子类)
public class ServletInitializer extends SpringBootServletInitializer{
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application){
// 传入SpringBoot应用主程序
return application.sources(SpringBootWebJspApplication.class);
}
}
即可使用启动服务器
(盲猜, 不是以后的重点, 暂时搁置)