今天,学习Springboot中的SpringMVC的自动配置原理
Spring MVC auto-configuration
Spring Boot 自动配置好了SpringMVC
以下是SpringBoot对SpringMVC的默认配置:(WebMvcAutoConfiguration)
ContentNegotiatingViewResolver and BeanNameViewResolver
@Bean
@ConditionalOnBean(View.class)
@ConditionalOnMissingBean
public BeanNameViewResolver beanNameViewResolver() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 10);
return resolver;
}
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(
beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver uses all the other view resolvers to locate
// a view so it should have a high precedence
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
1) 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象(View),视图对象决定如何
渲染(转发?重定向?))
2) ContentNegotiatingViewResolver:组合所有的视图解析器的;
3) 如何定制:我们可以自己给容器中添加一个视图解析器;ContentNegotiatingViewResolver会自动将其组合进来;
Support for serving static resources, including support for WebJars
静态资源文件夹路径,webjars(上一天,我们已经学过了)
Static index.html support
静态首页index.html访问.
Custom Favicon support
favicon.ico图标的访问。
自动注册了 Converter、GenericConverter、Formatter beans.
1)Converter:转换器; public String hello(User user):类型转换使用Converter。
2)Formatter:格式化器; 2017.12.17===Date。
@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date‐format")//在文件中配置日期格式化的规则
public Formatter<Date> dateFormatter() {
return new DateFormatter(this.mvcProperties.getDateFormat());//日期格式化组件
}
3)自己添加的格式化器转换器,我们只需要放在容器中即可.
HttpMessageConverters
1) HttpMessageConverter: SpringMVC用来转换Http请求和响应的;User—Json;
2) HttpMessageConverters:是从容器中获取所有的HttpMessageConverter;
3) 自己给容器中添加HttpMessageConverter,只需要将自己的组件注册容器中(@Bean,@Component)
@Configuration
public class MyConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = ...
HttpMessageConverter<?> another = ...
return new HttpMessageConverters(additional, another);//多参数
}
}
MessageCodesResolver
定义错误代码生成规则 (参考官方文档)
ConfigurableWebBindingInitializer
1)数据绑定的功能
2)我们可以配置一个ConfigurableWebBindingInitializer来替换默认的;(添加到容器)
初始化WebDataBinder;
请求数据=====JavaBean;
总结
org.springframework.boot.autoconfigure.web:web的所有自动场景;如果,你想使用Springboot的默认配置的Web功能,然后添加自己额外的Web组件,就:添加一个@Configuration的配置类,然后继承WebMvcConfigurerAdapter抽象类。(不能添加@EnableWebMvc注解)
如果,你不想使用Springboot的默认Web功能,那么,就:添加一个@Configuration的配置类,并且加上@EnableWebMvc注解,那么就你完全自定义SpringMVC组件。
官网文档中,这么说明的:
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 type
WebMvcConfigurerAdapter , but without @EnableWebMvc . If you wish to provide custom instances of
RequestMappingHandlerMapping , RequestMappingHandlerAdapter or ExceptionHandlerExceptionResolver
you can declare a WebMvcRegistrationsAdapter instance providing such components.
If you want to take complete control of Spring MVC, you can add your own @Configuration annotated with
@EnableWebMvc .
扩展SpringMVC
我们以前开发SpringMVC的时候,都会在xml中配置如下设置(自定义配置):
<mvc:view‐controller path="/hello" view‐name="success"/>
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/hello"/>
<bean></bean>
</mvc:interceptor>
</mvc:interceptors>
那么现在呢?
编写一个配置类(@Configuration),继承WebMvcConfigurerAdapter类型;不能标注@EnableWebMvc;既保留了所有的Springboot自动配置,也能用我们扩展的配置;
在我们的昨天的项目中,在com.liuzhuo.springboot包下,创建config包,并且创建Myconfig类:
@Configuration
public class Myconfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//super.addViewControllers(registry);
//浏览器发送 /liuzhuo 请求来到 success
registry.addViewController("/liuzhuo").setViewName("sucess");
}
}
然后在resources下的templates中:创建sucess.html文件:
启动Springboot应用:
在浏览器中输入:http://localhost:8080/liuzhuo
说明:我们自己配置的映射url成功。
此时,我们不仅可以使用Springboot的默认Web的配置,还能使用自己额外添加的控制器映射。
为什么,此时我们既能使用Springboot的默认配置,又能使用自己的配置呢?
原理:
1)查看WebMvcAutoConfiguration是SpringMVC的自动配置类
2)在WebMvcAutoConfiguration中,能发现一个静态内部类:WebMvcAutoConfigurationAdapter
// Defined as a nested config to ensure WebMvcConfigurerAdapter is not read when not
// on the classpath
@Configuration
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class, ResourceProperties.class })
public static class WebMvcAutoConfigurationAdapter extends WebMvcConfigurerAdapter {
该类上面有一个注解:@Import(EnableWebMvcConfiguration.class)。说明导入了EnableWebMvcConfiguration类。
3)打开EnableWebMvcConfiguration类:
@Configuration
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration
继承了DelegatingWebMvcConfiguration类,打开DelegatingWebMvcConfiguration类:
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
//从容器中获取所有的WebMvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
//一个参考实现;将所有的WebMvcConfigurer相关配置都来一起调用;
@Override
// public void addViewControllers(ViewControllerRegistry registry) {
// for (WebMvcConfigurer delegate : this.delegates) {
// delegate.addViewControllers(registry);
// }
}
}
}
4) 容器中所有的WebMvcConfigurer都会一起起作用
5) 我们的配置类也会被调用
效果:SpringMVC的自动配置和我们的扩展配置都会起作用
全面接管SpringMVC
SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置;所有的SpringMVC的自动配置都失效了
我们需要在配置类中添加@EnableWebMvc即可
//使用WebMvcConfigurerAdapter可以来扩展SpringMVC的功能
@EnableWebMvc
@Configuration
public class MyMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// super.addViewControllers(registry);
//浏览器发送 /atguigu 请求来到 success
registry.addViewController("/atguigu").setViewName("success");
}
}
直接启动我们的应用:
在浏览器中输入:http://localhost:8080/
默认的静态主页失效了。去掉@EnableWebMvc,静态主页映射就会成功。
静态主页映射:META-INF/resources、resourcs、static、public下的 index.html 都会映射:/**
为啥配置了@EnableWebMvc注解,SpringBoot的默认配置会失效呢?
原理:
1)打开@EnableWebMvc注解:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
导入了DelegatingWebMvcConfiguration类。
2)打开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);
}
}
···
}
DelegatingWebMvcConfiguration类:配置了SpringMVC的基本设置。
3)打开WebMvcAutoConfiguration:
@Configuration
@ConditionalOnWebApplication
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class,
WebMvcConfigurerAdapter.class })
//容器中没有这个组件的时候,这个自动配置类才生效
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
上面的一个条件注解:@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
当没有WebMvcConfigurationSupport存在时,WebMvcAutoConfiguration自动配置才会生效。
而DelegatingWebMvcConfiguration就是WebMvcConfigurationSupport。
所以@EnableWebMvc将WebMvcConfigurationSupport组件导入进来,从而使WebMvcAutoConfiguration自动配置失效。
总结:
推荐我们使用 SpringBoot的默认配置 + 自定义的配置,即:@Configuration + 继承WebMvcConfigurerAdapter 的配置类的形式。
我们完全自定义的模式:适合于简单、不负责的Web应用。
如何修改SpringBoot的默认配置
模式:
1)、 SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)
如果有就用用户配置的,如果没有,才自动配置;如果有些组件可以有多个(ViewResolver), 则将用户
配置的和自己默认的组合起来;
2)、在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
3)、在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置
CRUD-restful实战
下载完毕后。找到文档中的restful-crud-实验
默认访问首页
1)首先将目录下的静态页面,xxx.html导入到我们的项目中的template目录下:
2)asserts目录放到static目录下:
3) 将dao、entities放到com.liuzhuo.springboot包下:
然后点击DepartmentDao、EmployeeDao,重写导入我们的Department、Employee的包名。
4)启动应用:
在浏览器中输入:http://localhost:8080/
发现出现的页面是:static下的index.html页面,而不是template下的index.html页面。
因为静态主页会加载:META-INF/resources、resourcs、static、public下的 index.html
此时,我们不必要重新映射我们的主页,在我们的config下的Myconfig配置中:
@Configuration
public class Myconfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//super.addViewControllers(registry);
registry.addViewController("/liuzhuo").setViewName("sucess");
//添加主页的映射
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
registry.addViewController("/login.html").setViewName("login");
}
}
然后,将templates下的index.html 改为 login.html
重新启动应用:
在浏览器中输入:http://localhost:8080/
http://localhost:8080/login.html
http://localhost:8080/index.html
都是以下的页面:
5)因为我们使用 thymeleaf,所以需要在每个页面头部添加命名空间:
xmlns:th="http://www.thymeleaf.org"
<html lang="en" xmlns:th="http://www.thymeleaf.org" >
6) 在我们的静态页面中使用了bootstrap,所以需要bootstrap的webjar:
<!--bootstrap的jar包-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>bootstrap</artifactId>
<version>4.0.0</version>
</dependency>
在我们的login.html页面的中。找到引用bootstrap的地方:
th:href=”@{/webjars/bootstrap/4.0.0/css/bootstrap.css}”
<!-- Bootstrap core CSS -->
<link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.0.0/css/bootstrap.css}" rel="stylesheet">
查看有么有配置对?ctrl + 鼠标左键:能跳转到该文件处。
使用thymeleaf的th:href=”@{}”的好处:当我们给项目添加根目录时,也能自动帮我们添加上跟目录。
在application.properties中:
server.context-path=/curd
启动应用:
浏览器中输入:http://localhost:8080/
输入:http://localhost:8080/curd
右键审查元素:
能看到:link 中 href:自动帮我们添加上了curd的根目录
7)修改我们的css配置路径:
在login.html中:
th:href=”@{/asserts/css/signin.css}”
<!-- Custom styles for this template -->
<link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet">
页面中的其他引入资源,依次类推改写完毕即可。
国际化
以前,我们在SpringMVC中编写国际化时的步骤:
1)编写国际化配置文件
2)使用ResourceBundleMessageSource管理国际化资源文件
3) 在页面使用fmt:message取出国际化内容(jsp引擎)
现在,使用Springboot开发的国际化的步骤:
1) 编写国际化配置文件,抽取页面需要显示的国际化消息
在resources下创建i18n文件来放置我们的国际化的配置文件。(国际化文件只能是properties)
然后创建login.properties(默认的国际化配置)、login_en_US.properties。
idea会帮我们自动生成 Resource Bundle ‘login’ 文件夹:
然后,我们在 Resource Bundle ‘login’ 上面右键:new
点击加号:
填写: 语言_国家(en_US)
最后生成的效果:有三个国家化的配置文件:
填写我们需要国际化的部分:
观察login页面,发现,我们需要五次国家化的部分。
login.tip (登入的标题)
login.username (用户名)
login.password (密码)
login.remembear (记住密码)
login.bt (登入按钮)
然后,随便点击一个国家化文件。在下角处切换视图:Resource Bund
点击在该视图模式下的,左上角的加号:
填写key:
填写value:
依次类推:把
login.username (用户名)
login.password (密码)
login.remembear (记住密码)
login.bt (登入按钮)
添加上。
2) SpringBoot自动配置好了管理国际化资源文件的组件(MessageSourceAutoConfiguration)
@ConfigurationProperties(prefix = "spring.messages")
public class MessageSourceAutoConfiguration {
/**
* Comma‐separated list of basenames (essentially a fully‐qualified classpath
* location), each following the ResourceBundle convention with relaxed support for
* slash based locations. If it doesn't contain a package qualifier (such as
* "org.mypackage"), it will be resolved from the classpath root.
*/
private String basename = "messages";
//我们的配置文件可以直接放在类路径下叫messages.properties;
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
if (StringUtils.hasText(this.basename)) {
//设置国际化资源文件的基础名(去掉语言国家代码的)
messageSource.setBasenames(StringUtils.commaDelimitedListToStringArray(
StringUtils.trimAllWhitespace(this.basename)));
}
if (this.encoding != null) {
messageSource.setDefaultEncoding(this.encoding.name());
}
messageSource.setFallbackToSystemLocale(this.fallbackToSystemLocale);
messageSource.setCacheSeconds(this.cacheSeconds);
messageSource.setAlwaysUseMessageFormat(this.alwaysUseMessageFormat);
return messageSource;
}
如果,我们的国际化文件的名字就是messages的话,而放在类路径下的话,我们直接就可以使用了。
但是,现在我们使用了i18n文件,所以,我们需要在application.properties配置文件中:配置国际化的信息:
spring.messages.basename=i18n.login
i18n文件夹,login是文件的名字。
3)修改login.html中的需要国际化的部分
我们从thymeleaf的官方文档中,看到了如果使用国际化的话,使用 #{}
4)启动应用:
切换我们的地域语言:打开浏览器的设置(我使用的谷歌浏览器)
刷新浏览器:
以上为止,我们的国际化就搞定成功了。
效果:根据浏览器语言设置的信息切换了国际化。
原理:
国际化Locale(区域信息对象);LocaleResolver(获取区域信息对象);
打开WebMvcAutoConfiguration类:寻找与国际化有关的类:
@Bean
@ConditionalOnMissingBean
@ConditionalOnProperty(prefix = "spring.mvc", name = "locale")
public LocaleResolver localeResolver() {
if (this.mvcProperties
.getLocaleResolver() == WebMvcProperties.LocaleResolver.FIXED) {
return new FixedLocaleResolver(this.mvcProperties.getLocale());
}
AcceptHeaderLocaleResolver localeResolver = new AcceptHeaderLocaleResolver();
localeResolver.setDefaultLocale(this.mvcProperties.getLocale());
return localeResolver;
}
通过此类:我们能发现,Springboot给我们配置的默认的国家化Locale的是一个AcceptHeaderLocaleResolver。
AcceptHeaderLocaleResolver:是通过每次请求时,在请求头在获取请求的语言来进行国际化识别的。
启动我们的应用:
我们发现了Accept-Language:zh-CN.
修改我们的浏览器的语言:
再次访问我们的登入页面:
我们发现了Accept-Language:en-US.
这就是Springboot的默认的国际化原理。
我们想要自己自定义国际化配置,该怎么办呢?
Springboot的默认国际化:localeResolver上面有一个注解:@ConditionalOnMissingBean
意思就是:当我们的容器中不存在localeResolver,才会使用默认的AcceptHeaderLocaleResolver。
所以,我们只需要向容器中,添加我们的localeResolver即可,默认的localeResolver就会失效。
1)在component包下:创建MyLocaleResolver类(实现LocaleResolver):
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
//获取请求的参数信息
String language = request.getParameter("language");
//获取默认的地域信息。
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(language)) {
String[] split = language.split("_");
//第一个参数:语言信息
//第二个参数:国家信息
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) {
}
}
2)将我们的LocaleResolver添加到容器中:
在Myconfig类中:
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
3)修改login.html页面:
4)启动应用:
点击底下的:中文
点击底下的:English
我们自定义的国际化解析器成功!
发现:在此时,不管我们将浏览器中的语言设置什么,我们的默认登入都是中文的
因为,此时:我们使用的是自己的LocaleResovler:
当获取的language:String language = request.getParameter(“language”);
为空时,我们的Locale locale = Locale.getDefault();是获取的是本地的,我们这里就是中文。
不再是使用请求头中的那种方式了。
登陆
1)修改我们的登入页面:login.html
<form class="form-signin" action="dashboard.html" th:action="@{/user/login}" method="post">
登入的action改为:/user/login。请求方式:post。
2)在controller包下,创建LoginController:
@Controller
public class LoginController {
//@RequestMapping(value = "/user/login",method = RequestMethod.POST)
@PostMapping(value = "/user/login")
public String login(@RequestParam("username") String username,
@RequestParam("password") String password,
Map<String, Object> map) {
if (!StringUtils.isEmpty(username) &&
!StringUtils.isEmpty(password) &&
"admin".equals(username) &&
"123456".equals(password)) {
//登入成功!
return "dashboard";
} else {
//登入失败!
map.put("msg", "登入用户名或密码错误!");
return "login";
}
}
}
3) 启动项目:
输入用户名和密码
报错了:
原来是我们的login.html页面,没有给username和password添加name属性!
现在添加上:
重启项目,并添加正确的用户名和密码(admin,123456)
注意:
1)此时有可能还是刚刚的错误,因为thymeleaf默认是开启缓存功能的,所以我们开发的时候,要记住关闭缓存的功能
在配置文件中:
spring.thymeleaf.cache=false
2)我们在项目已经启动的时候,修改页面后,直接刷新页面,还是不会变的,要在修改后的页面处:ctrl+F9(重新编译页面)
使用(1)和(2)之后,就可以在项目已经启动的时候,直接修改页面也能得到最新的体验了。
现在,我们输入错误的用户名和密码:不会出现错误的信息。
怎么添加错误的信息在页面上呢?
给login.html页面添加一个
标签
当msg不为空时,才会出现错误的<p>标签。
重启项目:输入错误的用户名和密码
现在错误提示也完成了,再次输入正确的用户名和密码,现在一不小心按了F5,刷新了页面
发现出现了是否重新提交表单的情况,因为我们的后端是转发到登入成功页面的,地址栏还是之前的登入页面的地址,所以刷新会出现这种情况。
现在修改后端的代码,将其改为重定向。
在LoginController中:
在Myconfig中:
重启我们的应用:输入正确的用户名和密码
什么?404?main.html没有找到?why?
当我们看到地址栏时,发现我们重定向时:是在/user下重定向的,所以找不到main.html。
所以,在控制器Controller中:return "redirect:main.html";
main的前面不加/
的话
会在@PostMapping(value = “/user/login”)中去掉最后一个路径下重定向,即在/user下重定向。
如果是@PostMapping(value = “/user/liuzhuo/login”)的话,就在/user/liuzhuo下重定向。
怎样在我们的根路径下重定向呢?即在我们的/crud
根路径下重定向。只需在main的前面加/
即可:return "redirect:/main.html";
修改完毕后,重启我们的应用:输入正确的用户名和密码:
发现样式也正确了。因为是重定向而来的。此时刷新页面,也不会重发请求了。
拦截器进行登陆检查
此时,我们在另一个浏览器中:直接输入http://localhost:8080/curd/main.html
.
发现,没有登入就直接进来了,这是因为没有加拦截器的缘故。
1)在component包下:创建 LoginHandleInterceptor :
public class LoginHandleInterceptor implements HandlerInterceptor {
//在方法执行之前,调用。
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//从session中获取是否有loginUser属性的值
Object loginUser = request.getSession().getAttribute("loginUser");
if (StringUtils.isEmpty(loginUser)) {
//没有成功登入过。
//转发登入页面
request.getRequestDispatcher("/login.html").forward(request, response);
return false;
} else {
//成功登入过
return true;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
2)将我们自己定义的拦截器添加到拦截器链中:
在Myconfig类中:
@Override
public void addInterceptors(InterceptorRegistry registry) {
//super.addInterceptors(registry);
//添加拦截器我们自定义的拦截器
registry.addInterceptor(new LoginHandleInterceptor())
.addPathPatterns("/**") //拦截路径:/**:拦截所有的请求
.excludePathPatterns("/","/index.html","/login.html","/user/login");//排除登入和登入请求的拦截路径
}
这里虽然是拦截了所有的请求(/**),但是静态资源是不会被拦截的,Springboot已经帮我们排除掉了,所以放心使用.
3) 在我们的登入控制器LoginController中添加session的操作:
if (!StringUtils.isEmpty(username) &&
!StringUtils.isEmpty(password) &&
"admin".equals(username) &&
"123456".equals(password)) {
//登入成功!
//将用户名放入到session当中
session.setAttribute("loginUser", username);
//重定向到我们指定的登入成功的页面
return "redirect:/main.html";
4) 重启我们的应用。
在谷歌浏览器中,成功登入成功后,直接输入:http://localhost:8080/curd/main.html
发现,可以直接直接访问,因为已经成功登入过了,在session中已经保存了我们的用户信息。
在另外的浏览器中,直接输入:http://localhost:8080/curd/main.html
会转发到我们的登入页面,因为是转发,所以地址栏不变。
CRUD-员工列表
实验要求:
1) RestfulCRUD:CRUD满足Rest风格
URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
2) 实验的请求架构
3) 员工列表:
thymeleaf公共页面元素抽取
1、抽取公共片段
<div th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</div>
2、引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名::选择器
~{templatename::fragmentname}:模板名::片段名
模板名就是:aaa.html中的aaa
片段名就是:th:fragment="bbb"中的bbb
选择器就是:id选择器,class选择器等
3、默认效果:
insert的公共片段在div标签中
如果使用th:insert等属性进行引入,可以不用写~{}:
行内写法必须加上:[[~{}]] :不转义特殊字符(/n) 、 [(~{})] :转义特殊字符
三种引入公共片段的th属性:
th:insert:将公共片段整个插入到声明引入的元素中
th:replace:将声明引入的元素替换为公共片段
th:include:将被引入的片段的内容包含进这个标签中
<footer th:fragment="copy">
© 2011 The Good Thymes Virtual Grocery
</footer>
引入方式:
<div th:insert="footer :: copy"></div>
<div th:replace="footer :: copy"></div>
<div th:include="footer :: copy"></div>
效果:
<div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
</div>
<footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
<div>
© 2011 The Good Thymes Virtual Grocery
</div>
1) 将登入成功后的dashboard.html页面中的顶部和左边的侧单栏抽取出来:
在templates下:创建page文件夹,并创建两个html页面:
top.html页面:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<nav th:fragment="top" class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0">
<a class="navbar-brand col-sm-3 col-md-2 mr-0" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">[[${session.loginUser}]]</a>
<input class="form-control form-control-dark w-100" type="text" placeholder="Search" aria-label="Search">
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap">
<a class="nav-link" href="http://getbootstrap.com/docs/4.0/examples/dashboard/#">退出</a>
</li>
</ul>
</nav>
</body>
</html>
主要是其中的<nav></nav>
片段,这个片段是dashboard.html中的顶部的片段复制过来的。
其中主要是在<nav>
中加入了th:fragment=”top”属性
slide.html页面:
其中的<nav>
片段:是dashboard.html中的左边的侧单栏的部分复制过来的。
其中,主要加入了:id:slide 的属性。
2)修改dashboard.html页面的顶部与左边部分:
3)启动应用,运行发现顶部与左部运行完好。
现在,我们开始完成员工信息的部分;即:左单栏的Customer部分。
1)将templates下的list.html页面放到empl目录下,这样分文件夹管理更加合理
2)修改dashboard.html中的Customer部分的action属性:
因为左部已经抽取到slide.html页面,所以到slide.html页面中修改:
修改之前:
修改之后:将Customer改为员工信息,修改a标签的href
3)controller包下,创建EmployeeController:
@Controller
public class EmployeeController {
@Autowired
private EmployeeDao employeeDao;
@GetMapping("/emp")
public String list(Model model) {
Collection<Employee> employees = employeeDao.getAll();
model.addAttribute("employees", employees);
return "empl/list";
}
}
4) 启动应用
点击:员工信息
此时员工信息里面还是Customer,因为没有抽取出公共部分。
修改list.html的顶部与左部,与dashboard.html类似。自己完成。
完成后的效果:
但是,我们发现此时左部的高亮部分还是Dashboard
5)怎么修改高亮为员工信息呢?
thymeleaf模板引擎中,查看官方文档,发现可以有参数化的片段布局:8.2 Parameterizable fragment signatures
官方参考
引入片段的时候传入参数:
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
即:就是在我们引入的同时,在后面加一个括号,里面填写:key=value。
修改dashboard.html页面:
修改list.html页面:
修改slide.html页面:
在Dashboard处:
在员工信息处:
重启我们的应用:
点击Dashboard,Dashboard高亮。
点击员工信息,员工信息高亮。
6)将员工信息换成我们的后端传递过来的数据:
在list页面中:
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<h2><button class="btn btn-sm btn-success">员工添加</button></h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th>id</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="empl:${employees}">
<td th:text="${empl.id}">1001</td>
<td>[[${empl.lastName}]]</td>
<td th:text="${empl.email}">5589584@qq.com</td>
<td th:text="${empl.gender}=='0'?'女':'男'">男</td>
<td th:text="${empl.department.departmentName}">商业提升部</td>
<td th:text="${#dates.format(empl.birth, 'yyyy-MM-dd HH:mm:ss')}">2018/11/1</td>
<td>
<button class="btn btn-sm btn-primary">编辑</button>
<button class="btn btn-sm btn-danger">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</main>
ctrl+F9:
员工列表的添加
首先将员工信息的url换成 /empls :代表获取所有的员工信息,而 /empl :代表添加员工的信息。
1.将员工添加的按钮换成a标签:
2.在EmployeeController中:
@Autowired
private DepartmentDao departmentDao;
@GetMapping("/empl")
public String add(Model model) {
//获取所有的部门信息:
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
return "empl/employee";
}
3.在templates下的empl中添加employee.html页面:
首先,直接复制list.html为employee.html。
然后,修改其中的main标签部分:
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form>
<div class="form‐group">
<label>LastName</label>
<input type="text" class="form-control" placeholder="zhangsan">
</div>
<br>
<div class="form‐group">
<label>Email</label>
<input type="email" class="form-control" placeholder="zhangsan@atguigu.com">
</div>
<br>
<div class="form‐group">
<label>Gender</label><br/>
<div class="form‐check form‐check‐inline">
<input class="form‐check‐input" type="radio" name="gender" value="1">
<label class="form‐check‐label">男</label>
</div>
<div class="form‐check form‐check‐inline">
<input class="form‐check‐input" type="radio" name="gender" value="0">
<label class="form‐check‐label">女</label>
</div>
</div>
<br>
<div class="form‐group">
<label>department</label>
<select class="form-control">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<br>
<div class="form‐group">
<label>Birth</label>
<input type="text" class="form-control" placeholder="zhangsan">
</div>
<br>
<button type="submit" class="btn btn-sm btn-primary">添加</button>
</form>
</main>
4.重启应用:
5.此时的department部分还是死数据:
<div class="form‐group">
<label>department</label>
<select class="form-control">
<option th:value="${dept.id}" th:text="${dept.departmentName}" th:each="dept:${departments}">1</option>
</select>
</div>
刷新页面:
6.给form表单添加action和method属性:
<form th:action="@{/empl}" method="post">
7.书写添加员工的控制器
//添加员工
@PostMapping("/empl")
public String addEmployee(Employee employee) {
System.out.println(employee);
//添加员工
employeeDao.save(employee);
return "redirect:/empls";
}
8.重启应用,点击添加:
添加失败?因为我们的表单里面木有给属性添加name。
现在全部加上。
<input type="text" th:name="lastName" class="form-control" placeholder="zhangsan">
<input type="email" th:name="email" class="form-control" placeholder="zhangsan@atguigu.com">
<select class="form-control" th:name="department.id">
<input type="text" th:name="birth" class="form-control" placeholder="zhangsan">
然后再次尝试添加:
添加成功!!!
注意:这里填写生日的格式必须是 xxxx/xx/xx 的形式。其他形式就会出错,因为Springboot默认格式化的日期格式就是xxxx/xx/xx 的形式
如果,我们想改日期的格式呢?
打开WebMvcAutoConfiguration类:
@Bean
@ConditionalOnProperty(prefix = "spring.mvc", name = "date-format")
public Formatter<Date> dateFormatter() {
return new DateFormatter(this.mvcProperties.getDateFormat());
}
默认帮我们配置好的时间格式转化器。
点击:getDateFormat():
public String getDateFormat() {
return this.dateFormat;
}
再点击:this.dateFormat
/**
* Date format to use (e.g. dd/MM/yyyy).
*/
private String dateFormat;
说明默认的确实是:dd/MM/yyyy的形式。
想要修改默认的格式:只需要在application.properties文件中修改spring.mvc.date-format=xxxx即可:
#修改日期的格式
spring.mvc.date-format=yyyy-MM-dd
员工列表的编辑
修改添加二合一表单
1)首先修改编辑按钮:
<a th:href="@{/empl/}+${empl.id}" class="btn btn-sm btn-primary">编辑</a>
2) EmployeeController中:
//去编辑员工的页面
@GetMapping("/empl/{id}")
public String toEditPage(@PathVariable("id") Integer id, Model model) {
//获取员工的信息:
Employee employee = employeeDao.get(id);
model.addAttribute("employee",employee);
//获取所有的部门信息:
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("departments", departments);
//重定向到编辑员工的页面(add页面一样,共用)
return "empl/employee";
}
3) 在employee.html页面中回显员工的信息。
<form th:action="@{/empl}" method="post">
<div class="form‐group">
<label>LastName</label>
<input type="text" th:value="${employee.lastName}" th:name="lastName" class="form-control" placeholder="zhangsan">
</div>
<br>
<div class="form‐group">
<label>Email</label>
<input type="email" th:value="${employee.email}" th:name="email" class="form-control" placeholder="zhangsan@atguigu.com">
</div>
<br>
<div class="form‐group">
<label>Gender</label><br/>
<div class="form‐check form‐check‐inline">
<input class="form‐check‐input" type="radio" name="gender" value="1" th:checked="${employee.gender}==1">
<label class="form‐check‐label">男</label>
</div>
<div class="form‐check form‐check‐inline">
<input class="form‐check‐input" type="radio" name="gender" value="0" th:checked="${employee.gender}==0">
<label class="form‐check‐label">女</label>
</div>
</div>
<br>
<div class="form‐group">
<label>department</label>
<select class="form-control" th:name="department.id">
<option th:selected="${employee.department.id}==${dept.id}" th:value="${dept.id}" th:text="${dept.departmentName}" th:each="dept:${departments}">1
</option>
</select>
</div>
<br>
<div class="form‐group">
<label>Birth</label>
<input type="text" th:value="${#dates.format(employee.birth, 'yyyy-MM-dd HH:mm:ss')}" th:name="birth" class="form-control" placeholder="zhangsan">
</div>
<br>
<button type="submit" class="btn btn-sm btn-primary">添加</button>
</form>
4) 重启应用,点击编辑
回显成功。
此时,再次点击添加时:
控制台:
这是因为,我们点击添加的时候,model对象中根本没有employee对象,所以employee.html中的:
th:value=”${employee.lastName}”等都会出现空指针异常。
5)再次修改employee.html页面:添加判断语句
${employee!=null}?
例如:
th:value="${employee!=null}?${employee.lastName}"
此时:再次点击员工添加,就不会出现问题了。
6)button的按钮修改
<button type="submit" th:text="${employee!=null}?'修改':'添加'" class="btn btn-sm btn-primary">添加</button>
7)form表单的method的修改:
当是添加操作时,就是method=post提交。
当是修改操作时,就是method=put提交。
以前是SpringMVC的时候,想要使用put提交。
- SpringMVC中配置HiddenHttpMethodFilter;
- 页面创建一个post表单
- 创建一个input项,name=”_method”;值就是我们指定的请求方式
Springboot已经默认帮我们配置好了HiddenHttpMethodFilter。在WebMvcAutoConfiguration中:
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
所以,现在我们只需要添加一个input项,name=”_method”的标签即可:
<input type="hidden" name="_method" value="put" th:if="${employee!=null}"/>
<input type="hidden" name="id" th:value="${employee.id}" th:if="${employee!=null}"/>
8) EmployeeController中添加编辑的逻辑:
//修改员工
@PutMapping("/empl")
public String EditEmployee(Employee employee) {
System.out.println(employee);
//修改员工
employeeDao.save(employee);
return "redirect:/empls";
}
9) 随便点击一个员工进行修改:
修改成功!!!
员工列表的删除
1)修改删除的按钮以delete的方式提交
不能简单的将button按钮标签改为a标签,因为a标签默认是get方式提交。
我们使用js的方式来提交表单:
2)添加js代码:
<script type="text/javascript">
$(".deleteBtn").click(function () {
var del_url = $(this).attr("del_url");
$("#deleteEmpForm").attr("action", del_url).submit();
//改变form默认的提交方式
return false;
})
</script>
3) EmployeeController中:
//删除员工信息
@DeleteMapping("/empl/{id}")
public String deleteEmlp(@PathVariable("id") Integer id) {
//删除指定员工id的员工
employeeDao.delete(id);
return "redirect:/empls";
}
4) 重启应用,试试删除按钮