- RestfulCRUD
RestfulCRUD
默认访问首页
实现一:直接写一个Controller用来访问
但是为了一个首页访问就直接写一个Controller未免有点小题大做,所以可以使用实现二
@RequestMapping({"/", "/index.html"})
public String index() {
return "login";
}
实现二:添加视图映射
在配置类中写一个方法返回WebMvcConfigurer并添加到容器中(@Bean注解)
在WebMvcConfigurer类里面实现视图映射的方法即可
@Bean
public WebMvcConfigurer webMvcConfigurer() {
WebMvcConfigurer webMvcConfigurer = new WebMvcConfigurer() {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("login");
registry.addViewController("/index.html").setViewName("login");
}
return webMvcConfigurer;
}
这里为了避免麻烦多写一个类继承webMvcConfigurer直接使用了内部类来实现操作
webjars使用与thymeleaf引用语法
webjars导入
使用maven可以导入webjars比如jQuery,BootStrap等等
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.5.1</version>
</dependency>
webjars引入
在引用使用maven导入的webjars的时候我们可以使用thymeleaf的引用语法(也就是@)
<link href="#" th:href="@{/webjars/bootstrap/4.4.1-1/css/bootstrap.css}" rel="stylesheet">
thymeleaf标签语法引入
xmlns:th="http://www.thymeleaf.org"
webjars引入的文件会默认放在项目根目录下的webjars文件夹下
实现登陆页面的国际化
SSM时代实现方式
- 编写国际化配置文件
- 使用ResourceBundleMessagSource管理国际化资源文件
- 在页面中使用fmtmesasge
SpringBoot步骤
1. 编写国际化配置文件
抽取页面中需要显示的国际化消息(国际化配置文件放在i18n下)配置文件有一个主配置文件和若干个语种的配置文件
login.properties 主配置文件,也就是没有配置语种的显示
login.btn=登录~
login.remember=记住我~
login.tip=请登录~
login.username=用户名~
login.password=密码~
login_en_US.properties 英文配置文件
login.remember=Remember Me
login.tip=Please sign in
login.username=UserName
login.password=Password
login.btn=Sign In
login_zh_CN.properties
login.remember=记住我
login.tip=请登录
login.username=用户名
login.password=密码
login.btn=登陆
2. SpringBoot自动配置好了管理国际化资源文件的组件
(源码看不懂,以后补上这里的自动配置的源码分析)
因为SpringBoot帮我们自动配置好了路径映射规则所以这里只需要在配置文件中指定一下spring.messages.basename=i18n.login(主配置文件的路径加名称)
3. 去页面获取国际化的值,使用thymeleaf中的#{}获取即可
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
中文乱码问题解决
在Idea中Settings面板中查找File Encondings 中设置UTF-8勾选右面的复选框点击ok
页面中的国际化按钮切换效果实现
原理:
国际化之所以有效果Locale(区域信息对象);LocaleResolver(获取区域信息对象)
@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;
}
默认的就是根据请求头带来的区域信息过去Locale进行国际化,所以我们可以自己写一个LocaleResolver
在点击的链接上带上参数发送请求
<a class="btn btn-sm" th:href="@{/(l='zh_CN')}">中文</a>
<a class="btn btn-sm" th:href="@{/(l='en_US')}">English</a>
编写一个区域信息解析器
需要实现LocaleResolver接口重写resolveLocale和setLocale方法
Locale对象的构造器可以传入两个参数第一个值是语言代码第二个值是国家代码
public class MyLocaleResolver implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest request) {
String l = request.getParameter("l");
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(l)) {
String[] split = l.split("_");
locale = new Locale(split[0], split[1]);
}
return locale;
}
@Override
public void setLocale(HttpServletRequest request, HttpServletResponse response, Locale locale) { }
}
为了能使这段代码生效要将这个类加载到容器中
下面的代码写入之前的MyMvcConfig类中即可
@Bean
public LocaleResolver localeResolver() {
return new MyLocaleResolver();
}
登陆功能实现
编写LoginController
@Controller
public class LoginController {
// @RequestMapping(value = "/user/login", method = RequestMethod.POST)
@PostMapping("/user/login")
public String login(
@RequestParam("username") String username,
@RequestParam("password") String password,
Model model,
HttpSession session
) {
if (!StringUtils.isEmpty(username) && "123456".equals(password)) {
// 登陆成功
session.setAttribute("loginUser", username);
return "redirect:/main.html";
} else {
model.addAttribute("msg", "用户名密码错误!");
return "login";
}
}
}
开发时候修改后需要实时生效
禁用掉thymeleaf的缓存
spring.thymeleaf.cache=false
页面修改完成后ctrl+f9重新编译一下
编写错误的提示信息
<p style="color: red" th:text="${msg}" th:if="${not #strings.isEmpty(msg)}"></p>
拦截器进行检查
编写拦截器代码
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", "没有权限请先登录");
request.getRequestDispatcher("/").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 {
}
}
注册拦截器
在之前编写的MyConfig类中的扩展组件方法中添加拦截器的方法重写
// 注册拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(
new LoginHandlerInterceptor()
).addPathPatterns(
"/**"
).excludePathPatterns(
"/",
"/index.html",
"/user/login",
"/webjars/**",
"/asserts/**",
"/static/**"
);
}
员工列表功能
要求:
- 是一个RestdulCRUD:rest风格
URI: /资源名称/资源表示 HTTP请求方式区分对资源CRUD操作
进行的操作 | 普通的CRUD(uri来区分操作) | RestfulCRUD |
---|---|---|
查询 | getEmp | emp(GET) |
添加 | addEmp?xxx | emp(POST) |
修改 | updateEmp?id=xxx&xxx=xx | emp/(id)(PUT) |
删除 | deleteEmp?id=x | emp/(id)(DELETE) |
2. 实验的请求架构
进行的操作 | 请求URI | 请求方式 |
---|---|---|
查询所有员工 | emps | GET |
查询某个员工(来到修改页面) | emp/ | GET |
添加页面 | emp | GET |
添加员工 | emp | POST |
来到修改页面(查出员工进行信息回显) | emp/ | GET |
修改员工 | emp | PUT |
删除员工 | emp/ | DELETE |
员工列表实现-thymeleaf公共页面抽取
编写EmployeeController
编写第一个请求方法查询所有员工
@Controller
public class EmployeeController {
@Autowired
EmployeeDao employeeDao;
// 查询所有员工
@GetMapping("/emps")
public String list(
Model model
) {
Collection<Employee> employees = employeeDao.getAll();
// 放在请求域中
model.addAttribute("emps", employees);
return "emp/list";
}
}
thymeleaf公共页面抽取
1. 抽取公共片段
<div th:fragment="copy">
123456789
</div>
2. 引入公共片段
<div th:insert="~{footer :: copy}"></div>
~{templatename::selector}:模板名:选择器
~{templatename::fragmentname}:模板名:片段名
3. 默认效果:
insert的功能片段在div标签中
使用下三种th属性的时候可以不写~{}
但是使用行内写法的时候就必须写~{}
三种引入片段的th属性:
th:insert:将公共的片段整个插入到指定元素中
th:replease:将声明引入的元素替换位公共片段
th:include:将被引入的片段的内容包含进这个标签中
列表的动态高亮
引入片段的时候传入参数:
<div th:replace="commons/bar::#sidebar(activeUri='main')"></div>
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
公共片段获取参数并判断:
<a class="nav-link" th:class="${activeUri=='main'?'nav-link active':'nav-link'}" href="#" th:href="@{/main.html}">
<a class="nav-link" th:class="${activeUri=='emps'?'nav-link active':'nav-link'}" href="#" th:href="@{/emps}">
数据动态插入
<table class="table table-striped table-sm">
<thead>
<tr>
<th>#</th>
<th>lastName</th>
<th>email</th>
<th>gender</th>
<th>department</th>
<th>birth</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr th:each="emp : ${emps}">
<td th:text="${emp.id}"></td>
<td>[[${emp.lastName}]]</td>
<td>[[${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')}"></td>
<td>
<a th:href="@{'/emp/' + ${emp.id}}" class="btn btn-sm btn-primary">编辑</a>
<button th:attr="del_uri=@{'/emp/' + ${emp.id}}" type="submit" class="btn btn-sm btn-danger deleteBtn">删除</button>
</td>
</tr>
</tbody>
</table>
使用模板引擎的#dates.format()方法进行日期格式化
加入操作标签
员工添加功能实现
来到员工添加页面
跳转按钮
<h2><a class="btn btn-sm btn-success" th:href="@{/emp}">员工添加</a></h2>
编写Controller
// 员工添加页面
@GetMapping("/emp")
public String toAddPage(
Model model
) {
// 来到添加页面,查出所有的部门
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts", departments);
return "emp/add";
}
编写添加页面
<div th:replace="commons/bar::#topbar"></div>
<div class="container-fluid">
<div class="row">
<!--引入侧边栏-->
<div th:replace="commons/bar::#sidebar(activeUri='emps')"></div>
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<form th:action="@{/emp}" method="post">
<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" th:value="${emp!=null}?${emp.lastName}:null" type="text" class="form-control"
placeholder="zhangsan">
</div>
<div class="form-group">
<label>Email</label>
<input name="email" th:value="${emp!=null}?${emp.email}:null" 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"
th:checked="${emp!=null}?${emp.gender==1}:null">
<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}:null">
<label class="form-check-label">女</label>
</div>
</div>
<div class="form-group">
<label>department</label>
<select name="department.id" class="form-control">
<option th:selected="${emp!=null}?${dept.id==emp.department.id}:null" 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" th:value="${emp!=null}?${#dates.format(emp.birth, 'yyyy/MM/dd')}:null"
type="text" class="form-control" placeholder="zhangsan">
</div>
<button type="submit" th:text="${emp!=null}?'修改':'添加'" class="btn btn-primary">添加</button>
</form>
</main>
</div>
</div>
这里的每一个表单元素都有emp的判空是为了之后的修改员工的数据回显
员工添加实现
编写form的地址和请求方式
<form th:action="@{/emp}" method="post">
编写Controller
SpringMvc自动将请求参数和入参对象的属性进行一一绑定:要求就是请求参数的名字和javaBean入参对象里面的属性名一致
@PostMapping("/emp")
public String addEmp(
Employee employee
) {
// 来到员工列表页面
employeeDao.save(employee);
return "redirect:/emps";
}
提交格式不对:生日:日期
2018-12-12;20171212;2017.12.12
日期的格式化:SpringMvc将页面提交的值需要转换位指定的类型
2017-12-12---Date;类型转换,格式化;
默认日期是按照/格式
在配置文件中使用
spring.mvc.date-format=yyyy-MM-dd
这个东西在之后前台输入会使用js进行格式化后提交这里只需要知道这个知识点即可
员工修改功能
编辑按钮书写
<a th:href="@{'/emp/' + ${emp.id}}" class="btn btn-sm btn-primary">编辑</a>
点击这个按钮来到修改页面
而修改页面和添加页面一样的
编写按钮请求处理
@GetMapping("/emp/{id}")
public String toEditPage(
@PathVariable("id") Integer id,
Model model
) {
Employee employee = employeeDao.get(id);
model.addAttribute("emp", employee);
Collection<Department> departments = departmentDao.getDepartments();
model.addAttribute("depts", departments);
return "emp/add";
}
这里给request挂上emp和dep的数据在添加页面上获取就可以回显数据了,回显的逻辑处理在前面添加页面上
请求方式改变
之前添加页面的请求方式是Post
而我们要修改员工使用的是Put方式
发送put请求ssm步骤
- SpringMvc中配置HiddenHttpMethodFilter(SpringBoot自动配置了)
- 页面中创建一个Post表单
- 再创建一个Input项,name="_method"值是我们指定的请求方式
添加一个吟唱的Input框
<input type="hidden" name="_method" value="put" th:if="${emp!=null}">
注意在SpringBoot2.xx版本之后这个SpringMVC的自动配置默认是不启动的
在配置文件中编写启动即可
spring.mvc.hiddenmethod.filter.enabled=true
表单提交时需要一个员工的id值
需要编写一个Input标签来传送一个id
<input type="hidden" name="id" th:if="${emp!=null}" th:value="${emp.id}">
删除功能实现
发送删除请求
在之前删除的按纽外部添加一个表单用于发送删除请求,当然请求方式是delete方式,使用delete请求方式和之前使用put方式方法差不多
<form id="deleteEmpForm" action="" method="post">
<input type="hidden" name="_method" value="delete">
<button type="submit" class="btn btn-sm btn-danger">删除</button>
</form>
编写接收请求的方法
@DeleteMapping("/emp/{id}")
public String deleteEmployee(
@PathVariable("id") Integer id
) {
employeeDao.delete(id);
return "redirect:/emps";
}
表单优化和小细节
小问题
因为每一个btn按钮外面都添加了一个表单,使得添加的按钮和删除的按钮排列成竖向了,这是因为form表单是块级元素,给其添加display=inline让他变成行内元素即可
优化
虽然这样解决了样式问题但是每一个员工都又多出一个表单,就显得冗余性很强,可以使用js代码优化一下让页面只使用一个表单
将表单提取到循环标签外在button标签上添加一个自定义属性del_url传输请求的地址便于之后以js获取
<button th:attr="del_uri=@{'/emp/' + ${emp.id}}" type="submit" class="btn btn-sm btn-danger deleteBtn">删除</button>
编写js
$(".deleteBtn").click(function () {
$("#deleteEmpForm").attr("action", $(this).attr("del_uri")).submit();
})
错误处理机制
SpringBoot默认的错误处理机制
默认效果:
- 返回一个默认的错误页面、
- 如果是其他客户端访问默认是响应一个json数据
原理:参照SpringBoot自动配置类ErrorMvcAutoConfiguration:错误处理的自动配置
给容器中添加了以下组件
-
DefaulErrorAttributes
-
BasicReeoeController
处理默认的/error请求,写个两种写法一个返回一个html页面另一个返回一个json数据 -
ErrorPageCustomlzer
@Value("${error.path:/error}")
private String path = "/error";系统出现错误后来到error请求进行处理:(web.xml)注册的作物页面规则
- DefaulErrorViewResolver
步骤:
一旦系统出现4xx或者5xx之类的错误:ErrorPageCustomlzer就会生效(定制错误的响应规则);就会来到/error请求就会被BasicReeoeController处理,会调DefaulErrorViewResolver来确定返回哪个页面
定制错误的页面
如何定制错误的页面
有模板引擎的情况下:error/状态码【将错误页面命名为错误状态码html放在error文件夹下】,发生此状态俺们的错误就会来到对应的页面;
页面名称写成4xx和5xx,来匹配每种类型的所有错误(优先匹配精确状态码.html)
页面能获取的信息:
- timestamp:时间戳
- status:状态码
- error:错误提示
- exception:异常对象
- message:异常消息
- errors:JSR303数据校验的错误都在这里
SpringBoot2.x版本默认是不带异常对象的,需要在配置文件中配置
server.error.include-exception=true
没有模板引擎:默认在静态资源文件夹下找
以上都没有:默认来到SpringBoot的错误处理页面
如何定制其他客户端的json数据
自定义异常处理&返回定制json数据
@ControllerAdvice
public class MyExceptionHandler {
// 浏览器和客户端返回的都是json数据
@ResponseBody
@ExceptionHandler
public Map<String, Object> handleException(Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("code", "user.notexist");
map.put("message", e.getMessage());
return map;
}
}
// 没有自适应效果...
转发到/error进行自适应效果处理
@ExceptionHandler
public String handleException(Exception e) {
Map<String, Object> map = new HashMap<>();
map.put("code", "user.notexist");
map.put("message", e.getMessage());
// 转发到/error
return "forward:/error";
}
这样虽然浏览器是走页面了但是走的是默认的SpringBoot页面,原因是状态码是200不能找到页面
在方法中添加设置状态码的语句即可,状态码在HttpServletRequest对象中可以设置
request.setAttribute("javax.servlet.error.status_code", 400);
将定制数据携带数据;
出现错误以后,会来到/error请求,会被BasicErrorController处理,响应出去的可以获取的数据是由getErrorAttrbutes的到的(是AbstractErrorController(ErrorController)规定的方法);
第一种:完全编写一个ErrorController的是现类(或者是编写AbstractErrorController的子类)放在容器中;
第二种:页面上能用的数据,或者是json返回能用的数据都是ErrorAttrbutes。getErrorAttrbutes的到
容器中DefaultErrorAttrlbutes.getErrorAttrbutes()默认进行错误处理的
自定义ErrorAttributes
@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
@Override
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
map.put("jelly", "漂亮的女孩");
return map;
}
}
怎么获取handleException中set的map数据呢?
在handleException中向request中set进去这个map,然后在getErrorAttributes方法中获取就可以了
request.setAttribute("ext", map);
获取
Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);
map.put("ext", ext);
这里的参数webRequest是RequestAttributes的一个子接口,所以调用RequestAttributes中的getAttribute获取request中的数据即可
getAttribute的两个参数第一个参数是获得的属性名,第二参数是从那个域中获取0代表request域
int SCOPE_REQUEST = 0;
int SCOPE_SESSION = 1;
1就是代表session域了
最终的效果
错误是自适应的,可以通过ErrorAttributes该表吖返回的内容
配置嵌入式Servlet容器
SpringBoot默认是使用的嵌入式Servlet容器(Tomcat)
问题:
如何定制和修改Servlet容器的相关配置
修改容器的配置(ServletProperties)
server.port=8081
// 通用的Servlet容器设置
server.xxxx
// Tomact
server.tomcat.xxx
EmbeddedServletContainerCutomizer
编写一个EmbeddedServletContainerCutomizer(嵌入式Servlet容器定制器),来修改Servlet容器配置在SpringBoot2.x中使用WebServerFactoryCustomizer接口来定制实现时候需要传入泛型**
@Bean
public WebServerFactoryCustomizer webServerFactoryCustomizer() {
return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>() {
@Override
public void customize(ConfigurableWebServerFactory factory) {
factory.setPort(8083);
}
};
}
Servlet Filter Listener
由于SpringBoot默认是以jar包的方式启动嵌入式的Servlet容器来启动SpringBoot的Web应用,没有web.xml文件。
注册三打组件用以下方式:
ServletRegistrationBean
// 注册三大组件
@Bean
public ServletRegistrationBean myServlet() {
ServletRegistrationBean<Servlet> servletServletRegistrationBean = new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
return servletServletRegistrationBean;
}
FilterRegistartionBean
@Bean
public FilterRegistrationBean myFilter() {
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new MyFilter());
filterFilterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
return filterFilterRegistrationBean;
}
ServletListenerResistrationBean
@Bean
public ServletListenerRegistrationBean myListener() {
ServletListenerRegistrationBean<Mylistener> listenerRegistrationBean = new ServletListenerRegistrationBean<>(new Mylistener());
return listenerRegistrationBean;
}
SpringBoot帮我们自动注册了SpringMVC的前段控制器;DispatcherServlet,就是这种方式
在SpringBoot中使用其他的Servlet容器
tomcat(默认使用)
引入Web的Starter默认就是使用tomcat
修改默认只需要在原来的web的Starter中移除到tomcat的Starter
然后在添加上需要的Servlet容器的Starter就可以了
jetty(长连接)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-tomcat</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<!--引入其他的Servlet容器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jetty</artifactId>
</dependency>
Undertow(不支持JSP)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-undertow</artifactId>
</dependency>
嵌入式Servlet容器自动配置原理
EmbeddedServletContainerAutoConfiguration:嵌入式的Servlet容器自动配置
在SpringBoot2.x中这个类被ServletWebServerFactoryConfiguration代替
@Configuration(
proxyBeanMethods = false
)
class ServletWebServerFactoryConfiguration {
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({Servlet.class, Tomcat.class, UpgradeProtocol.class})// 当前是否引入了Tomcat依赖
@ConditionalOnMissingBean(
value = {ServletWebServerFactory.class},
search = SearchStrategy.CURRENT
)// 判断当前容器中没有用户自己定义的ServletWebServerFactory:嵌入式的容器工厂:作用:来创建嵌入式的Servlet容器
static class EmbeddedTomcat {
EmbeddedTomcat() {
}
@Bean
TomcatServletWebServerFactory tomcatServletWebServerFactory(ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers, ObjectProvider<TomcatContextCustomizer> contextCustomizers, ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
factory.getTomcatConnectorCustomizers().addAll((Collection)connectorCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatContextCustomizers().addAll((Collection)contextCustomizers.orderedStream().collect(Collectors.toList()));
factory.getTomcatProtocolHandlerCustomizers().addAll((Collection)protocolHandlerCustomizers.orderedStream().collect(Collectors.toList()));
return factory;
}
}
}
ServletWebServerFactory(嵌入式容器工厂)
@FunctionalInterface
public interface ServletWebServerFactory {
// 获取嵌入式的Servlet容器
WebServer getWebServer(ServletContextInitializer... initializers);
}
WebServer:(嵌入式的Servlet容器)
以TomcatServletWebServerFactory为例
@Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
if (this.disableMBeanRegistry) {
Registry.disableRegistry();
}
// 创建一个Tomcat
Tomcat tomcat = new Tomcat();
// 配置Tomcat的基本环境
File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
Connector connector = new Connector(this.protocol);
connector.setThrowOnFailure(true);
tomcat.getService().addConnector(connector);
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
// 将配置好的Tomcat传入,返回一个WebServer:并且启动Tomcat服务器
return getTomcatWebServer(tomcat);
}
版本问题
老师用的1.x版本中有一个后置处理器,但是在2.x中好像是没有了。
嵌入式Servlet容器启动原理
什么时候创建嵌入式的容器工厂?什么时候获取嵌入式的Servlet容器并启动Tomcat
获取嵌入式的Servlet容器工厂:
- SpringBoot应用启动运行Run方法
- refreshContext(context):SpringBoot刷新IOC容器,就是创建IOC容器对象并初始化容器,创建容器中的每一个组件
- refresh(context):刷新刚才创建好的ioc容器
- onRefresh();web的ioc容器重写了onRefresh方法
- web容器会创建嵌入式的Servlet容器;createEmbeddedServletContainer
- 获取嵌入式的Servlet容器工厂
使用外置的Servlet容器
嵌入式Servlet容器:jar
优点:简单便携;
缺点:默认不支持JSP,优化定制比较复杂
外置的Servlet容器:外面安装一个Tomcat应用以war包打包
步骤:
- 必须创建一个War项目(利用Idea创建目录结构)
- 将嵌入式的Tomcat指定位provided
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
- 必须编写一个SpringBootServletInltializer的字类,调用configure方法
public class ServletInitializer extends SpringBootServletInltializer{
@Overrude
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
// 传入SpringBoot的主程序
return application.sources(SpringBoot04WebJspApplication.class);
}
}
- 启动服务器就可以使用;