1.创建一个web模块
(1).创建SpringBoot应用,选中我们需要的模块;
(2).SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
(3).自己编写业务代码;
自动配置原理
@H_301_30@xxxxAutoConfiguration:帮我们给容器中自动配置组件; @H_301_30@xxxxProperties:配置类来封装配置文件的内容; |
2.SpringBoott对静态资源的映射规则
@H_301_30@@ConfigurationProperties(prefix = "spring.resources",ignoreUnknownFields = false) @H_301_30@public class ResourceProperties implements ResourceLoaderAware { @H_301_30@ //可以设置和静态资源有关的参数,缓存时间等 |
@H_301_30@WebMvcAuotConfiguration: @H_301_30@@Override @H_301_30@public void addResourceHandlers(ResourceHandlerRegistry registry) { @H_301_30@if (!this.resourceProperties.isAddMappings()) { @H_301_30@logger.debug("Default resource handling disabled"); @H_301_30@return; @H_301_30@} @H_301_30@Integer cachePeriod = this.resourceProperties.getCachePeriod(); @H_301_30@if (!registry.hasMappingForPattern("/webjars/**")) { @H_301_30@customizeResourceHandlerRegistration( @H_301_30@registry.addResourceHandler("/webjars/**") @H_301_30@.addResourceLocations( @H_301_30@"classpath:/Meta-INF/resources/webjars/") @H_301_30@.setCachePeriod(cachePeriod)); @H_301_30@} @H_301_30@String staticPathPattern = this.mvcProperties.getStaticPathPattern(); @H_301_30@ //静态资源文件夹映射 @H_301_30@if (!registry.hasMappingForPattern(staticPathPattern)) { @H_301_30@customizeResourceHandlerRegistration( @H_301_30@registry.addResourceHandler(staticPathPattern) @H_301_30@.addResourceLocations( @H_301_30@this.resourceProperties.getStaticLocations()) @H_301_30@.setCachePeriod(cachePeriod)); @H_301_30@} @H_301_30@} @H_301_30@ @H_301_30@ //配置index映射 @H_301_30@@Bean @H_301_30@public WelcomePageHandlerMapping welcomePageHandlerMapping( @H_301_30@ResourceProperties resourceProperties) { @H_301_30@return new WelcomePageHandlerMapping(resourceProperties.getWelcomePage(), @H_301_30@this.mvcProperties.getStaticPathPattern()); @H_301_30@} @H_301_30@ @H_301_30@ //配置图标 @H_301_30@@Configuration @H_301_30@@ConditionalOnProperty(value = "spring.mvc.favicon.enabled",matchIfMissing = true) @H_301_30@public static class FaviconConfiguration { @H_301_30@ @H_301_30@private final ResourceProperties resourceProperties; @H_301_30@ @H_301_30@public FaviconConfiguration(ResourceProperties resourceProperties) { @H_301_30@this.resourceProperties = resourceProperties; @H_301_30@} @H_301_30@ @H_301_30@@Bean @H_301_30@public SimpleUrlHandlerMapping faviconHandlerMapping() { @H_301_30@SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); @H_301_30@mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); @H_301_30@ //所有 **/favicon.ico @H_301_30@mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", @H_301_30@faviconRequestHandler())); @H_301_30@return mapping; @H_301_30@} @H_301_30@ @H_301_30@@Bean @H_301_30@public ResourceHttpRequestHandler faviconRequestHandler() { @H_301_30@ResourceHttpRequestHandler requestHandler = new ResourceHttpRequestHandler(); @H_301_30@requestHandler @H_301_30@.setLocations(this.resourceProperties.getFaviconLocations()); @H_301_30@return requestHandler; @H_301_30@} @H_301_30@ @H_301_30@} |
(1). 所有/webjars/**,都去 classpath:/Meta-INF/resources/webjars/ 找资源;webjars:以jar包的方式引入静态资源
@H_301_30@@H_301_30@<!--引入jquery-webjar 在访问的时候只需要写webjars下面资源的名称即可 --> @H_301_30@<dependency> @H_301_30@ <groupId>org.webjars</groupId> @H_301_30@ <artifactId>jquery</artifactId> @H_301_30@ <version>3.3.1</version> @H_301_30@</dependency> |
@H_871_403@访问:localhost:8080/webjars/jquery/3.3.1/jquery.js
(2)."/**" 访问当前项目的任何资源,到'静态资源文件夹'找映射
@H_301_30@"classpath:/Meta-INF/resources/", @H_301_30@"classpath:/resources/", @H_301_30@"classpath:/static/", @H_301_30@"classpath:/public/" @H_301_30@"/":当前项目的根路径 |
(3).index页; 静态资源文件夹下的所有index.html页面;被"/**"映射;
访问:localhost:8080/
@H_301_30@(4). 图标**/favicon.ico都在静态资源文件下找
@H_301_30@3.模板引擎
JSP、Velocity、Freemarker、Thymeleaf
@H_301_30@SpringBoot推荐使用的Thymeleaf,语法更简单,功能更强大.
(1).引入thymeleaf
@H_301_30@<dependency> @H_301_30@<groupId>org.springframework.boot</groupId> @H_301_30@<artifactId>spring-boot-starter-thymeleaf</artifactId> @H_301_30@ <!--2.1.6--> @H_301_30@</dependency> @H_301_30@切换thymeleaf版本 @H_301_30@<properties> @H_301_30@<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version> @H_301_30@<!-- 布局功能的支持程序 thymeleaf3主程序 layout2以上版本 --> @H_301_30@<!-- thymeleaf2 layout1--> @H_301_30@<thymeleaf-layout-dialect.version>2.2.2</thymeleaf-layout-dialect.version> @H_301_30@ </properties> |

(2).使用thymeleaf
@H_301_30@@ConfigurationProperties(prefix = "spring.thymeleaf") @H_301_30@public class ThymeleafProperties { @H_301_30@ @H_301_30@private static final Charset DEFAULT_ENCODING = Charset.forName("UTF-8"); @H_301_30@ @H_301_30@private static final MimeType DEFAULT_CONTENT_TYPE = MimeType.valueOf("text/html"); @H_301_30@ @H_301_30@public static final String DEFAULT_PREFIX = "classpath:/templates/"; @H_301_30@ @H_301_30@public static final String DEFAULT_SUFFIX = ".html"; @H_301_30@ // |
@H_871_403@把HTML页面放在classpath:/templates/,thymeleaf就能自动渲染;
@H_301_30@开发文档 @H_301_30@[1].添加名称空间
@H_301_30@@H_301_30@xmlns:th="http://www.thymeleaf.org" |
[2].使用thymeleaf语法
@H_301_30@<!DOCTYPE html> @H_301_30@<html lang="en" xmlns:th="http://www.thymeleaf.org"> @H_301_30@<head> @H_301_30@ <Meta charset="UTF-8"> @H_301_30@ <title>Title</title> @H_301_30@</head> @H_301_30@<body> @H_301_30@ <h1>成功!</h1> @H_301_30@ <!--th:text 将div里面的文本内容设置为 --> @H_301_30@ <div th:text="${hello}">这是显示欢迎信息</div> @H_301_30@</body> @H_301_30@</html> |
(3).thymeleaf语法
@H_301_30@[1].th:text:改变当前元素里面的文本内容
@H_301_30@[2].表达式
@H_301_30@@H_301_30@Simple expressions:(表达式语法) @H_301_30@ Variable Expressions: ${...}:获取变量值;OGNL; @H_301_30@ 1)、获取对象的属性、调用方法 @H_301_30@ 2)、使用内置的基本对象: @H_301_30@ #ctx : the context object. @H_301_30@ #vars: the context variables. @H_301_30@ #locale : the context locale. @H_301_30@ #request : (only in Web Contexts) the HttpServletRequest object. @H_301_30@ #response : (only in Web Contexts) the HttpServletResponse object. @H_301_30@ #session : (only in Web Contexts) the HttpSession object. @H_301_30@ #servletContext : (only in Web Contexts) the ServletContext object. @H_301_30@ @H_301_30@ ${session.foo} @H_301_30@ 3)、内置的一些工具对象: @H_301_30@#execInfo : information about the template being processed. @H_301_30@#messages : methods for obtaining externalized messages inside variables expressions,in the same way as they would be obtained using #{…} Syntax. @H_301_30@#uris : methods for escaping parts of URLs/URIs @H_301_30@#conversions : methods for executing the configured conversion service (if any). @H_301_30@#dates : methods for java.util.Date objects: formatting,component extraction,etc. @H_301_30@#calendars : analogous to #dates,but for java.util.Calendar objects. @H_301_30@#numbers : methods for formatting numeric objects. @H_301_30@#strings : methods for String objects: contains,startsWith,prepending/appending,etc. @H_301_30@#objects : methods for objects in general. @H_301_30@#bools : methods for boolean evaluation. @H_301_30@#arrays : methods for arrays. @H_301_30@#lists : methods for lists. @H_301_30@#sets : methods for sets. @H_301_30@#maps : methods for maps. @H_301_30@#aggregates : methods for creating aggregates on arrays or collections. @H_301_30@#ids : methods for dealing with id attributes that might be repeated (for example,as a result of an iteration). @H_301_30@ @H_301_30@ Selection Variable Expressions: *{...}:选择表达式:和${}在功能上是一样; @H_301_30@ 补充:配合 th:object="${session.user}: @H_301_30@ <div th:object="${session.user}"> @H_301_30@ <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> @H_301_30@ <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> @H_301_30@ <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> @H_301_30@ </div> @H_301_30@ @H_301_30@ Message Expressions: #{...}:获取国际化内容 @H_301_30@ Link URL Expressions: @{...}:定义URL; @H_301_30@ @{/order/process(execId=${execId},execType='FAST')} @H_301_30@ Fragment Expressions: ~{...}:片段引用表达式 @H_301_30@ <div th:insert="~{commons :: main}">...</div> @H_301_30@ @H_301_30@Literals(字面量) @H_301_30@ Text literals: 'one text','Another one!',… @H_301_30@ Number literals: 0,34,3.0,12.3,… @H_301_30@ Boolean literals: true,false @H_301_30@ Null literal: null @H_301_30@ Literal tokens: one,sometext,main,… @H_301_30@Text operations:(文本操作) @H_301_30@ String concatenation: + @H_301_30@ Literal substitutions: |The name is ${name}| @H_301_30@Arithmetic operations:(数学运算) @H_301_30@ Binary operators: +,-,*,/,% @H_301_30@ Minus sign (unary operator): - @H_301_30@Boolean operations:(布尔运算) @H_301_30@ Binary operators: and,or @H_301_30@ Boolean negation (unary operator): !,not @H_301_30@Comparisons and equality:(比较运算) @H_301_30@ Comparators: >,<,>=,<= ( gt,lt,ge,le ) @H_301_30@ Equality operators: ==,!= ( eq,ne ) @H_301_30@Conditional operators:条件运算(三元运算符) @H_301_30@ If-then: (if) ? (then) @H_301_30@ If-then-else: (if) ? (then) : (else) @H_301_30@ Default: (value) ?: (defaultvalue) @H_301_30@Special tokens: @H_301_30@ No-Operation: _ |
4.SpringMVC自动配置
@H_301_30@(1).Spring MVC auto-configuration
Spring Boot 自动配置SpringMVC
SpringBoot对SpringMVC的默认配置(WebMvcAutoConfiguration)
@H_301_30@ Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans. @H_301_30@ @H_871_403@自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何渲染(转发?重定向?)) @H_301_30@ ContentNegotiatingViewResolver:组合所有的视图解析器的 @H_301_30@ 如何定制:我们可以自己给容器中添加一个视图解析器;自动的将其组合进来; @H_301_30@ Support for serving static resources,including support for WebJars (see below). @H_301_30@ @H_871_403@静态资源文件夹路径,webjars @H_301_30@ Automatic registration of Converter, GenericConverter, Formatter beans. @H_301_30@ Converter:转换器; public String hello(User user):类型转换使用Converter @H_301_30@ Formatter格式化器; 2017.12.17===Date;@H_301_30@@Bean @H_301_30@@ConditionalOnProperty(prefix = "spring.mvc",name = "date-format")//在文件中配置日期格式化的规则 @H_301_30@public Formatter<Date> dateFormatter() { @H_301_30@return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件 @H_301_30@} |
@H_301_30@初始化WebDataBinder; @H_301_30@请求数据=====JavaBean; |
(2).扩展SpringMVC
@H_301_30@ <mvc:view-controller path="/hello" view-name="success"/> @H_301_30@ <mvc:interceptors> @H_301_30@ <mvc:interceptor> @H_301_30@ <mvc:mapping path="/hello"/> @H_301_30@ <bean></bean> @H_301_30@ </mvc:interceptor> @H_301_30@ </mvc:interceptors> |
@H_871_403@编写配置类(@Configuration),是WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc;
@H_871_403@在保留所有的自动配置情况下,也能用我们扩展的配置
@H_301_30@//使用 WebMvcConfigurer可以来扩展SpringMVC功能 @H_301_30@//因为WebMvcConfigurerAdapter已经过时,所以我们使用接口WebMvcConfigurer替代 @H_301_30@@Configuration @H_301_30@public class MyMvcConfig implements WebMvcConfigurer { @H_301_30@ @Override @H_301_30@ public void addViewControllers(ViewControllerRegistry registry) { @H_301_30@ //super.addViewControllers(registry) @H_301_30@ //浏览器发送/pluto请求来到success @H_301_30@ registry.addViewController("/pluto").setViewName("success"); @H_301_30@ } @H_301_30@} |
原理解析:
[1].WebMvcAutoConfiguration是SpringMVC自动配置类
[2].在做其他自动配置时会导入@Import(EnableWebMvcConfiguration.class)
@H_301_30@@H_301_30@ @Configuration @H_301_30@public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration { @H_301_30@ private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @H_301_30@ @H_301_30@//从容器中获取所有的WebMvcConfigurer @H_301_30@ @Autowired(required = false) @H_301_30@ public void setConfigurers(List<WebMvcConfigurer> configurers) { @H_301_30@ if (!CollectionUtils.isEmpty(configurers)) { @H_301_30@ this.configurers.addWebMvcConfigurers(configurers); @H_301_30@ //一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用; @H_301_30@ @Override @H_301_30@ // public void addViewControllers(ViewControllerRegistry registry) { @H_301_30@ // for (WebMvcConfigurer delegate : this.delegates) { @H_301_30@ // delegate.addViewControllers(registry); @H_301_30@ // } @H_301_30@ } @H_301_30@ } @H_301_30@} |
[3].容器中所有的WebMvcConfigurer都会一起起作用
[4].手写的配置类也会被调用
@H_871_403@结果:SpringMVC的自动配置和手写的扩展配置都起作用;
(3).全面接管SpringMVC
SpringBoot对SpringMVC的自动配置不需要;自己配置所有配置;所有的SpringMVC自动配置使其失效。
@H_871_403@需要在配置类中添加@EnableWebMvc即可;
@H_301_30@//使用 WebMvcConfigurer可以来扩展SpringMVC功能 @H_301_30@//因为WebMvcConfigurerAdapter已经过时,所以我们使用接口WebMvcConfigurer替代 @H_301_30@@EnableWebMvc @H_301_30@@Configuration @H_301_30@public class MyMvcConfig implements WebMvcConfigurer { @H_301_30@ @Override @H_301_30@ public void addViewControllers(ViewControllerRegistry registry) { @H_301_30@ //super.addViewControllers(registry) @H_301_30@ //浏览器发送/pluto请求来到success @H_301_30@ registry.addViewController("/pluto").setViewName("success"); @H_301_30@ } @H_301_30@} |
@H_871_403@原理解析:@EnableWebMvc自动配置失效
[1].@EnableWebMvc的核心
@H_301_30@@H_301_30@@Retention(RetentionPolicy.RUNTIME) @H_301_30@@Target({ElementType.TYPE}) @H_301_30@@Documented @H_301_30@@Import({DelegatingWebMvcConfiguration.class}) @H_301_30@public @interface EnableWebMvc { @H_301_30@} |
[2].DelegatingWebMvcConfiguration
@H_301_30@@H_301_30@@Configuration( @H_301_30@ proxyBeanMethods = false @H_301_30@) @H_301_30@public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { |
[3].WebMvcAutoConfiguration
@H_301_30@@Configuration( @H_301_30@ proxyBeanMethods = false @H_301_30@) @H_301_30@@ConditionalOnWebApplication( @H_301_30@ type = Type.SERVLET @H_301_30@) @H_301_30@@ConditionalOnClass({Servlet.class,DispatcherServlet.class,WebMvcConfigurer.class}) @H_301_30@//容器中没有这个组件的时候,这个自动配置类才生效 @H_301_30@@ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @H_301_30@@AutoConfigureOrder(-2147483638) @H_301_30@@AutoConfigureAfter({DispatcherServletAutoConfiguration.class,TaskExecutionAutoConfiguration.class,ValidationAutoConfiguration.class}) @H_301_30@public class WebMvcAutoConfiguration { |
[4].@EnableWebMvc将WebMvcConfigurationSupport组件导入进来
[5].导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能
5.修改SpringBoot的默认配置
@H_301_30@模式:(1).SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来;
(2).在SpringBoot中有非常多的xxxConfigurer帮助我们进行扩展配置
(3).在SpringBoot中v 有很多的xxxCustomizer帮助我们进行定制配置
6.RestfulCRUD
(1).访问首页(默认)
@H_301_30@//使用 WebMvcConfigurer可以来扩展SpringMVC功能 @H_301_30@//因为WebMvcConfigurerAdapter已经过时,所以我们使用接口WebMvcConfigurer替代 @H_301_30@//@EnableWebMvc @H_301_30@@Configuration @H_301_30@public class MyMvcConfig implements WebMvcConfigurer { @H_301_30@ @Override @H_301_30@ public void addViewControllers(ViewControllerRegistry registry) { @H_301_30@ //super.addViewControllers(registry) @H_301_30@ //浏览器发送/pluto请求来到success @H_301_30@ registry.addViewController("/pluto").setViewName("success"); @H_301_30@ } @H_301_30@ @H_301_30@ //所有的WebMvcConfigurer组件会一起起作用 @H_301_30@ //@Bean将组件注册在容器中 @H_301_30@ @Bean @H_301_30@ @H_301_30@ public WebMvcConfigurer webMvcConfigurer(){ @H_301_30@ WebMvcConfigurer dapter = new WebMvcConfigurer() { @H_301_30@ @Override @H_301_30@ public void addViewControllers(ViewControllerRegistry registry) { @H_301_30@ registry.addViewController("/").setViewName("login"); @H_301_30@ registry.addViewController("/index.html").setViewName("login"); @H_301_30@ } @H_301_30@ }; @H_301_30@ return dapter; @H_301_30@ } @H_301_30@} |
(2).国际化
[1].编写国际化配置文件
[2].使用ResourceBundleMessageSource管理国际化资源文件
[3].在页面使用fmt:message取出国际化内容
实现步骤:
[1].编写国际化配置文件
@H_301_30@#login_zh_CN.properties @H_301_30@login.btn=登录 @H_301_30@login.password=密码 @H_301_30@login.remember=记住我 @H_301_30@login.tip=请登录 @H_301_30@login.username=用户名 @H_301_30@ @H_301_30@#login_en_US.properties @H_301_30@login.btn=Sing In @H_301_30@login.password=Password @H_301_30@login.remember=remember me @H_301_30@login.tip=Please sign in @H_301_30@login.username=UserName @H_301_30@ @H_301_30@#login.properties @H_301_30@login.btn=登录~ @H_301_30@login.password=密码~ @H_301_30@login.remember=记住我~ @H_301_30@login.tip=请登录~ @H_301_30@login.username=用户名~ |
[2].SpringBoot自动配置好了管理国际化资源文件的组件
@H_301_30@@ConfigurationProperties(prefix = "spring.messages") @H_301_30@public class MessageSourceAutoConfiguration { @H_301_30@ @H_301_30@ /** @H_301_30@* Comma-separated list of basenames (essentially a fully-qualified classpath @H_301_30@* location),each following the ResourceBundle convention with relaxed support for @H_301_30@* slash based locations. If it doesn't contain a package qualifier (such as @H_301_30@* "org.mypackage"),it will be resolved from the classpath root. @H_301_30@*/ @H_301_30@private String basename = "messages"; @H_301_30@ //我们的配置文件可以直接放在类路径下叫messages.properties; @H_301_30@ @H_301_30@ @Bean @H_301_30@public MessageSource messageSource() { @H_301_30@ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); @H_301_30@if (StringUtils.hasText(this.basename)) { @H_301_30@ //设置国际化资源文件的基础名(去掉语言国家代码的) @H_301_30@messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray( @H_301_30@StringUtils.trimAllWhitespace(this.basename))); @H_301_30@} @H_301_30@if (this.encoding != null) { @H_301_30@messageSource.setDefaultEncoding(this.encoding.name()); @H_301_30@} @H_301_30@messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale); @H_301_30@messageSource.setCacheSeconds(this.cacheSeconds); @H_301_30@messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat); @H_301_30@return messageSource; @H_301_30@} |
[3].取出国际化内容
@H_301_30@<!DOCTYPE html> @H_301_30@<html lang="en" xmlns:th="http://www.thymeleaf.org"> @H_301_30@ <head> @H_301_30@ <Meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> @H_301_30@ <Meta name="viewport" content="width=device-width,initial-scale=1,shrink-to-fit=no"> @H_301_30@ <Meta name="description" content=""> @H_301_30@ <Meta name="author" content=""> @H_301_30@ <title>Signin Template for Bootstrap</title> @H_301_30@ <!-- Bootstrap core CSS --> @H_301_30@ <link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet"> @H_301_30@ <!-- Custom styles for this template --> @H_301_30@ <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet"> @H_301_30@ </head> @H_301_30@ @H_301_30@ <body class="text-center"> @H_301_30@ <form class="form-signin" action="dashboard.html"> @H_301_30@ <img class="mb-4" th:src="@{asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72"> @H_301_30@ <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1> @H_301_30@ <label class="sr-only" th:text="#{login.username}">Username</label> @H_301_30@ <input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus=""> @H_301_30@ <label class="sr-only" th:text="#{login.password}">Password</label> @H_301_30@ <input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required=""> @H_301_30@ <div class="checkBox mb-3"> @H_301_30@ <label> @H_301_30@ <input type="checkBox" value="remember-me"> [[#{login.remember}]] @H_301_30@ </label> @H_301_30@ </div> @H_301_30@ <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button> @H_301_30@ <p class="mt-5 mb-3 text-muted">© 2017-2018</p> @H_301_30@ <a class="btn btn-sm">中文</a> @H_301_30@ <a class="btn btn-sm">English</a> @H_301_30@ </form> @H_301_30@ @H_301_30@ </body> @H_301_30@ @H_301_30@</html> |
@H_871_403@如果出现乱码的现象,那么只需要重新改下编码就行。可以设置全局配置,也可以设置项目配置。以下是全局配置
@H_301_30@@H_871_403@效果:根据浏览器语言设置的信息切换了国际化
@H_871_403@原理解析:国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象)
@H_301_30@@Bean @H_301_30@@ConditionalOnMissingBean @H_301_30@@ConditionalOnProperty(prefix = "spring.mvc",name = "locale") @H_301_30@public LocaleResolver localeResolver() { @H_301_30@if (this.mvcProperties @H_301_30@.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) { @H_301_30@return new FixedLocaleResolver(this.mvcProperties.getLocale()); @H_301_30@} @H_301_30@AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); @H_301_30@localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); @H_301_30@return localeResolver; @H_301_30@} @H_301_30@默认的就是根据请求头带来的区域信息获取Locale进行国际化 |
[4].点击链接切换国际化
@H_301_30@/** @H_301_30@ * 可以在连接上携带区域信息 @H_301_30@ */ @H_301_30@public class MyLocaleResolver implements LocaleResolver { @H_301_30@ @H_301_30@ @Override @H_301_30@ public Locale resolveLocale(HttpServletRequest request) { @H_301_30@ String l = request.getParameter("l"); @H_301_30@ Locale locale = Locale.getDefault(); @H_301_30@ if(!StringUtils.isEmpty(l)){ @H_301_30@ String[] split = l.split("_"); @H_301_30@ locale = new Locale(split[0],split[1]); @H_301_30@ } @H_301_30@ return locale; @H_301_30@ } @H_301_30@ @H_301_30@ @Override @H_301_30@ public void setLocale(HttpServletRequest request,HttpServletResponse response,Locale locale) { @H_301_30@ @H_301_30@ } @H_301_30@} @H_301_30@ @H_301_30@ @H_301_30@ @Bean @H_301_30@ public LocaleResolver localeResolver(){ @H_301_30@ return new MyLocaleResolver(); @H_301_30@ } @H_301_30@} |
(3).登录
@H_871_403@开发期间模板引擎页面修改以后,如果想实时生效,需要执行以下两个步骤
[1].禁用模板引擎的缓存
@H_301_30@# 禁用缓存 @H_301_30@spring.thymeleaf.cache=false |
[2].重新编译
@H_301_30@页面修改完成以后ctrl+f9 |
@H_301_30@<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p> |
防止表单重复提交的办法:重定向
@H_301_30@#LoginController.java @H_301_30@if(!StringUtils.isEmpty(username)&& "123456".equals(password)){ @H_301_30@ //登录成功 防止表单重复提交,可以重定向到主页 @H_301_30@ return "redirect:/main.html"; |
@H_301_30@#MyMvcConfig.java @H_301_30@//所有的WebMvcConfigurer组件会一起起作用 @H_301_30@//@Bean将组件注册在容器中 @H_301_30@@Bean @H_301_30@public WebMvcConfigurer webMvcConfigurer(){ @H_301_30@ WebMvcConfigurer dapter = new WebMvcConfigurer() { @H_301_30@ @Override @H_301_30@ public void addViewControllers(ViewControllerRegistry registry) { @H_301_30@ registry.addViewController("/").setViewName("login"); @H_301_30@ registry.addViewController("/index.html").setViewName("login"); @H_301_30@ registry.addViewController("/main.html").setViewName("dashboard"); @H_301_30@ } @H_301_30@ }; @H_301_30@ return dapter; @H_301_30@} |
(4).拦截器进行登陆检查
[1].拦截器
@H_301_30@/** @H_301_30@ * 登录检查 @H_301_30@ */ @H_301_30@public class LoginHandlerInterceptor implements HandlerInterceptor{ @H_301_30@ //目标方法执行之前 @H_301_30@ @Override @H_301_30@ public boolean preHandle(HttpServletRequest request,Object handler) throws Exception { @H_301_30@ Object user = request.getSession().getAttribute("loginUser"); @H_301_30@ if(user==null){ @H_301_30@ //未登录 返回登录页面 @H_301_30@ request.setAttribute("msg","没有权限请先登录"); @H_301_30@ request.getRequestDispatcher("/index.html").forward(request,response); @H_301_30@ return false; @H_301_30@ }else { @H_301_30@ //已登录 放行请求 @H_301_30@ return true; @H_301_30@ } @H_301_30@ } @H_301_30@} |
[2].注册拦截器
@H_301_30@//所有的WebMvcConfigurer组件会一起起作用 @H_301_30@//@Bean将组件注册在容器中 @H_301_30@@Bean @H_301_30@public WebMvcConfigurer webMvcConfigurer(){ @H_301_30@ WebMvcConfigurer dapter = new WebMvcConfigurer() { @H_301_30@ @Override @H_301_30@ public void addViewControllers(ViewControllerRegistry registry) { @H_301_30@ registry.addViewController("/").setViewName("login"); @H_301_30@ registry.addViewController("/index.html").setViewName("login"); @H_301_30@ registry.addViewController("/main.html").setViewName("dashboard"); @H_301_30@ } @H_301_30@ @H_301_30@ //注册拦截器 @H_301_30@ @Override @H_301_30@ public void addInterceptors(InterceptorRegistry registry) { @H_301_30@ //super.addInterceptors(registry); @H_301_30@ //静态资源 *.css *.js @H_301_30@ //springboot已经做了静态资源映射 @H_301_30@ registry.addInterceptor(new LoginHandlerInterceptor()).addPathPatterns("/**") @H_301_30@ .excludePathPatterns("/index.html","/","/user/login"); @H_301_30@ } @H_301_30@ @H_301_30@ }; @H_301_30@ @H_301_30@ @H_301_30@ return dapter; @H_301_30@} |
(5).CRUD-员工列表
[1].需求分析
RestfulCRUD:CRUD满足Rest风格;
URI:/资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
@H_301_30@ |
普通CRUD(uri来区分操作) |
RestfulCRUD |
getEmp |
emp---GET |
|
addEmp?xxx |
emp---POST |
|
updateEmp?id=xxx&xxx=xx |
emp/{id}---PUT |
|
deleteEmp?id=1 |
emp/{id}---DELETE |
[2].实验的请求架构
@H_301_30@实验功能 | @H_301_30@请求URI | @H_301_30@请求方式 |
@H_301_30@查询所有员工 | @H_301_30@emps | @H_301_30@GET |
@H_301_30@查询某个员工(来到修改页面) | @H_301_30@emp/1 | @H_301_30@GET |
@H_301_30@来到添加页面 | @H_301_30@emp | @H_301_30@GET |
@H_301_30@添加员工 | @H_301_30@emp | @H_301_30@POST |
@H_301_30@来到修改页面(查出员工进行信息回显) | @H_301_30@emp/1 | @H_301_30@GET |
@H_301_30@修改员工 | @H_301_30@emp | @H_301_30@PUT |
@H_301_30@删除员工 | @H_301_30@emp/1 | @H_301_30@DELETE |
[3].员工列表
thymeleaf公共页面元素抽取
@H_301_30@1、抽取公共片段 @H_301_30@<div th:fragment="copy"> @H_301_30@© 2011 The Good Thymes Virtual Grocery @H_301_30@</div> @H_301_30@ @H_301_30@2、引入公共片段 @H_301_30@<div th:insert="~{footer :: copy}"></div> @H_301_30@~{templatename::selector}:模板名::选择器 @H_301_30@~{templatename::fragmentname}:模板名::片段名 @H_301_30@ @H_301_30@3、默认效果: @H_301_30@insert的公共片段在div标签中 @H_301_30@如果使用th:insert等属性进行引入,可以不用写~{}: @H_301_30@行内写法可以加上:[[~{}]];[(~{})]; |
@H_301_30@<footer th:fragment="copy"> @H_301_30@© 2011 The Good Thymes Virtual Grocery @H_301_30@</footer> @H_301_30@ @H_301_30@引入方式 @H_301_30@<div th:insert="footer :: copy"></div> @H_301_30@<div th:replace="footer :: copy"></div> @H_301_30@<div th:include="footer :: copy"></div> @H_301_30@ @H_301_30@效果 @H_301_30@<div> @H_301_30@ <footer> @H_301_30@ © 2011 The Good Thymes Virtual Grocery @H_301_30@ </footer> @H_301_30@</div> @H_301_30@ @H_301_30@<footer> @H_301_30@© 2011 The Good Thymes Virtual Grocery @H_301_30@</footer> @H_301_30@ @H_301_30@<div> @H_301_30@© 2011 The Good Thymes Virtual Grocery @H_301_30@</div> |
引入片段的时候传入参数
@H_301_30@<nav class="col-md-2 d-none d-md-block bg-light sidebar" id="sidebar"> @H_301_30@ <div class="sidebar-sticky"> @H_301_30@ <ul class="nav flex-column"> @H_301_30@ <li class="nav-item"> @H_301_30@ <a class="nav-link active" @H_301_30@ th:class="${activeUri=='main.html'?'nav-link active':'nav-link'}" @H_301_30@ href="#" th:href="@{/main.html}"> @H_301_30@ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-home"> @H_301_30@ <path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"></path> @H_301_30@ <polyline points="9 22 9 12 15 12 15 22"></polyline> @H_301_30@ </svg> @H_301_30@ Dashboard <span class="sr-only">(current)</span> @H_301_30@ </a> @H_301_30@ </li> @H_301_30@ @H_301_30@<!--引入侧边栏;传入参数--> @H_301_30@<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div> |
(6).CRUD-员工添加
templates/emp/add.html
@H_301_30@<form> @H_301_30@ <div class="form-group"> @H_301_30@ <label>LastName</label> @H_301_30@ <input type="text" class="form-control" placeholder="zhangsan"> @H_301_30@ </div> @H_301_30@ <div class="form-group"> @H_301_30@ <label>Email</label> @H_301_30@ <input type="email" class="form-control" placeholder="zhangsan@atguigu.com"> @H_301_30@ </div> @H_301_30@ <div class="form-group"> @H_301_30@ <label>Gender</label><br/> @H_301_30@ <div class="form-check form-check-inline"> @H_301_30@ <input class="form-check-input" type="radio" name="gender" value="1"> @H_301_30@ <label class="form-check-label">男</label> @H_301_30@ </div> @H_301_30@ <div class="form-check form-check-inline"> @H_301_30@ <input class="form-check-input" type="radio" name="gender" value="0"> @H_301_30@ <label class="form-check-label">女</label> @H_301_30@ </div> @H_301_30@ </div> @H_301_30@ <div class="form-group"> @H_301_30@ <label>department</label> @H_301_30@ <select class="form-control"> @H_301_30@ <option>1</option> @H_301_30@ <option>2</option> @H_301_30@ <option>3</option> @H_301_30@ <option>4</option> @H_301_30@ <option>5</option> @H_301_30@ </select> @H_301_30@ </div> @H_301_30@ <div class="form-group"> @H_301_30@ <label>Birth</label> @H_301_30@ <input type="text" class="form-control" placeholder="zhangsan"> @H_301_30@ </div> @H_301_30@ <button type="submit" class="btn btn-primary">添加</button> @H_301_30@</form> |
[1].格式问题
若提交的日期格式不对,则会出现404
@H_301_30@SpringMVC将页面提交的值需要转换为指定的类型
@H_301_30@spring: @H_301_30@ mvc: @H_301_30@ format: @H_301_30@ date: yyyy-MM-dd |
(7).CRUD-员工修改
@H_301_30@<!--需要区分是员工修改还是添加;--> @H_301_30@<form th:action="@{/emp}" method="post"> @H_301_30@ <!--发送put请求修改员工数据--> @H_301_30@ <!-- @H_301_30@1、SpringMVC中配置HiddenHttpMethodFilter;(SpringBoot自动配置好的) @H_301_30@2、页面创建一个post表单 @H_301_30@3、创建一个input项,name="_method";值就是我们指定的请求方式 @H_301_30@--> @H_301_30@ <input type="hidden" name="_method" value="put" th:if="${emp!=null}"/> @H_301_30@ <input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}"> @H_301_30@ <div class="form-group"> @H_301_30@ <label>LastName</label> @H_301_30@ <input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}"> @H_301_30@ </div> @H_301_30@ <div class="form-group"> @H_301_30@ <label>Email</label> @H_301_30@ <input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}"> @H_301_30@ </div> @H_301_30@ <div class="form-group"> @H_301_30@ <label>Gender</label><br/> @H_301_30@ <div class="form-check form-check-inline"> @H_301_30@ <input class="form-check-input" type="radio" name="gender" value="1" th:checked="${emp!=null}?${emp.gender==1}"> @H_301_30@ <label class="form-check-label">男</label> @H_301_30@ </div> @H_301_30@ <div class="form-check form-check-inline"> @H_301_30@ <input class="form-check-input" type="radio" name="gender" value="0" th:checked="${emp!=null}?${emp.gender==0}"> @H_301_30@ <label class="form-check-label">女</label> @H_301_30@ </div> @H_301_30@ </div> @H_301_30@ <div class="form-group"> @H_301_30@ <label>department</label> @H_301_30@ <!--提交的是部门的id--> @H_301_30@ <select class="form-control" name="department.id"> @H_301_30@ <option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option> @H_301_30@ </select> @H_301_30@ </div> @H_301_30@ <div class="form-group"> @H_301_30@ <label>Birth</label> @H_301_30@ <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}"> @H_301_30@ </div> @H_301_30@ <button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button> @H_301_30@</form> |
(8).CRUD-员工删除
@H_301_30@<tr th:each="emp:${emps}"> @H_301_30@ <td th:text="${emp.id}"></td> @H_301_30@ <td>[[${emp.lastName}]]</td> @H_301_30@ <td th:text="${emp.email}"></td> @H_301_30@ <td th:text="${emp.gender}==0?'女':'男'"></td> @H_301_30@ <td th:text="${emp.department.departmentName}"></td> @H_301_30@ <td th:text="${#dates.format(emp.birth,'yyyy-MM-dd HH:mm')}"></td> @H_301_30@ <td> @H_301_30@ <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a> @H_301_30@ <button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button> @H_301_30@ </td> @H_301_30@</tr> @H_301_30@ @H_301_30@ @H_301_30@<script> @H_301_30@ $(".deleteBtn").click(function(){ @H_301_30@ //删除当前员工的 @H_301_30@ $("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit(); @H_301_30@ return false; @H_301_30@ }); @H_301_30@</script> |
7.错误处理机制
(1).SpringBoot默认的错误处理机制
[1].默认效果
@H_301_30@@H_871_403@浏览器发送请求的请求头
@H_301_30@@H_871_403@其它客户端:默认响应一个json数据
@H_301_30@[2].原理
ErrorMvcAutoConfiguration:错误处理自动配置
@H_301_30@1].DefaultErrorAttributes
@H_301_30@@H_301_30@帮我们在页面共享信息; @H_301_30@@Override @H_301_30@public Map<String,Object> getErrorAttributes(RequestAttributes requestAttributes, @H_301_30@boolean includeStackTrace) { @H_301_30@Map<String,Object> errorAttributes = new LinkedHashMap<String,Object>(); @H_301_30@errorAttributes.put("timestamp",new Date()); @H_301_30@addStatus(errorAttributes,requestAttributes); @H_301_30@addErrorDetails(errorAttributes,requestAttributes,includeStackTrace); @H_301_30@addPath(errorAttributes,requestAttributes); @H_301_30@return errorAttributes; @H_301_30@} |
2].BasicErrorController
@H_301_30@@H_301_30@@Controller @H_301_30@@RequestMapping({"${server.error.path:${error.path:/error}}"}) @H_301_30@public class BasicErrorController extends AbstractErrorController { @H_301_30@ private final ErrorProperties errorProperties; |
@H_301_30@#产生html类型的数据,浏览器发送的请求来到这个方法处理 @H_301_30@@RequestMapping( @H_301_30@ produces = {"text/html"} @H_301_30@) @H_301_30@public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) { @H_301_30@ HttpStatus status = this.getStatus(request); @H_301_30@ Map<String,Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request,this.getErrorAttributeOptions(request,MediaType.TEXT_HTML))); @H_301_30@ response.setStatus(status.value()); @H_301_30@ //到那个页面作为错误页面;包含页面地址和页面内容 @H_301_30@ ModelAndView modelAndView = this.resolveErrorView(request,response,status,model); @H_301_30@ return modelAndView != null ? modelAndView : new ModelAndView("error",model); @H_301_30@} @H_301_30@ @H_301_30@#产生json数据,其他客户端来到这个方法处理 @H_301_30@@RequestMapping @H_301_30@public ResponseEntity<Map<String,Object>> error(HttpServletRequest request) { @H_301_30@ HttpStatus status = this.getStatus(request); @H_301_30@ if (status == HttpStatus.NO_CONTENT) { @H_301_30@ return new ResponseEntity(status); @H_301_30@ } else { @H_301_30@ Map<String,Object> body = this.getErrorAttributes(request,MediaType.ALL)); @H_301_30@ return new ResponseEntity(body,status); @H_301_30@ } @H_301_30@} |
3].ErrorMvcAutoConfiguration.ErrorPageCustomizer
@H_301_30@@H_301_30@#系统出现错误后到error请求进行处理 @H_301_30@#web.xml注册的错误页面规则 @H_301_30@public class ErrorProperties { @H_301_30@ @Value("${error.path:/error}") @H_301_30@ private String path = "/error"; |
4].DefaultErrorViewResolverConfiguration
@H_301_30@@H_301_30@@Override @H_301_30@public ModelAndView resolveErrorView(HttpServletRequest request,HttpStatus status, @H_301_30@Map<String,Object> model) { @H_301_30@ModelAndView modelAndView = resolve(String.valueOf(status),model); @H_301_30@if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { @H_301_30@modelAndView = resolve(SERIES_VIEWS.get(status.series()),model); @H_301_30@} @H_301_30@return modelAndView; @H_301_30@} @H_301_30@ @H_301_30@private ModelAndView resolve(String viewName,Map<String,Object> model) { @H_301_30@ //默认SpringBoot可以去找到一个页面? error/404 @H_301_30@String errorViewName = "error/" + viewName; @H_301_30@ @H_301_30@ //模板引擎可以解析这个页面地址就用模板引擎解析 @H_301_30@TemplateAvailabilityProvider provider = this.templateAvailabilityProviders @H_301_30@.getProvider(errorViewName,this.applicationContext); @H_301_30@if (provider != null) { @H_301_30@ //模板引擎可用的情况下返回到errorViewName指定的视图地址 @H_301_30@return new ModelAndView(errorViewName,model); @H_301_30@} @H_301_30@ //模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html @H_301_30@return resolveResource(errorViewName,model); @H_301_30@} |
[3].步骤
系统出现4xx或者5xx的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);来到/error请求;被BasicErrorController处理;
1].响应页面
页面是由DefaultErrorViewResolver解析得到的
@H_301_30@protected ModelAndView resolveErrorView(HttpServletRequest request, @H_301_30@ HttpServletResponse response,Object> model) { @H_301_30@ //所有的ErrorViewResolver得到ModelAndView @H_301_30@ for (ErrorViewResolver resolver : this.errorViewResolvers) { @H_301_30@ ModelAndView modelAndView = resolver.resolveErrorView(request,model); @H_301_30@ if (modelAndView != null) { @H_301_30@ return modelAndView; @H_301_30@ } @H_301_30@ } @H_301_30@ return null; @H_301_30@} |
(2).定制错误响应
[1].如何定制错误页面
1].模板引擎存在
@H_871_403@有模板引擎的情况下;error/状态码;将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下
@H_301_30@@H_871_403@我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误,精确优先(优先寻找精确的状态码.html);
@H_301_30@ | @H_301_30@ |
timestamp |
时间戳 |
status |
状态码 |
error |
|
exception |
异常对象 |
message |
异常消息 |
errors |
JSR303数据校验的错误 |
2].模板引擎不存在
@H_301_30@ 模板引擎找不到这个错误页面,静态资源文件夹下找; @H_301_30@3].其它
@H_301_30@ @H_871_403@以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面;[2].如何定制错误json数据
1].自定义异常处理&返回定制json数据
8.配置嵌入式Servlet容器
SpringBoot默认使用Tomcat作为嵌入式的Servlet容器
@H_301_30@(1).定制|修改Servlet容器配置
[1].修改server相关配置(ServerProperties)
@H_301_30@server.port=8081 @H_301_30@server.context-path=/crud @H_301_30@server.tomcat.uri-encoding=UTF-8 @H_301_30@ @H_301_30@//通用的Servlet容器设置 @H_301_30@server.xxx @H_301_30@//Tomcat的设置 @H_301_30@server.tomcat.xxx |
[2].WebServerFactoryCustomizer
编写WebServerFactoryCustomizer:嵌入式Servlet容器定制器;修改Servlet容器的配置
@H_301_30@//spring1.0支持 EmbeddedServletContainerCustomizer @H_301_30@//spring2.0支持 webServerFactoryCustomizer @H_301_30@//参考文档:https://blog.csdn.net/Stitch__/article/details/88751497 @H_301_30@@Bean @H_301_30@public WebServerFactoryCustomizer webServerFactoryCustomizer(){ @H_301_30@ @H_301_30@ return new WebServerFactoryCustomizer <ConfigurableWebServerFactory>() { @H_301_30@ @H_301_30@ //定制嵌入式的servlet容器相关规则 @H_301_30@ @Override @H_301_30@ public void customize(ConfigurableWebServerFactory factory) { @H_301_30@ factory.setPort(8088); @H_301_30@ } @H_301_30@ }; @H_301_30@} |
(2).注册Servlet三大组件(Servlet、Filter、Listener)
@H_301_30@ 注册三大组件用以下方式:ServletRegistrationBean、FilterRegistrationBean、ServletListenerRegistrationBean[1].ServletRegistrationBean
@H_301_30@@Bean @H_301_30@public FilterRegistrationBean myFilter(){ @H_301_30@ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); @H_301_30@ registrationBean.setFilter(new MyFilter()); @H_301_30@ registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet")); @H_301_30@ return registrationBean; @H_301_30@} |
[2].FilterRegistrationBean
@H_301_30@@Bean @H_301_30@public FilterRegistrationBean myFilter(){ @H_301_30@ FilterRegistrationBean registrationBean = new FilterRegistrationBean(); @H_301_30@ registrationBean.setFilter(new MyFilter()); @H_301_30@ registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet")); @H_301_30@ return registrationBean; @H_301_30@} |
[3].ServletListenerRegistrationBean
@H_301_30@@Bean @H_301_30@public ServletListenerRegistrationBean myListener(){ @H_301_30@ ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean(new MyListener()); @H_301_30@ return registrationBean; @H_301_30@} |
SpringBoot帮我们自动SpringMVC的时候,自动的注册SpringMVC的前端控制器;DIspatcherServlet;
@H_301_30@ @Bean( @H_301_30@ name = {"dispatcherServletRegistration"} @H_301_30@ ) @H_301_30@ @ConditionalOnBean( @H_301_30@ value = {DispatcherServlet.class}, @H_301_30@ name = {"dispatcherServlet"} @H_301_30@ ) @H_301_30@ public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,WebMvcProperties webMvcProperties,ObjectProvider<MultipartConfigElement> multipartConfig) { @H_301_30@ DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,webMvcProperties.getServlet().getPath()); @H_301_30@ //默认拦截:/ 所有请求 包括静态资源 但是不拦截jsp /*会拦截jsp @H_301_30@ //可以通过server.serverletPath修改SrpingMVC前段控制器默认拦截的请求 @H_301_30@ registration.setName("dispatcherServlet"); @H_301_30@ registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup()); @H_301_30@ multipartConfig.ifAvailable(registration::setMultipartConfig); @H_301_30@ return registration; @H_301_30@ } @H_301_30@} |
(3).springboot使用其它servlet容器
@H_301_30@
[1].tomcat
Tomcat(默认使用)
@H_301_30@<dependency> @H_301_30@ <groupId>org.springframework.boot</groupId> @H_301_30@ <artifactId>spring-boot-starter-web</artifactId> @H_301_30@ 引入web模块默认就是使用嵌入式的Tomcat作为Servlet容器; @H_301_30@</dependency> |
[2].Jetty
目前只在springboot1.5.10版本上可以成功,spring2.0版本的都有错误
@H_301_30@@H_301_30@<!-- 引入web模块 --> @H_301_30@<dependency> @H_301_30@ <groupId>org.springframework.boot</groupId> @H_301_30@ <artifactId>spring-boot-starter-web</artifactId> @H_301_30@ <exclusions> @H_301_30@ <exclusion> @H_301_30@ <artifactId>spring-boot-starter-tomcat</artifactId> @H_301_30@ <groupId>org.springframework.boot</groupId> @H_301_30@ </exclusion> @H_301_30@ </exclusions> @H_301_30@</dependency> @H_301_30@ @H_301_30@<!--引入其他的Servlet容器--> @H_301_30@<dependency> @H_301_30@ <artifactId>spring-boot-starter-jetty</artifactId> @H_301_30@ <groupId>org.springframework.boot</groupId> @H_301_30@</dependency> |
[3].Undertow
Undertow在springBoot1.5.10和SpringBoot2.0都成功运行
@H_301_30@<!-- 引入web模块 --> @H_301_30@<dependency> @H_301_30@ <groupId>org.springframework.boot</groupId> @H_301_30@ <artifactId>spring-boot-starter-web</artifactId> @H_301_30@ <exclusions> @H_301_30@ <exclusion> @H_301_30@ <artifactId>spring-boot-starter-tomcat</artifactId> @H_301_30@ <groupId>org.springframework.boot</groupId> @H_301_30@ </exclusion> @H_301_30@ </exclusions> @H_301_30@</dependency> @H_301_30@ @H_301_30@<!--引入其他的Servlet容器--> @H_301_30@<dependency> @H_301_30@ <artifactId>spring-boot-starter-undertow</artifactId> @H_301_30@ <groupId>org.springframework.boot</groupId> @H_301_30@</dependency> |
(4).嵌入式Servlet容器自动配置原理
[1].SpringBoot1.5.10版本
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置
@H_301_30@@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE) @H_301_30@@Configuration @H_301_30@@ConditionalOnWebApplication @H_301_30@@Import(BeanPostProcessorsRegistrar.class) @H_301_30@//导入BeanPostProcessorsRegistrar:Spring注解版;给容器中导入一些组件 @H_301_30@//导入了EmbeddedServletContainerCustomizerBeanPostProcessor: @H_301_30@//后置处理器:bean初始化前后(创建完对象,还没赋值赋值)执行初始化工作 @H_301_30@public class EmbeddedServletContainerAutoConfiguration { @H_301_30@ @H_301_30@ @Configuration @H_301_30@@ConditionalOnClass({ Servlet.class,Tomcat.class })//判断当前是否引入了Tomcat依赖; @H_301_30@@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class,search = SearchStrategy.CURRENT)//判断当前容器没有用户自己定义EmbeddedServletContainerFactory:嵌入式的Servlet容器工厂;作用:创建嵌入式的Servlet容器 @H_301_30@public static class EmbeddedTomcat { @H_301_30@ @H_301_30@@Bean @H_301_30@public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { @H_301_30@ return new TomcatEmbeddedServletContainerFactory(); @H_301_30@ } @H_301_30@ @H_301_30@} @H_301_30@ @H_301_30@ /** @H_301_30@* Nested configuration if Jetty is being used. @H_301_30@*/ @H_301_30@@Configuration @H_301_30@@ConditionalOnClass({ Servlet.class,Server.class,Loader.class, @H_301_30@WebAppContext.class }) @H_301_30@@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class,search = SearchStrategy.CURRENT) @H_301_30@public static class EmbeddedJetty { @H_301_30@ @H_301_30@@Bean @H_301_30@public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() { @H_301_30@ return new JettyEmbeddedServletContainerFactory(); @H_301_30@ } @H_301_30@ @H_301_30@} @H_301_30@ @H_301_30@/** @H_301_30@* Nested configuration if Undertow is being used. @H_301_30@*/ @H_301_30@@Configuration @H_301_30@@ConditionalOnClass({ Servlet.class,Undertow.class,SslClientAuthMode.class }) @H_301_30@@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class,search = SearchStrategy.CURRENT) @H_301_30@public static class EmbeddedUndertow { @H_301_30@ @H_301_30@@Bean @H_301_30@public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() { @H_301_30@ return new UndertowEmbeddedServletContainerFactory(); @H_301_30@ } @H_301_30@ @H_301_30@} |
1].EmbeddedServletContainerFactory
EmbeddedServletContainerFactory:嵌入式Servlet容器工厂
@H_301_30@@H_301_30@public interface EmbeddedServletContainerFactory { @H_301_30@ @H_301_30@ //获取嵌入式的Servlet容器 @H_301_30@ EmbeddedServletContainer getEmbeddedServletContainer( @H_301_30@ ServletContextInitializer... initializers); @H_301_30@ @H_301_30@} |
2].EmbeddedServletContainer
EmbeddedServletContainer:嵌入式的Servlet容器
@H_301_30@3].TomcatEmbeddedServletContainerFactory
@H_301_30@@Override @H_301_30@public EmbeddedServletContainer getEmbeddedServletContainer( @H_301_30@ ServletContextInitializer... initializers) { @H_301_30@ //创建一个Tomcat @H_301_30@ Tomcat tomcat = new Tomcat(); @H_301_30@ @H_301_30@ //配置Tomcat的基本环节 @H_301_30@ File baseDir = (this.baseDirectory != null ? this.baseDirectory @H_301_30@ : createTempDir("tomcat")); @H_301_30@ tomcat.setBaseDir(baseDir.getAbsolutePath()); @H_301_30@ Connector connector = new Connector(this.protocol); @H_301_30@ tomcat.getService().addConnector(connector); @H_301_30@ customizeConnector(connector); @H_301_30@ tomcat.setConnector(connector); @H_301_30@ tomcat.getHost().setAutoDeploy(false); @H_301_30@ configureEngine(tomcat.getEngine()); @H_301_30@ for (Connector additionalConnector : this.additionalTomcatConnectors) { @H_301_30@ tomcat.getService().addConnector(additionalConnector); @H_301_30@ } @H_301_30@ prepareContext(tomcat.getHost(),initializers); @H_301_30@ @H_301_30@ //将配置好的Tomcat传入进去,返回一个EmbeddedServletContainer;并且启动Tomcat服务器 @H_301_30@ return getTomcatEmbeddedServletContainer(tomcat); @H_301_30@} |
4].嵌入式容器配置修改
@H_871_403@对嵌入式容器的配置修改是需要一下支持生效
@H_301_30@ServerProperties、EmbeddedServletContainerCustomizer |
5]修改原理
EmbeddedServletContainerCustomizerBeanPostProcessor
@H_871_403@容器中导入了EmbeddedServletContainerCustomizerBeanPostProcessor
@H_301_30@//初始化之前 @H_301_30@@Override @H_301_30@public Object postProcessBeforeInitialization(Object bean,String beanName) @H_301_30@ throws BeansException { @H_301_30@ //如果当前初始化的是一个ConfigurableEmbeddedServletContainer类型的组件 @H_301_30@ if (bean instanceof ConfigurableEmbeddedServletContainer) { @H_301_30@ // @H_301_30@ postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean); @H_301_30@ } @H_301_30@ return bean; @H_301_30@} @H_301_30@ @H_301_30@private void postProcessBeforeInitialization( @H_301_30@ConfigurableEmbeddedServletContainer bean) { @H_301_30@ //获取所有的定制器,调用每一个定制器的customize方法来给Servlet容器进行属性赋值; @H_301_30@ for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) { @H_301_30@ customizer.customize(bean); @H_301_30@ } @H_301_30@} @H_301_30@ @H_301_30@private Collection<EmbeddedServletContainerCustomizer> getCustomizers() { @H_301_30@ if (this.customizers == null) { @H_301_30@ // Look up does not include the parent context @H_301_30@ this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>( @H_301_30@ this.beanfactory @H_301_30@ //从容器中获取所有这葛类型的组件:EmbeddedServletContainerCustomizer @H_301_30@ //定制Servlet容器,给容器中可以添加一个EmbeddedServletContainerCustomizer类型的组件 @H_301_30@ .getBeansOfType(EmbeddedServletContainerCustomizer.class, @H_301_30@ false,false) @H_301_30@ .values()); @H_301_30@ Collections.sort(this.customizers,AnnotationAwareOrderComparator.INSTANCE); @H_301_30@ this.customizers = Collections.unmodifiableList(this.customizers); @H_301_30@ } @H_301_30@ return this.customizers; @H_301_30@} @H_301_30@ @H_301_30@ServerProperties也是定制器 |
6].步骤
1).SpringBoot根据导入的依赖情况,给容器中添加相应的
EmbeddedServletContainerFactory【TomcatEmbeddedServletContainerFactory】
2).容器中某个组件要创建对象就会惊动后置处理器
EmbeddedServletContainerCustomizerBeanPostProcessor;只要是嵌入式的Servlet容器工厂,后置处理器就工作;
3).后置处理器,从容器中获取所有的EmbeddedServletContainerCustomizer,调用定制器的定制方法
[2].SpringBoot2.0版本
(5).嵌入式Servlet容器启动原理
问题:
①.何时创建嵌入式的Servlet容器工厂
②.何时获取嵌入式的Servlet容器并启动Tomcat
[1].获取嵌入式的Servlet容器工厂
1].SpringBoot应用启动运行run方法
2].refreshContext
refreshContext(context);
SpringBoot刷新IOC容器【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;
@H_871_403@如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,否则:AnnotationConfigApplicationContext
3].refresh
@H_301_30@ refresh(context); @H_301_30@ refresh刷新刚才创建好的ioc容器;@H_301_30@public void refresh() throws BeansException,IllegalStateException { @H_301_30@ synchronized (this.startupShutdownMonitor) { @H_301_30@ // Prepare this context for refreshing. @H_301_30@ prepareRefresh(); @H_301_30@ @H_301_30@ // Tell the subclass to refresh the internal bean factory. @H_301_30@ ConfigurableListablebeanfactory beanfactory = obtainFreshbeanfactory(); @H_301_30@ @H_301_30@ // Prepare the bean factory for use in this context. @H_301_30@ preparebeanfactory(beanfactory); @H_301_30@ @H_301_30@ try { @H_301_30@ // Allows post-processing of the bean factory in context subclasses. @H_301_30@ postProcessbeanfactory(beanfactory); @H_301_30@ @H_301_30@ // Invoke factory processors registered as beans in the context. @H_301_30@ invokebeanfactoryPostProcessors(beanfactory); @H_301_30@ @H_301_30@ // Register bean processors that intercept bean creation. @H_301_30@ registerBeanPostProcessors(beanfactory); @H_301_30@ @H_301_30@ // Initialize message source for this context. @H_301_30@ initMessageSource(); @H_301_30@ @H_301_30@ // Initialize event multicaster for this context. @H_301_30@ initApplicationEventMulticaster(); @H_301_30@ @H_301_30@ // Initialize other special beans in specific context subclasses. @H_301_30@ onRefresh(); @H_301_30@ @H_301_30@ // Check for listener beans and register them. @H_301_30@ registerListeners(); @H_301_30@ @H_301_30@ // Instantiate all remaining (non-lazy-init) singletons. @H_301_30@ finishbeanfactoryInitialization(beanfactory); @H_301_30@ @H_301_30@ // Last step: publish corresponding event. @H_301_30@ finishRefresh(); @H_301_30@ } @H_301_30@ @H_301_30@ catch (BeansException ex) { @H_301_30@ if (logger.isWarnEnabled()) { @H_301_30@ logger.warn("Exception encountered during context initialization - " + @H_301_30@ "cancelling refresh attempt: " + ex); @H_301_30@ } @H_301_30@ @H_301_30@ // Destroy already created singletons to avoid dangling resources. @H_301_30@ destroyBeans(); @H_301_30@ @H_301_30@ // Reset 'active' flag. @H_301_30@ cancelRefresh(ex); @H_301_30@ @H_301_30@ // Propagate exception to caller. @H_301_30@ throw ex; @H_301_30@ } @H_301_30@ @H_301_30@ finally { @H_301_30@ // Reset common introspection caches in Spring's core,since we @H_301_30@ // might not ever need Metadata for singleton beans anymore... @H_301_30@ resetCommonCaches(); @H_301_30@ } @H_301_30@ } @H_301_30@} |
4].onRefresh
onRefresh()web的ioc容器重写了onRefresh方法
5].createEmbeddedServletContainer
webioc容器会创建嵌入式的Servlet容器;
createEmbeddedServletContainer();
@H_301_30@ |
6].获取嵌入式的Servlet容器工厂
@H_301_30@ EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory(); |
@H_871_403@从ioc容器中获取EmbeddedServletContainerFactory 组件;TomcatEmbeddedServletContainerFactory创建对象,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
7].使用容器工厂获取嵌入式的Servlet容器
@H_301_30@this.embeddedServletContainer = containerFactory.getEmbeddedServletContainer(getSelfInitializer()); |
8].嵌入式的Servlet容器创建对象并启动Servlet容器
@H_871_403@先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来
IOC容器启动创建嵌入式的Servlet容器
9.外置Servlet容器
@H_301_30@ @H_871_403@嵌入式Servlet容器:应用打成可执行的jar @H_301_30@ @H_871_403@嵌入式Servlet容器优点:简单、便携 @H_301_30@ @H_871_403@嵌入式Servlet容器缺点:默认不支持JSP、优化定制比较复杂(使用定制器[ServerProperties、自定义EmbeddedServletContainerCustomizer[,自己编写嵌入式Servlet容器的创建工厂[EmbeddedServletContainerFactory[); @H_301_30@ @H_871_403@外置式Servlet容器:外面安装Tomcat---应用war包的方式打包;(1).步骤
[1].创建war项目
@H_301_30@ @H_871_403@利用idea创建好目录结构[2].修改pom文件
将嵌入式的Tomcat指定为provided
@H_301_30@<dependency> @H_301_30@ <groupId>org.springframework.boot</groupId> @H_301_30@ <artifactId>spring-boot-starter-tomcat</artifactId> @H_301_30@ <scope>provided</scope> @H_301_30@</dependency> |
[3].SpringBootServletInitializer
编写一个SpringBootServletInitializer的子类,并调用configure方法
@H_301_30@public class ServletInitializer extends SpringBootServletInitializer { @H_301_30@ @H_301_30@ @Override @H_301_30@ protected SpringApplicationBuilder configure(SpringApplicationBuilder application) { @H_301_30@ //传入SpringBoot应用的主程序 @H_301_30@ return application.sources(SpringBoot04WebJspApplication.class); @H_301_30@ } @H_301_30@ @H_301_30@} |
[4].启动服务器
(2).原理
jar包:执行SpringBoot主类main方法,启动ioc容器,创建嵌入式Servlet容器;
@H_301_30@ war包:启动服务器,服务器启动SpringBoot应用[SpringBootServletInitializer],启动ioc容器; @H_301_30@[1].规则
1].服务器启动(web应用启动)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例
2].ServletContainerInitializer的实现放在jar包的Meta-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名
3].使用@HandlesTypes,在应用启动的时候加载需要的类;
[2].流程
1].启动Tomcat
2].org\springframework\spring-web\4.3.14.RELEASE\spring-web-4.3.14.RELEASE.jar!\Meta-INF\services\javax.servlet.ServletContainerInitializer:
Spring的web模块里面有这个文件:
org.springframework.web.SpringServletContainerInitializer
3].SpringServletContainerInitializer
将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;
4].每一个WebApplicationInitializer都调用自己的onStartup;
@H_301_30@5].相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法
@H_301_30@6].SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建容器
@H_301_30@protected WebApplicationContext createRootApplicationContext( @H_301_30@ ServletContext servletContext) { @H_301_30@ //1、创建SpringApplicationBuilder @H_301_30@ SpringApplicationBuilder builder = createSpringApplicationBuilder(); @H_301_30@ StandardServletEnvironment environment = new StandardServletEnvironment(); @H_301_30@ environment.initPropertySources(servletContext,null); @H_301_30@ builder.environment(environment); @H_301_30@ builder.main(getClass()); @H_301_30@ ApplicationContext parent = getExistingRootWebApplicationContext(servletContext); @H_301_30@ if (parent != null) { @H_301_30@ this.logger.info("Root context already created (using as parent)."); @H_301_30@ servletContext.setAttribute( @H_301_30@ WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,null); @H_301_30@ builder.initializers(new ParentContextApplicationContextInitializer(parent)); @H_301_30@ } @H_301_30@ builder.initializers( @H_301_30@ new ServletContextApplicationContextInitializer(servletContext)); @H_301_30@ builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class); @H_301_30@ @H_301_30@ //调用configure方法,子类重写了这个方法,将SpringBoot的主程序类传入了进来 @H_301_30@ builder = configure(builder); @H_301_30@ @H_301_30@ //使用builder创建一个Spring应用 @H_301_30@ SpringApplication application = builder.build(); @H_301_30@ if (application.getSources().isEmpty() && AnnotationUtils @H_301_30@ .findAnnotation(getClass(),Configuration.class) != null) { @H_301_30@ application.getSources().add(getClass()); @H_301_30@ } @H_301_30@ Assert.state(!application.getSources().isEmpty(), @H_301_30@ "No SpringApplication sources have been defined. Either override the " @H_301_30@ + "configure method or add an @Configuration annotation"); @H_301_30@ // Ensure error pages are registered @H_301_30@ if (this.registerErrorPageFilter) { @H_301_30@ application.getSources().add(ErrorPageFilterConfiguration.class); @H_301_30@ } @H_301_30@ //启动Spring应用 @H_301_30@ return run(application); @H_301_30@} |
7].Spring的应用就启动并且创建IOC容器
@H_301_30@public ConfigurableApplicationContext run(String... args) { @H_301_30@ StopWatch stopWatch = new StopWatch(); @H_301_30@ stopWatch.start(); @H_301_30@ ConfigurableApplicationContext context = null; @H_301_30@ FailureAnalyzers analyzers = null; @H_301_30@ configureHeadlessProperty(); @H_301_30@ SpringApplicationRunListeners listeners = getRunListeners(args); @H_301_30@ listeners.starting(); @H_301_30@ try { @H_301_30@ ApplicationArguments applicationArguments = new DefaultApplicationArguments( @H_301_30@ args); @H_301_30@ ConfigurableEnvironment environment = prepareEnvironment(listeners, @H_301_30@ applicationArguments); @H_301_30@ Banner printedBanner = printBanner(environment); @H_301_30@ context = createApplicationContext(); @H_301_30@ analyzers = new FailureAnalyzers(context); @H_301_30@ prepareContext(context,environment,listeners,applicationArguments, @H_301_30@ printedBanner); @H_301_30@ @H_301_30@ //刷新IOC容器 @H_301_30@ refreshContext(context); @H_301_30@ afterRefresh(context,applicationArguments); @H_301_30@ listeners.finished(context,null); @H_301_30@ stopWatch.stop(); @H_301_30@ if (this.logStartupInfo) { @H_301_30@ new StartupInfoLogger(this.mainApplicationClass) @H_301_30@ .logStarted(getApplicationLog(),stopWatch); @H_301_30@ } @H_301_30@ return context; @H_301_30@ } @H_301_30@ catch (Throwable ex) { @H_301_30@ handleRunFailure(context,analyzers,ex); @H_301_30@ throw new IllegalStateException(ex); @H_301_30@ } @H_301_30@} |
@H_871_403@重要:启动Servlet容器,再启动SpringBoot应用
参考文档
https://docs.spring.io/spring-boot/docs/1.5.9.RELEASE/reference/htmlsingle/#howto-use-thymeleaf-3
https://github.com/thymeleaf/thymeleaf/releases
https://github.com/ultraq/thymeleaf-layout-dialect/releases
https://www.thymeleaf.org/documentation.html