使用spring boot的开发流程:css
自动配置原理?html
请记住,饮水则思源,在每运用一个场景的时候,都要记住这是自动配置原理生效的。同时也要思考一下,spring-boot为咱们配置了什么?能不能修改?有哪些配置能够修改?是否能够扩展等。java
必定要记住:xxxAutoConfiguration帮咱们在容器中自动的配置相关组件,而其xxxProperties封装了配置文件的内容。react
查询类(ctrl+shift+n)WebMvcAutoConfiguration
查看其源码,与web相关的配置在此处均可以看到:jquery
@ConfigurationProperties( prefix = "spring.resources", ignoreUnknownFields = false ) public class ResourceProperties {
也就是说有关配置能够经过spring.resourcec进行设置。web
再来看spring
public void addResourceHandlers(ResourceHandlerRegistry registry) { if (!this.resourceProperties.isAddMappings()) { logger.debug("Default resource handling disabled"); } else { Duration cachePeriod = this.resourceProperties.getCache().getPeriod(); CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl(); if (!registry.hasMappingForPattern("/webjars/**")) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); } String staticPathPattern = this.mvcProperties.getStaticPathPattern(); if (!registry.hasMappingForPattern(staticPathPattern)) { this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); } } }
从该代码中咱们能够看到,全部/webjars/**
都去classpath:/META-INF/resources/webjars
中找资源;bootstrap
引入jquery:后端
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.0</version> </dependency>
咱们将下载到的jquery包展开能够看清楚目录结构,结合上面有关webjars的解释,咱们能够尝试经过访问127.0.0.1:8081/webjars/jquery/3.3.0/jquery.js,发现能够成功的访问到该静态资源。也就是说,打包后的项目的确是能够访问到的。数组
还有一种配置资源的方式,"/**"访问当前项目的任何资源:
也就说,咱们在上述文件夹上面放置静态资源,若是没有特别处理,系统默认都是能够访问到的。
继续查看该类的源码:
@Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext) { return new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern()); }
此处配置的是欢迎页的映射。他都是静态资源文件夹下的全部index.html文件。被/**
映射。 如localhost:8008则就是访问欢迎页面。
@Bean public SimpleUrlHandlerMapping faviconHandlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setOrder(-2147483647); mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", this.faviconRequestHandler())); return mapping; }
该处配置咱们喜欢的图标,即浏览器表标签页左方的小图标。仍是映射到了**.favicon.ico
,也就是咱们的全部静态文件夹下面。
从上面能够看出,系统默认用一个staticLocations
做为映射路径,该路径默认为刚才所列出的默认路径,若是要配置自定义的资源路径,则能够:
spring.resources.static-locations=classpath:/hello,classpath:/hello2
就能够了,但要注意以前的默认值就不生效了。
市面上比较经常使用的有JSP、Velocity、FreeMarker、以及Spring boot推荐的Thymeleaf。
虽然模板引擎不少,可是其核心思想都是同样的,即按照必定的语法标准,将你的数据转化为实际的html页面,他们的区别只在于语法。
spring boot推荐的themeleaf语法比较简单,并且功能更强大。
注意,若是是1.x版本的spring boot此处若想使用3.x版本的thymeleaf的话,请在properties配置节配置其版本号以及布局版本号,以覆盖SB中默认的低版本thymeleaf。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
查看spring-boot-autoconfigure
包能够查看到有关thymeleaf相关的配置信息ThymeleafAutoConfiguration
。其无非就是为thymeleaf添加相关的组件。咱们主要关注的是其相关的配置规则:ThymeleafProperties。
@ConfigurationProperties( prefix = "spring.thymeleaf" ) public class ThymeleafProperties { private static final Charset DEFAULT_ENCODING; public static final String DEFAULT_PREFIX = "classpath:/templates/"; public static final String DEFAULT_SUFFIX = ".html"; private boolean checkTemplate = true; private boolean checkTemplateLocation = true; private String prefix = "classpath:/templates/"; private String suffix = ".html"; private String mode = "HTML"; private Charset encoding; private boolean cache; private Integer templateResolverOrder; private String[] viewNames; private String[] excludedViewNames; private boolean enableSpringElCompiler; private boolean renderHiddenMarkersBeforeCheckboxes; private boolean enabled; private final ThymeleafProperties.Servlet servlet; private final ThymeleafProperties.Reactive reactive;
从默认规则里面咱们不难看出不少东西其实已经无需修改,就按该默认配置进行业务代码编写就好了。也就是从配置中能够看出,咱们的全部页面只须要放在classpath:/templates/
资源文件夹下面,并以.html
即为便可,根本无需其余多余的配置:
@RequestMapping("success") public String success(){ return "success"; }
该代码交给thymeleaf渲染template/success.html
的文件。
详细的官方文件请点击:参考文档
<html xmlns:th="http://www.thymeleaf.org">
@RequestMapping("success") public String success(Map<String, Object> map){ map.put("hello", "你好"); return "success"; }
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> this is success page. <p th:text="${hello}">这里显示默认信息。</p> </body> </html>
从输出结果能够看出,该页面彻底能够独立于出来直接运行,其输出的结果不通过渲染只不过只会输出
这里是默认信息
这样的字段而已,作到了很好的先后端分离。
改变当前元素的文本内容等,例如:th:html属性
其值均可以替换原生的元素对应的值。 相关的介绍参考官方文档;
参考文档Iteration
部分。
该标签用于遍历数组数据。须要注意的是,该标签写在那个标签上,那个标签都会在每次遍历中生成一次。由此咱们能够了解到,该标签常常会和表格行标签<tr>
搭配使用。
<h1>Product list</h1> <table> <tr> <th>NAME</th> <th>PRICE</th> <th>IN STOCK</th> </tr> <tr th:each="prod : ${prods}"> <td th:text="${prod.name}">Onions</td> <td th:text="${prod.price}">2.41</td> <td th:text="${prod.inStock}? #{true} : #{false}">yes</td> </tr> </table>
以上全部的片断均可以组合使用,例如:
'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))
该表达式用于获取变量的值,使用的是OGNL语法:
${session.user.id}
和${...}的功能是同样的,不过有一个补充使用,即配合<th:object>
使用,以下面的示例:
<div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="*{lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div>
其等价于:
<div> <p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p> </div>
也就是说,用*{}
能够省略公共的对象信息。固然,咱们也能够在循环体内部混合使用这两种语法:
<div th:object="${session.user}"> <p>Name: <span th:text="*{firstName}">Sebastian</span>.</p> <p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p> <p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p> </div>
该表达式用于获取国际化内容。
定义url。
<!-- Will produce 'http://localhost:8080/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a> <!-- Will produce '/gtvg/order/details?orderId=3' (plus rewriting) --> <a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a> <!-- Will produce '/gtvg/order/3/details' (plus rewriting) --> <a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>
若是须要添加多个参数的话,用逗号分隔便可。
@{/order/process(execId=${execId},execType='FAST')}
片断引用表达式。
<div th:insert="~{commons :: main}">...</div>
参考文档Inlining
部分。
不少时候,咱们想显示的文本实际上是单纯的文本节点,彻底不想使用html标签包裹,这时候须要怎么写呢。咱们显然不能这样写:
my name is ${$name}.
正确的写法应该使用行内表达式
my name is [[${name}]]!
关于行内式有两种标签写法:
[[...]]
等价于th:text
,会转义特殊字符,即按原样输出,<h1>
标签会格式化内部文字;[(...)]
等价于th:utext
,不会转义特殊字符,即按相应的标签格式输出,例如<h1>
标签直接输出为<h1>
。转义这个有点绕,不当心把本身也绕进去了。简单一点的记法:想要有html标签格式化文本,用
text
([[...]]
),想原样输出标签用utext
,。
Spring boot 自动配置好了Spring mvc,如下是SB对spring mvc的默认配置,这些配置组件均可以在WebMvcConfigurationSupport
类中查看相应源码:
自动配置了ViewResoulver(视图解析器:根据方法的返回值获得视图对象(View),视图对象决定如何渲染...转发、重定向)
ContentNegotiatingViewResolver
: 组合全部的视图解析器。Support for serving static resources, including support for WebJars (covered later in this document)): 静态资源文件夹路径,webjars.
Automatic registration of Converter, GenericConverter, and Formatter beans.
Support for HttpMessageConverters
(covered later in this document)
Automatic registration of MessageCodesResolver
(covered later in this document).
Static index.html
support.
Custom Favicon support (covered later in this document).
Automatic use of a ConfigurableWebBindingInitializer
bean (covered later in this document).
SB对web的自动配置其实不止这个类中作了,其实还有其余的以***AutoConfiguration的类,都对web场景进行了一些配置。
模式:给容器中添加对应的自定义组件就能够了。
咱们之前配置试图映射、拦截器均可以在springmvc.xml中进行配置,以下图所示:
```xml <mvc:view-controller path="/hello" view-name="success"/> <mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/hello"/> <bean></bean> </mvc:interceptor> </mvc:interceptors>
那在spring boot中该怎么作呢?
编写一个配置类(@Configuration
),是WebMvcConfigurerAdapter
类型;不能标注@EnableWebMvc
;
注意此处是1.x版本,2.x版本提示这个类准备移除了,咱们再也不须要继承
WebMvcConfigurerAdapter
,而是直接实现接口WebMvcConfigurer
来写配置类便可。
@Configuration public class MyConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 浏览器发送/restweb也会来到hello页面. registry.addViewController("/restweb").setViewName("hello"); } }
该方式即保留了spring boot的全部自动配置,也能用咱们的扩展配置。能够查看以下了解原理:
@import(EnableWebMvcConfiguration.class)
@Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } }
spring boot 会将全部的WebMvcConfiguration相关配置一块儿调用
这种状况下,咱们通常是不想要SpringBoot的全部配置,全部的都是由咱们本身来指定。只须要在自定义配置类中添加一个配置@EnableWebMvc
便可,这样以后全部的SpringMVC的自动配置都失效了。
@EnableWebMvc
原理:
@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { }
注意其中的DelegatingWebMvcConfiguration
,其源码以下:
@Configuration public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
咱们再来看自动配置类的源码:
@Configuration @ConditionalOnWebApplication( type = Type.SERVLET ) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration {
咱们能够看到这一句
@ConditionalOnMissingBean({WebMvcConfigurationSupport.class})
其生效方式恰好就是若容器中没有WebMvcConfigurationSupport
,因为前面的注解致使该组件导入了进来,所以自动配置类就不生效了。导入的WebMvcConfigurationSupport
只是SpringMVC最基本的功能。
使用SpringBoot通常不推荐SpringMVC全面接管(那就不必用SpringBoot了)
添加entities包以及dao包,引入测试用例类以及静态资源,这里参考原视频教程配套代码。 添加thymeleaf场景包,springboot会自动启用该场景:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
咱们已经将index.html放在了template文件夹下面,此时,咱们为了将index.html映射到网站的首页,通常会写以下的控制方法:
@RequestMapping({"/","index.html"}) public String index(){ return "index"; }
该控制器方法能够将请求路径"/","index.html"
都映射为index.html的资源信息(thymeleaf渲染),还能够经过在配置类中配置映射的方式完成上述效果:
@Override public void addViewControllers(ViewControllerRegistry registry) { // 浏览器发送/restweb也会来到hello页面. registry.addViewController("/").setViewName("index"); // registry.addViewController("/index.html").setViewName("index"); }
再有,咱们前面有讲到,springboot会将咱们生成的全部组件添加到相应的组件族中,所以,咱们这里也能够手动的建立一个WebMvcConfigurer,也一样生效(别忘了添加组件注解:@Bean
,将组件注册到容器中),上下两端代码完成的效果是一致的,固然也能够是互补的(映射不一样的话):
@Bean public WebMvcConfigurer myWebMvcConfigurer(){ return new WebMvcConfigurer() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/index").setViewName("index"); } }; }
<dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.3.1-1</version> </dependency> <dependency> <groupId>org.webjars</groupId> <artifactId>bootstrap</artifactId> <version>4.1.3</version> </dependency>
引入以后,则能够将以前的相对路径引入的静态资源替换掉了,找到index.html页面(改成login.html),使用th:xxx
修改路径引入:
<link th:href="@{/webjars/bootstrap/4.1.3/css/bootstrap.css}" href="asserts/css/bootstrap.min.css" rel="stylesheet">
<html xmlns:th="http://www.thymeleaf.org">
别忘了经过这个能够添加thymeleaf语法提示哦。
在springMVC中,咱们须要:
步骤:
/resource
目录下新建i18n文件夹ResourcebundleMessageSource
管理国际化资源文件,在spring boot中,则不须要了(他已经帮我配置好了)。查看类MessageSourceAutoConfiguration
中已经有该组件的配置了。@Configuration @ConditionalOnMissingBean( value = {MessageSource.class}, search = SearchStrategy.CURRENT ) @AutoConfigureOrder(-2147483648) @Conditional({MessageSourceAutoConfiguration.ResourceBundleCondition.class}) @EnableConfigurationProperties public class MessageSourceAutoConfiguration { @Bean @ConfigurationProperties( prefix = "spring.messages" ) public MessageSourceProperties messageSourceProperties() { return new MessageSourceProperties(); }
配置了组件
@Bean public MessageSource messageSource(MessageSourceProperties properties) { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); if (StringUtils.hasText(properties.getBasename())) { messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(StringUtils.trimAllWhitespace(properties.getBasename()))); }
咱们注意到这里
String basename = context.getEnvironment().getProperty("spring.messages.basename", "messages");
也就是说,咱们的配置文件能够直接放在类路径下叫messages.properties便可,可是这样作显然不是很好,所以咱们只须要修改spring.messages.basename
配置便可自定义国际化内容的存放位置。
spring.messages.basename=i18n/login
再次提醒,这里2.x版本. 1.x版本这里彷佛是不同的写法都行
i18n.login
#{...}
部分源码以下:
<input type="text" class="form-control" th:placeholder="#{login.username}" placeholder="Username" required="" autofocus="">
如今咱们想经过点击下方的中文``English
两个按钮切换国际化页面该怎么作呢?
国家化Locale(区域信息对象);LocaleResolver获取区域信息对象,在WebMvcAutoConfiguration
:
@Bean @ConditionalOnMissingBean @ConditionalOnProperty( prefix = "spring.mvc", name = {"locale"} ) public LocaleResolver localeResolver() { if (this.mvcProperties.getLocaleResolver() == org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties.LocaleResolver.FIXED) { return new FixedLocaleResolver(this.mvcProperties.getLocale()); } else { AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver(); localeResolver.setDefaultLocale(this.mvcProperties.getLocale()); return localeResolver; } }
默认的区域信息解析器就是根据请求头带来的区域信息进行的国际化。
Accept-Language: en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7
所以,咱们彻底能够本身写一个区域化信息解析器来本身处理请求信息。
<a class="btn btn-sm" th:href="@{/index.html(l='zh_CN')}">中文</a> <a class="btn btn-sm" th:href="@{/index.html(l='en_US')}">English</a>
别忘了添加该组件:
@Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); }
注意此处方法名必须写为
localResolver
,由于Spring会默认将其做为Bean的名字,而SB的默认区域解析器会以此做为是否生成的条件。
模板殷勤修改之后,若想实时生效,须要作以下操做:
(1) 禁用模板引擎缓存(开发过程当中建议关闭,生产环境可开启)
spring.thymeleaf.cache=false
(2)idea编译器的话,按ctrl + F9,从新编译一下便可。
#工具.工具方法 if标签的优先级比text高
<p style="color:red" th:text="{msg}" th:if="${not #strings.isEmpty(msg)}"></p>
return "redirect:/main.html"
经过进行登陆检查,即实现SpringMVC中的HandlerIntercepter
拦截方法.
@Controller public class LoginController { @PostMapping(value = {"/user/login"}) public String login(@RequestParam("username")String username, @RequestParam("password") String password, HttpSession session, Map<String, Object> map){ if(!StringUtils.isEmpty(username) && "123456".equals(password)){ session.setAttribute("loginUser","admin"); return "redirect:/index"; }else{ map.put("msg", "用户名或密码错误"); return "login"; } } }
其中:
@PostMapping(value = {"/user/login","login"})
等价于 @RequestMapping(value={"/user/login","login"}, method = RequestMethod.POST)
;@RequestParam("username")
强调该请求必须接受一个名为username的参数,不然会报400错误,并提示要求的参数不存在。index
给模板引擎解析,显然当前是转发user/login请求,若是咱们刷新页面,会出现表单的重复提交,而且,若是index.html中若是写的是相对路径,则其路径会误认为user是一个项目名称,致使资源所有引入失败。所以,咱们须要将其重定向。return "redirect:/index";
@Override public void addViewControllers(ViewControllerRegistry registry) { // 浏览器发送/restweb也会来到hello页面. registry.addViewController("/login").setViewName("login"); registry.addViewController("/").setViewName("index"); registry.addViewController("/index").setViewName("index"); }
其中:
registry.addViewController("/login").setViewName("login");
就等价于咱们写一个Controller进行映射:
@RequestMapping(value="/login") public String login(){ return "login"; }
所以,若是只是单纯的映射跳转而没有其余的业务逻辑的话,推荐在这里直接配置便可。
首先,咱们要编写一个拦截器组件:
public class LoginHandlerInterupter implements HandlerInterceptor { /** * 其余的方法因为java8的default,咱们能够不实现了,专心写这部分代码进行登陆检查便可 * @param request * @param response * @param handler * @return * @throws Exception */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object loginUser = request.getSession().getAttribute("loginUser"); // 若登陆,放行请求 if(loginUser != null){ return true; } request.setAttribute("msg","未登陆,请先登陆"); System.out.println("this is is a preHandler method."); request.getRequestDispatcher("/login").forward(request, response); return false; } }
注意:
HandlerInterceptor
接口,经过查看该接口的源码能够发现:default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; }
其全部的方法都用了java8新关键字default
修饰了全部的接口方法,所以,在咱们的实现类中,无需实现全部的方法(若是不了解的话,请参考java8相关书籍——推荐 java8实战 ,大概就是default修饰的方法不须要实现类实现了,挑本身想覆盖的方法覆盖便可。)
添加拦截器组件到容器中,并配置相关的参数:
/** * 添加自定义拦截器到容器中,并配置相关参数 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // 视频中老师有讲到spring boot已经对静态资源作了映射,不过他是针对1.x版本这样解释,可是我2.0版本这里是不行的,仍是得放行静态资源,因此加了/assert和webjars这两个静态资源路径的放行,否则静态资源仍是会被拦截下来。请注意一下。 registry.addInterceptor(new LoginHandlerInterupter()).addPathPatterns("/**") . excludePathPatterns("/login","/user/login","/asserts/**","/webjars/**"); }
WebMvcConfigurer
的配置类中,目前其彻底代码以下所示:@Configuration public class MyConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 浏览器发送/restweb也会来到hello页面. registry.addViewController("/login").setViewName("login"); registry.addViewController("/").setViewName("index"); registry.addViewController("/index").setViewName("index"); } /** * 添加自定义拦截器到容器中,并配置相关参数 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { // SB已经对静态资源 *.css , *.jss进行了映射,咱们能够不加管理 registry.addInterceptor(new LoginHandlerInterupter()).addPathPatterns("/**") . excludePathPatterns("/login","/user/login","/asserts/**","/webjars/**"); } /** * 自定义本地解析器 * @return */ @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); }
能够看到,该配置类实现WebMvcConfigurer
接口,用于修改spring mvc相关的默认配置,好比这里添加了视图控制器、拦截器,另外则是一个以前为了作本地化配置的自定义本地化解析器。
addPathPatterns("/**")
代表该拦截器会拦截全部的请求.excludePathPatterns("/login","/user/login")
代表"/login","/user/login","/asserts/**","/webjars/**"
这四个请求,是特殊的、不拦截的,注意后两个是资源文件入口。测试运行正常的登陆,一切运行正常。