Web开发
- 简介
- 使用SpringBoot
- 创建SpringBoot应用,选中我们需要的模块
- SpringBoot已经默认将这些场景配置好了,只需要在配置文件中指定少量配置就可以运行起来
- 自己编写业务代码
- 自动配置原理?
- 这个场景SpringBoot帮我们配置了什么?能不能修改?能修改哪些配置?能不能扩展?xxx
- xxxxAutoConfiguration:帮我们给容器中自动配置组件
- xxxxProperties:配置类来封装配置文件的内容
- SpringBoot对静态资源的映射规则
-
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(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl)); } } }
- 方式1:所有 /webjars/**,都去 classpath:/META-INF/resources/webjars/ 找资源
- webjars:以jar包的方式引入引入静态资源
- 将常用的前端框架以Maven依赖的方式呈现:http://www.webjars.org/
- 方式2:" /** " 访问当前项目的任何资源,都去(静态资源的文件夹)找映射
"classpath:/META-INF/resources/" "classpath:/resources/" "classpath:/static/" "classpath:/public/" "/": 当前项目的根路径
- localhost:8080/abc === 去静态资源文件夹里面找abc
- 方式3:欢迎页, 静态资源文件夹下的所有index.html页面;被 " /** " 映射
//欢迎页映射 @Bean public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern()); welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider)); return welcomePageHandlerMapping; }
- localhost:8080/ 找index页面
- 方式4:所有的 **/favicon.ico 都是在静态资源文件下找
//配置喜欢的图标 @Configuration @ConditionalOnProperty(value = "spring.mvc.favicon.enabled", matchIfMissing = true) public static class FaviconConfiguration { private final ResourceProperties resourceProperties; public FaviconConfiguration(ResourceProperties resourceProperties) { this.resourceProperties = resourceProperties; } @Bean public SimpleUrlHandlerMapping faviconHandlerMapping() { SimpleUrlHandlerMapping mapping = new SimpleUrlHandlerMapping(); mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); //所有 **/favicon.ico mapping.setUrlMap(Collections.singletonMap("**/favicon.ico", faviconRequestHandler())); return mapping; } }
- 我的SpringBoot中没有FaviconConfiguration类,所以我还没有找到能设置的地方,也不知道怎么设置???
- 上述的设置是吧ico文件放在resources资源映射文件夹中,重启即可
- 上边的代码是1.5.9版本的SpringBoot,我的事2.2.0
- 自己定义静态文件资源,多个路径使用逗号分隔
- 在properties文件中配置:spring.resources.static-locations=classpath:/hello/,classpath:/atguigu
- 模板引擎
- SpringBoot推荐的Thymeleaf,语法更简单,功能更强大
- 引入thymeleaf
<!-- 引入thymeleaf的依赖包 --> <!-- 我这里默认使用的是3.0.11版本 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>
Thymeleaf使用
@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"; //只要我们把 HTML 页面放在 classpath:/templates/,thymeleaf就能自动渲染
使用:
导入thymeleaf的名称空间
使用thymeleaf语法
语法规则
th:text,改变当前元素里面的文本内容
th,任意属性,来代替原生属性的值
Simple expressions:(表达式语法)
Variable Expressions: ${...}:获取变量值;OGNL
获取对象的属性、调用方法
使用内置的基本对象:
#ctx : the context object.
#vars: the context variables.
#locale : the context locale.
#request : (only in Web Contexts) the HttpServletRequest object.
#response : (only in Web Contexts) the HttpServletResponse object.
#session : (only in Web Contexts) the HttpSession object.
#servletContext : (only in Web Contexts) the ServletContext object.
内置的一些工具对象:
#execInfo : information about the template being processed.
#messages : methods for obtaining externalized messages inside variables expressions, in the same way as they would be obtained using #{…} syntax.
#uris : methods for escaping parts of URLs/URIs
#conversions : methods for executing the configured conversion service (if any).
#dates : methods for java.util.Date objects: formatting, component extraction, etc.
#calendars : analogous to #dates , but for java.util.Calendar objects.
#numbers : methods for formatting numeric objects.
#strings : methods for String objects: contains, startsWith, prepending/appending, etc.
#objects : methods for objects in general.
#bools : methods for boolean evaluation.
#arrays : methods for arrays.
#lists : methods for lists.
#sets : methods for sets.
#maps : methods for maps.
#aggregates : methods for creating aggregates on arrays or collections.
#ids : methods for dealing with id attributes that might be repeated (for example, as a result of an iteration).
Selection Variable Expressions: *{...}:选择表达式,和${}在功能上是一样
补充:配合 th:object="${session.user}"
Message Expressions: #{...}:获取国际化内容
Link URL Expressions: @{...}:定义URL
如果含有多个变量,那么使用(key=value , key=value)的方式即可
Fragment Expressions: ~{...}:片段引用表达式
<div th:insert="~{commons :: main}">...</div>
Literals(字面量)
Text literals: 'one text' , 'Another one!' ,…字符串
Number literals: 0 , 34 , 3.0 , 12.3 ,…数字
Boolean literals: true , false
Null literal: null
Literal tokens: one , sometext , main ,…多个数据用逗号隔开
Text operations:(文本操作)
String concatenation(字符串拼接): +
Literal substitutions(字符串替换): |The name is ${name}|
Arithmetic operations:(数学运算)
Binary operators: + , - , * , / , %
Minus sign (unary operator): -
Boolean operations:(布尔运算)
Binary operators: and , or
Boolean negation (unary operator): ! , not
Comparisons and equality:(比较运算)
Comparators: > , < , >= , <= ( gt , lt , ge , le )
Equality operators: == , != ( eq , ne )
Conditional operators:条件运算(三元运算符)
If-then: (if) ? (then)
If-then-else: (if) ? (then) : (else)
Default: (value) ?: (defaultvalue)
Special tokens:(不想做什么操作)
No-Operation: _
举例:
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>成功!</h1> <!--th:text 将div里面的文本内容设置为 --> <div id="div01" class="myDiv" th:id="${hello}" th:class="${hello}" th:text="${hello}">这是显示欢迎信息</div> <hr/> <!--th:text,不会转义特殊字符,输出双引号内的数据--> <div th:text="${hello}"></div> <!--th:utext,会转义特殊字符,将双引号之内的特殊字符转义之后输出--> <div th:utext="${hello}"></div> <hr/> <!--th:each每次遍历都会生成这个标签,三个h4标签--> <h4 th:text="${user}" th:each="user : ${users}"></h4> <hr/> <h4> <!--[[...]] corresponds to th:text--> <!--[(...)] corresponds to th:utext--> <span th:each="user : ${users}">[[${user}]]</span> </h4> </body> </html>
SpringMVC自动配置
Spring Boot 自动配置好了SpringMVC.
The auto-configuration adds the following features on top of Spring’s defaults:
- Inclusion of
ContentNegotiatingViewResolver
andBeanNameViewResolver
beans.
- 自动配置了ViewResolver(视图解析器:根据方法的返回值得到视图对象【view】,视图对象决定如何渲染【转发页面?重定向页面?】【Ctrl+N类搜索】)
ContentNegotiatingViewResolver:组合所有的视图解析器
- 如何定制:我们可以自己给容器中添加一个视图解析器,自动的将其组合进来
- Support for serving static resources, including support for WebJars (see below). ==> 静态资源文件夹Webjars
- Static
index.html
support. ==> 静态首页- Custom
Favicon
support (see below). ==> favicon.ico- Automatic registration of
Converter
,GenericConverter
,Formatter
beans.
Converter:转换器,public String hello(Users user),类型转换使用Converter
Formatter:格式化器,2019-11-02===Data,
自己添加的格式化器转换器,我们只需要放在容器中即可- Support for
HttpMessageConverters
(see below).
HttpMessageConverters:SpringMVC用来转换Http请求和响应的,
例如:有User类型的对象,想以Json数据的方式写出去
HttpMessageConverters:
是从容器中确定,获取所有的HttpMessageConverters
- 自己给容器中添加
HttpMessageConverters
,只需要将自己的组件注册容器中(@Bean,@Component)- Automatic registration of
MessageCodesResolver
(see below). ==> 定义错误代码生成规则- Automatic use of a
ConfigurableWebBindingInitializer
bean (see below).
- 我们可以配置一个
ConfigurableWebBindingInitializer
来替换默认的(添加到容器)- org.springframework.boot.autoconfigure.web:web的所有自动场景
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 typeWebMvcConfigurationSupport
, but without@EnableWebMvc
. If you wish to provide custom instances ofRequestMappingHandlerMapping
,RequestMappingHandlerAdapter
orExceptionHandlerExceptionResolver
you can declare aWebMvcRegistrationsAdapter
instance providing such components.If you want to take complete control of Spring MVC, you can add your own
@Configuration
annotated with@EnableWebMvc
.扩展SpringMVC
编写一个配置类(@Configuration),是WebMvcConfigurationSupport类型,不能标注@EnableWebMvc
SpringBoot2.0中原先的WebMvcConfigurerAdapter 已经过时了,改成了
直接实现WebMvcConfigurer
直接继承WebMvcConfigurationSupport
既保留了SpringBoot的所有自动配置,也能用我们扩展的配置
//使用WebMvcConfigurationSupport可以扩展SpringMVC的功能 @Configuration public class MyMvcConfig extends WebMvcConfigurationSupport { @Override public void addViewControllers(ViewControllerRegistry registry) { //浏览器发送/atguigu请求,也来到success页面 registry.addViewController("/atguigu").setViewName("success"); } }
原理:
WebMvcAutoConfiguration是SpringMVC的自动配置类
在做其他配置时会导入:@Import({WebMvcAutoConfiguration.EnableWebMvcConfiguration.class})
容器中所有的WebMvcConfigurer都会一起起作用
我们的配置类也会被调用
效果:SpringMVC的自动配置和我们的扩展配置都会起作用
全面接管SpringBoot
SpringBoot对SpringMVC的自动配置不需要了,所有都是我们自己配置,所有的SpringMVC的自动配置都失效了
我们需要在配置类中添加@EnableWebMvc即可
原理:
@EnableWebMvc的核心
@Configuration(proxyBeanMethods = false) @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass({Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class}) //ConditionalOnMissingBean: 容器中没有这个组件的时候,这个自动配置类才生效 @ConditionalOnMissingBean({WebMvcConfigurationSupport.class}) @AutoConfigureOrder(-2147483638) @AutoConfigureAfter({DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class, ValidationAutoConfiguration.class}) public class WebMvcAutoConfiguration {
@EnableWebMvc将WebMvcConfigurationSupport组件导入进来
导入的WebMvcConfigurationSupport只是SpringMVC最基本的功能
如何修改SpringBoot的默认配置
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(@Bean、@Component)如果有就用用户配置的,如果没有,才自动配置,如果有些组件可以有多个(ViewResolver)将用户配置的和自己默认的组合起来
在SpringBoot中会有非常多的xxxConfigurer帮助我们进行扩展配置
在SpringBoot中会有很多的xxxCustomizer帮助我们进行定制配置
RestfulCRUD
默认访问首
//SpringBoot2.0改成了,使用 WebMvcConfigurationSupport 可以扩展SpringMVC的功能 //WebMvcConfigurerAdapter 不过也可以使用 //@EnableWebMvc 不要接管SpringMVC @Configuration public class MyMvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { //浏览器发送/atguigu请求,也来到success页面 registry.addViewController("/atguigu").setViewName("success"); } //所有的WebMvcConfigurerAdapter组件都会一起起作用 @Bean //将组件注册在容器 public WebMvcConfigurerAdapter webMvcConfigurerAdapter(){ WebMvcConfigurerAdapter adapter = new WebMvcConfigurerAdapter() { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("login"); registry.addViewController("/index.html").setViewName("login"); } }; return adapter; } }
国际化
编写国际化配置文件
使用ResourceBundleMessageSource管理国际化资源文件
在页面使用fmt:message取出国际化内容
步骤:
1、编写国际化配置文件,抽取页面需要显示的国际化消息
2、SpringBoot自动配置好了管理国际化资源文件的组件
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()))); } if (properties.getEncoding() != null) { messageSource.setDefaultEncoding(properties.getEncoding().name()); } messageSource.setFallbackToSystemLocale(properties.isFallbackToSystemLocale()); Duration cacheDuration = properties.getCacheDuration(); if (cacheDuration != null) { messageSource.setCacheMillis(cacheDuration.toMillis()); } messageSource.setAlwaysUseMessageFormat(properties.isAlwaysUseMessageFormat()); messageSource.setUseCodeAsDefaultMessage(properties.isUseCodeAsDefaultMessage()); return messageSource; }
3、去页面获取国际化的值
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>Signin Template for Bootstrap</title> <!-- Bootstrap core CSS --> <link href="asserts/css/bootstrap.min.css" th:href="@{/webjars/bootstrap/4.3.1/css/bootstrap.css}" rel="stylesheet"> <!-- Custom styles for this template --> <link href="asserts/css/signin.css" th:href="@{/asserts/css/signin.css}" rel="stylesheet"> </head> <body class="text-center"> <form class="form-signin" action="dashboard.html"> <img class="mb-4" th:src="@{asserts/img/bootstrap-solid.svg}" src="asserts/img/bootstrap-solid.svg" alt="" width="72" height="72"> <h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1> <label class="sr-only" th:text="#{login.username}">Username</label> <input type="text" class="form-control" placeholder="Username" th:placeholder="#{login.username}" required="" autofocus=""> <label class="sr-only" th:text="#{login.password}">Password</label> <input type="password" class="form-control" placeholder="Password" th:placeholder="#{login.password}" required=""> <div class="checkbox mb-3"> <label> <input type="checkbox" value="remember-me"/>[[#{login.remember}]] </label> </div> <button class="btn btn-lg btn-primary btn-block" type="submit" th:text="#{login.btn}">Sign in</button> <p class="mt-5 mb-3 text-muted">© 2017-2018</p> <a class="btn btn-sm">中文</a> <a class="btn btn-sm">English</a> </form> </body> </html>
效果:根据浏览器语言设置的信息切换了国际化
原理:
国际化Locale(区域信息对象),LocaleResolver(获取区域信息对象)
//默认的就是根据请求头带来的区域信息获取Locale进行国际化 @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; } }
4、点击链接切换国际化
/** * 可以在链接上携带区域信息 */ public class MyLocaleResolver implements LocaleResolver { @Override public Locale resolveLocale(HttpServletRequest rRequest) { String l = rRequest.getParameter("l"); //获取系统默认的Locale Locale locale = Locale.getDefault(); //如果获取到了,则将字符串进行分割 //假设是zh_CN,那么得到的字符串数组为split[0]="zh",split[1]="CN" if (!StringUtils.isEmpty(l)){ String[] split = l.split("_"); locale = new Locale(split[0], split[1]); } return locale; } @Override public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) { } } /** * 将我们的区域信息添加到容器中 * @return */ @Bean public LocaleResolver localeResolver(){ return new MyLocaleResolver(); }
简单登陆
开发期间模板引擎页面修改以后,要实时生效
# 禁用模板引擎的缓存 # 项目运行期间,不会把html页面的修改进行生效,要想实时生效怎么办? # 第一步: 禁用缓存 # 第二步: 在修改的html页面使用Ctrl+F9,重新编译页面 spring.thymeleaf.cache=false
登陆:在Controller中判断用户名及密码
@Controller public class LoginController { //处理登录功能 // @DeleteMapping // @GetMapping // @PutMapping //@RequestMapping(value = "/user/login",method = RequestMethod.POST) //@PostMapping:处理一个POST请求 @PostMapping(value = "/user/login") public String login(@RequestParam("username") String username, @RequestParam("password") String password, Map<String,Object> map){ if (!StringUtils.isEmpty(username) && password.equals("123456")){ //登陆成功 return "dashboard"; } else { //登陆失败 map.put("msg","用户名密码错误"); return "login"; } } }
登陆的错误消息显示,在HTML页面中添加判断
<!--条件判断: 当msg不为空的时候则生成消息内容--> <p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
拦截器进行登陆检查
重定向之后再两个不同的浏览器中输入URL,都可以进入,那么我们做的登陆就失去意义了,我们可以用拦截器机制来做一个登陆检查
拦截器:
/** * 登录检查,没有登录的用户不能访问后台的主页和对数据进行增删改查 */ public class LoginHandlerInterceptor implements HandlerInterceptor { //目标方法执行之前 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("loginUser"); if (user == null){ //未登录,返回登陆页面 request.setAttribute("msg","没有权限请先登录"); //获取到转发器,转发请求到index.html,并将请求和响应转发出去 request.getRequestDispatcher("/index.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 { } }
//注册拦截器 @Override public void addInterceptors(InterceptorRegistry registry) { //静态资源: *.css , *.js //SpringBoot已经做了静态资源映射,所以我们写拦截器的时候不需要处理静态资源 registry.addInterceptor(new LoginHandlerInterceptor()) //添加拦截器要拦截的请求 .addPathPatterns("/**") //排除一些请求 .excludePathPatterns("/index.html","/","/user/login"); }
CRUD-员工列表
实验要求:
RestfulCRUD:CRUD满足Rest风格
URI: /资源名称/资源标识 HTTP请求方式区分对资源CRUD操作
普通CRUD(uri来区分操作) RestfulCRUD 查询 getEmp emp---GET 添加 addEmp?xxx emp---POST 修改 updateEmp?id=xxx&xxx=xx emp/{id}---PUT 删除 deleteEmp?id=1 emp/{id}---DELETE - 实验的请求架构
实验功能 请求URI 请求方式 查询所有员工 emps GET 查询某个员工(来到修改页面) emp/{id} GET 来到添加页面 emp GET 添加员工 emp POST 来到修改页面(查出员工进行信息回显) emp/{id} GET 修改员工 emp PUT 删除员工 emp/{id} DELETE - 员工列表:
thymeleaf公共页面元素抽取
1、抽取公共片段 <div th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </div> 2、引入公共片段 <div th:insert="~{footer :: copy}"></div> ~{templatename::selector}:模板名::选择器 ~{templatename::fragmentname}:模板名::片段名 3、默认效果: insert的公共片段在div标签中 如果使用th:insert等属性进行引入,可以不用写~{}: 行内写法可以加上:[[~{}]];[(~{})];
三种引入公共片段的th属性:
th:insert is the simplest,it will simply insert the specified fragment as the body of its host tag.
将公共片段整个插入到声明引入的元素中
th:replace actually replaces its host tag with the specified fragment.
将声明引入的元素替换为公共片段
th:include is similar to th:insert , but instead of inserting the fragment it only inserts the contents of this
将被引入的片段的内容包含进这个标签中
举例:
<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>
问题:
因为引入了片段之后,会发现当你点击某个按钮时发现,只有第一个按钮是高亮显示的,我们需要的是点击那个按钮,那个按钮是高亮的
引入片段的时候传入参数:
解决高亮,使用判断的方式:
CRUD-员工添加
<!--添加员工信息的form表单--> <form th:action="@{/emp}" method="post"> <div class="form-group"> <label>LastName</label> <input name="lastName" type="text" class="form-control" placeholder="zhangsan"> </div> <div class="form-group"> <label>Email</label> <input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com"> </div> <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> <div class="form-group"> <label>department</label> <!--提交的是部门的id--> <select class="form-control" name="department.id"> <option th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option> </select> </div> <div class="form-group"> <label>Birth</label> <input name="birth" type="text" class="form-control" placeholder="zhangsan"> </div> <button type="submit" class="btn btn-primary">添加</button> </form>
//员工添加功能 /** * SpringMVC自动将请求参数和入参对象的属性进行一一绑定: * 要求了请求参数的名字和JavaBean入参的对象里边的属性名是一样的 */ @PostMapping("/emp") public String addEmp(Employee employee){ //来到员工列表页面 System.out.println("保存的员工信息:"+employee); //保存员工数据 employeeDao.save(employee); /** * 两种方式: * 重定向到一个地址: redirect * 转发到一个地址: forward * /: 表示当前项目路径 */ return "redirect:/emps"; }
这里有个问题,在页面提交信息的时候当生日格式为:2019-11-5时,返回的是一个错误页面,只有当生日格式为:2019/11/5时,才会返回正确的网页
日期的格式化:SpringMVC将页面提交的值需要转换为指定的类型
CRUD-员工修改
修改添加二合一表单
<!--需要区分是员工修改还是员工添加,需要添加的时候emp对象是空的,而需要修改的时候emp不为空,所以只需要在取值之前进行判断,不为空取值--> <!--添加员工信息的form表单--> <form th:action="@{/emp}" method="post"> <!--发送put请求,修改员工数据--> <!-- 1、SpringMVC中配置HiddenHttpMethodFilter,(SpringBoot自动配置好的) 2、页面创建一个post表单 3、创建一个input项,name="_method",值就是我们指定的请求方式 --> <input type="hidden" name="_method" value="put" th:if="${emp!=null}"> <input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}"> <div class="form-group"> <label>LastName</label> <input name="lastName" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${emp.lastName}"> </div> <div class="form-group"> <label>Email</label> <input name="email" type="email" class="form-control" placeholder="zhangsan@atguigu.com" th:value="${emp!=null}?${emp.email}"> </div> <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="${emp!=null}?${emp.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="${emp!=null}?${emp.gender==0}"> <label class="form-check-label">女</label> </div> </div> <div class="form-group"> <label>department</label> <!--提交的是部门的id--> <select class="form-control" name="department.id"> <option th:selected="${emp!=null}?${dept.id == emp.department.id}" th:value="${dept.id}" th:each="dept:${depts}" th:text="${dept.departmentName}">1</option> </select> </div> <div class="form-group"> <label>Birth</label> <input name="birth" type="text" class="form-control" placeholder="zhangsan" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"> </div> <button type="submit" class="btn btn-primary" th:text="${emp!=null}?'修改':'添加'">添加</button> </form>
CRUD-员工删除
<tr th:each="emp:${emps}"> <td th:text="${emp.id}"></td> <td>[[${emp.lastName}]]</td> <td th:text="${emp.email}"></td> <td th:text="${emp.gender}==0?'女':'男'"></td> <td th:text="${emp.department.departmentName}"></td> <td th:text="${#dates.format(emp.birth, 'yyyy-MM-dd HH:mm')}"></td> <td> <a class="btn btn-sm btn-primary" th:href="@{/emp/}+${emp.id}">编辑</a> <button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deleteBtn">删除</button> </td> </tr> <script> $(".deleteBtn").click(function(){ //删除当前员工的 $("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit(); return false; }); </script>
错误处理机制
SpringBoot默认的错误处理机制
浏览器,返回一个默认的错误页面
浏览器发送请求的请求头:
如果是其他客户端,默认响应一个json数据
消息体内容:时间戳,状态码,错误信息,提示消息,访问路径
原理:
可以参照ErrorMvcAutoConfiguration,错误处理的自动配置
给容器中添加了以下组件
DefaultErrorAttributes:
//帮我们在页面共享信息 public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> errorAttributes = new LinkedHashMap(); errorAttributes.put("timestamp", new Date()); this.addStatus(errorAttributes, webRequest); this.addErrorDetails(errorAttributes, webRequest, includeStackTrace); this.addPath(errorAttributes, webRequest); return errorAttributes; }
BasicErrorController:处理默认/error请求
@Controller @RequestMapping({"${server.error.path:${error.path:/error}}"}) public class BasicErrorController extends AbstractErrorController { 产生html类型的数据,浏览器发送的请求来到这个方法处理 @RequestMapping(produces = {"text/html"}) public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) { HttpStatus status = this.getStatus(request); Map<String, Object> model = Collections.unmodifiableMap(this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.TEXT_HTML))); response.setStatus(status.value()); //去哪个页面作为错误页面,包含页面地址和页面内容 ModelAndView modelAndView = this.resolveErrorView(request, response, status, model); return modelAndView != null ? modelAndView : new ModelAndView("error", model); } @RequestMapping //产生json数据,其他客户端来到这个方法处理 public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) { HttpStatus status = this.getStatus(request); if (status == HttpStatus.NO_CONTENT) { return new ResponseEntity(status); } else { Map<String, Object> body = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL)); return new ResponseEntity(body, status); } }
ErrorPageCustomizer:
// 系统出现错误以后来到error请求进行处理 // 类似于以前的: web.xml注册的错误页面规则 @Value("${error.path:/error}") private String path = "/error";
DefaultErrorViewResolver:
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) { ModelAndView modelAndView = this.resolve(String.valueOf(status.value()), model); if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) { modelAndView = this.resolve((String)SERIES_VIEWS.get(status.series()), model); } return modelAndView; } private ModelAndView resolve(String viewName, Map<String, Object> model) { //默认SpringBoot可以去找到一个页面? error/404 String errorViewName = "error/" + viewName; //模板引擎可以解析这个页面地址就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders.getProvider(errorViewName, this.applicationContext); //模板引擎可用的情况下返回到errorViewName指定的视图地址,否则,就在静态资源文件夹下找errorViewName对应的页面 error/404.html return provider != null ? new ModelAndView(errorViewName, model) : this.resolveResource(errorViewName, model); }
步骤:
一但系统出现4xx或者5xx之类的错误,ErrorPageCustomizer就会生效(定制错误的响应规则),就会来到/error请求,就会被BasicErrorController处理
响应页面:去哪个页面是由DefaultErrorViewResolver解析得到的
protected ModelAndView resolveErrorView(HttpServletRequest request, HttpServletResponse response, HttpStatus status, Map<String, Object> model) { //所有的ErrorViewResolver得到ModelAndView Iterator var5 = this.errorViewResolvers.iterator(); ModelAndView modelAndView; do { if (!var5.hasNext()) { return null; } ErrorViewResolver resolver = (ErrorViewResolver)var5.next(); modelAndView = resolver.resolveErrorView(request, status, model); } while(modelAndView == null); return modelAndView; }
如果定制错误响
如何定制错误的页面
有模板引擎的情况下,error/ 状态码,【将错误页面命名为 错误状态码.html 放在模板引擎文件夹里面的 error文件夹下】,发生此状态码的错误就会来到对应的页面
我们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的 所有错误,精确优先(优先寻找精确的状态码.html)
页面能获取的信息:
没有模板引擎(模板引擎找不到这个错误页面),静态资源文件夹下找
以上都没有错误页面,就是默认来到SpringBoot默认的错误提示页面
如何定制错误的Json数据
自定义异常处理&返回定制json数据
@ControllerAdvice public class MyExceptionHandler { @ResponseBody @ExceptionHandler(UserNotExistException.class) public Map<String,Object> handlerException(Exception e){ Map<String,Object> map = new HashMap<>(); map.put("code","user.notexist"); map.put("message",e.getMessage()); return map; } } //没有自适应效果,浏览器和客户端返回的都是Json
转发到/error进行自适应响应效果处理
@ExceptionHandler(UserNotExistException.class) public String handleException(Exception e, HttpServletRequest request){ Map<String,Object> map = new HashMap<>(); //传入我们自己的错误状态码 4xx 5xx,否则就是默认的200,而且也不会进入定制错误页面的解析流程 /** * Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code"); */ request.setAttribute("javax.servlet.error.status_code",500); map.put("code","user.notexist"); map.put("message",e.getMessage()); request.setAttribute("ext",map); //转发到/error return "forward:/error"; }
将我们的定制数据携带出去
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去可以获取的数据是由getErrorAttributes得到的(是AbstractErrorController(ErrorController)规定的方法)
完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中
页面上能用的数据,或者是json返回能用的数据都是通过errorAttributes.getErrorAttributes得到,容器中DefaultErrorAttributes.getErrorAttributes(),默认进行数据处理的
自定义ErrorAttributes
//给容器中加入我们自己定义的ErrorAttributes @Component public class MyErrorAttributes extends DefaultErrorAttributes { //返回的map就是页面和json能获取的所有字段 @Override public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) { Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace); map.put("company","atguigu"); //这是我们异常处理器携带的数据 Map<String,Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0); map.put("ext",ext); return map; } }
最终的效果:响应是自适应的,可以通过定制ErrorAttributes改变需要返回的内容