一、Controller和模板引擎
1.Controller用到的注解
@GetMapping – 处理get请求的路径映射
@GetMapping("emp/{id}")
@PutMapping – 处理put请求的路径映射
@PutMapping("emp/{id}")
@PathVariable – 获取请求参数中的’id’
@PathVariable("id")
给前端返回一个bean
model.setAttribute("emp",employee);
2.页面重用(thymeleaf)
2.1 标签实现重用(新增页面和编辑页面)
th:value="${emp!=null}?${emp.lastName}"//如果回显对象不为空就直接回显值
th:if="${emp!=null}"//如果回显对象有值才生成当前元素
th:checked="${emp!=null}?${emp.gender==1}"//条件成立则选中
th:selected="${emp!=null}?${emp.deptId==dept.id}"//条件成立则选中
th:text="${emp!=null}?'编辑':'添加'"//三元判断
th:action="@{/emp/}+${emp.id}"//@{}代表当前项目路径下,后面用拼接的方式拼接id
2.2 简化列表每一列按钮请求方式
上面发送delete请求的代码中,每一个删除按钮都有一个单独的form表单,这样的格式显得代码十分的臃肿,现在用另一种方法来轻量化表单。
以删除按钮为例:
- 用th:attr来设置自定义属性值
//删除按钮上定义当前记录的id
<button th:attr="del_uri=@{/emp/}+${emp.id}" class="btn btn-sm btn-danger deletebtn">删除</button>
- form表单-只写一个
<form id="deleteEmpForm" method="post">
<input type="hidden" name="_method" value="delete" >
</form>
- 删除按钮绑定事件
<script>
//绑定删除按钮的点击事件
//动态设置删除form表单的action值,并提交。
$(".deletebtn").click(function(){
$("#deleteEmpForm").attr("action",$(this).attr("del_uri")).submit();
return false;
});
</script>
3.发送自定义请求(RESTful)
SpringBoot已经在WebMvcAutoConfiguration.java中自动配置了HiddenHttpMethodFilter过滤器,作用就是把请求转换为指定的请求格式。
可以指定put、get、delete、post
<!--1.页面创建一个post方式的表单-->
<!--2.创建一个hidden,这个hidden的name="_method" ,value就是需要过滤器转换的目标请求方式。(hidden只在编辑时生成)-->
<form th:action="@{/emp/}+${emp.id}" method="post">
<input type="hidden" name="_method" value="delete" >
</form>
二、错误处理原理&定制错误页面
1.SpringBoot默认错误处理原理
默认错误处理配置类:ErrorMvcAutoConfiguration 配置类中配置了以下几个组件:
ErrorPageCustomizer 当系统出现错误以后来到error请求进行处理
BasicErrorController:处理默认/error请求,去哪儿个页面由DefaultErrorViewResolver解析得到
DefaultErrorViewResolver:找到error/(状态码)对应的页面,如果有模板引擎就去模板引擎下找,否则就去静态资源文件夹下找,都没有就返回代码中定义的一段视图代码。
DefaultErrorAttributes:帮我们在页面共享信息,获取页面展示或者json返回的数据。
步骤:
一但系统出现4xx或者5xx之类的错误;ErrorPageCustomizer就会生效(定制错误的响应规则);它会发送/error请求,而error请求会被BasicErrorController处理;BasicErrorController中又通过DefaultErrorViewResolver解析得到该去哪儿个页面
@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status,
Map<String, Object> model) {
ModelAndView modelAndView = resolve(String.valueOf(status), model);
if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
modelAndView = resolve(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);
if (provider != null) {
//模板引擎可用的情况下返回到errorViewName指定的视图地址
return new ModelAndView(errorViewName, model);
}
//模板引擎不可用,就在静态资源文件夹下找errorViewName对应的页面 error/404.html
return resolveResource(errorViewName, model);
}
2.定制错误页面
- 有模板引擎的情况下;error/状态码
【只要将将错误页面命名为:错误状态码.html
放在模板引擎文件夹里面的 error文件夹下】
发生此状态码的错误就会来到对应的页面;同时,们可以使用4xx和5xx作为错误页面的文件名来匹配这种类型的所有错误(优先寻找精确的 状态码.html)
页面能获取的信息:
timestamp:时间戳
status:状态码
error:错误提示
exception:异常对象
message:异常消息
errors:JSR303数据校验的错误都在这里 - 没有模板引擎(模板引擎找不到这个错误页面),就会去静态资源文件夹下找;
- 如果模板和静态资源文件夹下都没有错误页面,就返回代码中定义的一段视图代码;
3.定制错误json
方法:
- 完全来编写一个ErrorController的实现类【或者是编写AbstractErrorController的子类】,放在容器中;(比较繁琐)
- 页面上能用的数据,或者是json返回能用的数据默认都是通过容器中 DefaultErrorAttributes.getErrorAttributes() 获取的;可以重写这个方法来实现自定义。
//给容器中加入我们自己定义的ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
//重写getErrorAttributes方法,里面对数据进行处理
@Override
public Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(requestAttributes, includeStackTrace);
map.put("company","atguigu");
return map;
}
}
三、嵌入式Servlet容器
SpringBoot默认的Servlet容器是Tomcat
1.修改Servlet容器的相关配置(配置文件)
ServerProperties类中规定了嵌入式Servlet容器的一些配置,如果想要修改默认配置只需在配置文件中重新配置对应的属性即可。
server.port=8081
server.context-path=/crud
server.tomcat.uri-encoding=UTF-8
//通用的Servlet容器设置
server.xxx
//Tomcat的设置
server.tomcat.xxx
2.添加定制器的方法来实现修改Servlet容器配置
编写一个方法,给容器中注入EmbeddedServletContainerCustomizer(Servlet容器定制器)
@Bean //一定要将这个定制器加入到容器中
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
return new EmbeddedServletContainerCustomizer() {
//定制嵌入式的Servlet容器相关的规则
@Override
public void customize(ConfigurableEmbeddedServletContainer container) {
container.setPort(8083);//设置属性
}
};
}
3.切换其他嵌入式Servlet容器
只需要在pom文件中把别的容器的依赖去掉,只引入要使用的容器就可以了。
Tomcat (默认就是使用嵌入式的Tomcat作为Servlet容器)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Jetty
<dependency>
<artifactId>spring-boot-starter-jetty</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
Undertow
<dependency>
<artifactId>spring-boot-starter-undertow</artifactId>
<groupId>org.springframework.boot</groupId>
</dependency>
4.嵌入式Servlet容器自动配置原理
嵌入式Servlet容器自动配置原理
步骤:
- SpringBoot根据pom文件中导入的依赖情况,给容器中添加相应的容器工厂对象EmbeddedServletContainerFactory【比如tomcat容器就对应TomcatEmbeddedServletContainerFactory工厂对象】
- 只要是嵌入式的Servlet容器工厂要创建对象就会惊动后置处理器;EmbeddedServletContainerCustomizerBeanPostProcessor;
- 后置处理器会从容器中获取所有的EmbeddedServletContainerCustomizer 定制器,并遍历调用定制器的定制方法来给创建出的对象赋予属性值。
5.嵌入式Servlet容器启动原理
嵌入式Servlet容器启动原理 获取嵌入式Servlet容器的过程:
- SpringBoot应用启动运行run()方法
- refreshContext(context)—SpringBoot刷新IOC容器
【创建IOC容器对象,并初始化容器,创建容器中的每一个组件】;
如果是web应用创建AnnotationConfigEmbeddedWebApplicationContext,
否则:AnnotationConfigApplicationContext- refresh(context) 刷新刚才创建好的ioc容器;
- onRefresh(); web的ioc容器重写了onRefresh方法
- webioc容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer();
- 获取嵌入式的Servlet容器工厂
//从ioc容器中获取EmbeddedServletContainerFactory 组件; EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
当TomcatEmbeddedServletContainerFactory创建对象时,后置处理器一看是这个对象,就获取所有的定制器来先定制Servlet容器的相关配置;
- 使用容器工厂获取嵌入式的Servlet容器:
containerFactory.getEmbeddedServletContainer(getSelfInitializer());
- 嵌入式的Servlet容器创建对象并启动Servlet容器;先启动嵌入式的Servlet容器,再将ioc容器中剩下没有创建出的对象获取出来;
总结: IOC容器启动时创建嵌入式的Servlet容器
6.使用外部的Servlet容器&JSP支持
1.嵌入式Servlet容器:(应用jar方式打包)
优点:简单、便携。
缺点:默认不支持JSP、优化定制比较复杂。
2.使用外置的Servlet容器(应用war方式打包)
步骤:
- 必须创建一个war项目(创建时利用idea创建好目录结构和web.xml)
- 将嵌入式的Tomcat指定为provided;表示打包时不把tomcat打进包里,使用外部的Servlet容器。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
- 必须编写一个SpringBootServletInitializer的子类,并调用configure方法(此步骤原理见下一节)
public class ServletInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
//传入SpringBoot应用的主程序
return application.sources(SpringBoot04WebJspApplication.class);
}
}
- 启动服务器就可以使用
7.外部Servlet容器启动SpringBoot应用原理
1.使用外部Servlet容器的启动过程
SpringBoot打成jar包的执行过程:
- 执行SpringBoot主类的main方法
- 启动ioc容器
- 创建嵌入式的Servlet容器
SpringBoot打成war包的执行过程:
- 启动服务器
- 服务器启动SpringBoot应用【SpringBootServletInitializer】
- 启动ioc容器
war包的执行具体流程:
- 启动Tomcat(先启动Servlet容器)
- 服务器启动时(web应用启动时)会创建当前web应用里面每一个jar包里面ServletContainerInitializer实例。ServletContainerInitializer的实现放在jar包的META-INF/services文件夹下,有一个名为javax.servlet.ServletContainerInitializer的文件,内容就是ServletContainerInitializer的实现类的全类名。
- 使用@HandlesTypes,在应用启动的时候加载我们感兴趣的类。SpringServletContainerInitializer将@HandlesTypes(WebApplicationInitializer.class)标注的所有这个类型的类都传入到onStartup方法的Set<Class<?>>;为这些WebApplicationInitializer类型的类创建实例;
- 每一个WebApplicationInitializer都调用自己的onStartup,相当于我们的SpringBootServletInitializer的类会被创建对象,并执行onStartup方法;
- SpringBootServletInitializer实例执行onStartup的时候会createRootApplicationContext;创建IOC容器
总结: 启动Servlet容器,再启动SpringBoot应用,最后创建IOC容器
四、注册Servlet三大组件
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的web应用,没有web.xml文件,所以可以通过三个类来分别往容器中添加servlet、filter、listener。
- ServletRegistrationBean:往容器中添加Servlet组件
//注册Servlet
@Bean
public ServletRegistrationBean myServlet(){
//有参构造中传的第一个参数为要添加的Servlet,第二个参数为此Servlet映射的路径
ServletRegistrationBean registrationBean = new ServletRegistrationBean(new MyServlet(),"/myServlet");
return registrationBean;
}
- FilterRegistrationBean:往容器中添加过滤器组件
//注册过滤器
@Bean
public FilterRegistrationBean myFilter(){
//可以使用(Filter,ServletRegistrationBean)的构造器,也可以使用无参构造创建对象,然后...
//设置过滤器和要过滤的url
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());
registrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return registrationBean;
}
- ServletListenerRegistrationBean:往容器中添加监听器组件
//注册监听器
@Bean
public ServletListenerRegistrationBean myListener(){
//有参构造传入要注册的监听器
ServletListenerRegistrationBean<MyListener> registrationBean = new ServletListenerRegistrationBean<>(new MyListener());
return registrationBean;
}
PS:当SpringBoot帮我们自动配置SpringMVC的时
自动的注册SpringMVC的前端控制器:DIspatcherServlet就是使用ServletRegistrationBean来注册的。
DispatcherServletAutoConfiguration中:
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public ServletRegistrationBean dispatcherServletRegistration(
DispatcherServlet dispatcherServlet) {
ServletRegistrationBean registration = new ServletRegistrationBean(
dispatcherServlet, this.serverProperties.getServletMapping());
//默认拦截:/ --所有请求;包静态资源,但是不拦截jsp请求;
// /* --会拦截jsp
//可以通过server.servletPath来修改SpringMVC前端控制器默认拦截的请求路径
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(
this.webMvcProperties.getServlet().getLoadOnStartup());
if (this.multipartConfig != null) {
registration.setMultipartConfig(this.multipartConfig);
}
return registration;
}