inputFlashMap = RequestContextUtils.getInputFlashMap(request);
Object msg = inputFlashMap.get("msg");
System.out.println("msg消息 = " + msg);
return "index";
}
```
### RequestContextUtils
```java
WebApplicationContext context = RequestContextUtils.findWebApplicationContext(request);
context.getBeansWithAnnotation(Controller.class)
```
## 静态资源处理
### servlet容器默认servlet
在tomcat的conf文件夹下有一个web.xml里面对于default的配置如下,默认servlet通常用来处理静态资源
由于其url-pattern值为`/`,所以所有没有对应映射的请求就会由此默认servlet处理,包括对静态资源的请求
```xml
default
org.apache.catalina.servlets.DefaultServlet
debug
0
listings
false
1
default
/
```
### mvc添加默认servlet
```java
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class AppConfig implements WebMvcConfigurer {
/**
* 启用默认servlet的功能
* 这样就会注册一个处理/**地址的处理器,他的优先级最低
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// 默认去找servlet容器中名字为default的servlet去处理请求
configurer.enable();
//如果servlet容器中没有名字为default的servlet,该怎么办?
// configurer.enable("cj");
}
}
```
### mvc资源处理
```java
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class AppConfig implements WebMvcConfigurer {
/**
* 下面的方法与上面的方法可以同时存在,但建议只用下面的方法
*
* @param registry
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("/static/**")
.addResourceLocations("classpath:/public/");
}
}
```
## 全局异常处理
### 基本做法
实现步骤
- 创建类
- 类上加@ControllerAdvice
- 类里添加方法
- 方法上添加@ExceptionHandler注解,注解里写上它能处理的异常类型
- 方法的参数写上一个异常类型作为参数
- 方法的返回值与@RequestMapping修饰的方法能支持的返回类型一样,可以是String,ModelAndView等等
```java
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 1.异常处理方法的返回值与@RequestMapping修饰的方法的返回值类型是一样
* 2.方法的参数是可以没有的,如果有,基本上就是能处理异常的类型作为参数类型
*
* @return
*/
@ExceptionHandler(Throwable.class)
public String handleException(Throwable throwable) {
System.out.println("throwable-------");
return "error";
}
/**
* 这个方法是处理RuntimeException及其子类的异常
* 如果发生的异常有多个处理方法都可以处理,
* 会选最靠近自己的方法来处理异常
*
* @param throwable
* @return
*/
@ExceptionHandler(RuntimeException.class)
public String handleRuntimeException(RuntimeException throwable) {
System.out.println("runtime exception-----");
return "error";
}
}
```
### 视图找不到
下面代码由于index2.jsp这个文件,仍然会报404错误,这不属于全局异常能处理的范畴
```java
/**
* 这个方法返回index2,但没有对应的视图,会出现404
* 这个不归属于全局异常处理的范畴
*
* 全局异常处理的只是控制器方法
*
* @return
*/
@RequestMapping("/index2")
public String index2() {
System.out.println("演示找不到视图的情况---");
return "index2";
}
```
## json
### 添加依赖
```xml
com.fasterxml.jackson.core
jackson-databind
```
### 配置消息转换器
这一步是可选的,主要是处理日期格式的问题,建议在`extendMessageConverters`方法里配置
```java
/**
* 此方法是完全由自己配置各种转换器,不会采用任何mvc框架默认已有的转换器
*
* @param converters
*/
@Override
public void configureMessageConverters(List> converters) {
}
/**
* extend开头的方法,是在已有的转换器基础之上,额外在处理转换器
*
* @param converters
*/
@Override
public void extendMessageConverters(List> converters) {
// jackson的序列化主要是靠ObjectMapper
// 所以设置日期格式就需要像下面这样
ObjectMapper objectMapper = new ObjectMapper();
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
objectMapper.setDateFormat(dateFormat);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper);
converters.add(0, converter);
//建议用Builder的方式来配置
// Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
// ObjectMapper objectMapper1 = builder.dateFormat(new SimpleDateFormat("yyyy-MM-dd")).build();
}
```
### 编写控制器方法
ResponseBody注解可以写在方法上,也可以写在类上,写在类上等于此类中所有的方法上都加了这个注解
ResponseBody注解写在控制器类上的话,就可以用RestController注解取代ResponseBody注解与Controller注解
```java
@RequestMapping("/emp")
@ResponseBody
public EmpVO emp() {
EmpVO e = new EmpVO(11, "cj", new Date());
return e;
}
```
### 获取ajax数据
编写下面的控制器方法,在要把请求json数据转换为对象的方法参数添加@RequestBody注解即可
```java
@RequestMapping("/insert")
public ResponseVO insert(@RequestBody EmpVO empVO) {}
```
客户端传递如下的数据即可
```json
{
"id":100,
"username":"cj"
}
```
### 统一的数据响应
#### 统一的数据响应对象
```java
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResponseVO {
private int code;
private String msg;
private Object result;
}
```
##### 全局异常处理器
```java
@RestControllerAdvice //===ControllerAdvice+ ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseVO handleRuntime(RuntimeException re) {
ResponseVO responseVO = new ResponseVO(500, re.getMessage(), null);
return responseVO;
}
}
```
#### 某个正常响应方法
```java
@RequestMapping("/xxx")
@ResponseBody
public ResponseVO xxx() {
ResponseVO responseVO = new ResponseVO(200, "ok", true);
return responseVO;
}
```
### ajax案例
#### 查询所有的案例
```html
Title
index
```
***后端控制方法***
```java
@RequestMapping("/getAll")
@ResponseBody
public ResponseVO getAll() {
EmpVO e = new EmpVO(11, "cj", new Date());
EmpVO e2 = new EmpVO(12, "cj2", new Date());
EmpVO e3 = new EmpVO(13, "cj3", new Date());
ArrayList al = new ArrayList<>();
al.add(e);
al.add(e2);
al.add(e3);
Random random = new Random();
int j = random.nextInt(100);
if (j > 20) {
throw new RuntimeException("cucuo");
}
ResponseVO responseVO = new ResponseVO(200, "ok", al);
return responseVO;
}
```
***异常处理代码***
```java
@RestControllerAdvice //===ControllerAdvice+ ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseVO handleRuntime(RuntimeException re) {
ResponseVO responseVO = new ResponseVO(500, re.getMessage(), null);
return responseVO;
}
}
```
#### 增加案例
```html
Title
index
{{msg}}
```
***后端控制器方法***
```java
@RequestMapping("/insert")
@ResponseBody
public ResponseVO insert(@RequestBody EmpVO empVO) {
System.out.println(empVO);
ResponseVO responseVO = new ResponseVO(200, "ok", true);
return responseVO;
}
```
## 拦截器
### 编写拦截器
几个要点
- 实现HandlerInterceptor接口
- 三个方法含义见下面代码中的注释
```java
public class FirstInterceptor implements HandlerInterceptor {
/**
* 在控制器方法执行之前执行
*
* 此方法的返回值为true的时候表示放行
*
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("-----1 pre------");
return true;
}
/**
* 在控制器方法执行之后执行
*
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("-----1 post------");
}
/**
* 在控制器方法执行完毕,试图解析完毕,试图渲染(render)完毕之后才执行
*
* 3个接口方法,这一个是最后执行
*
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("------1 after------");
}
}
```
### 注册拦截器
几个要点
- addInterceptor方法就完成了拦截器的注册
- order方法控制拦截器之间的顺序,数字越小优先级越高
- excludePathPatterns用来添加排除路径模式,意思是符合这些模式的地址不经过拦截器处理
```java
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 顺序:值越小优先级越高
// registry.addInterceptor(new FirstInterceptor()).order(50);
// registry.addInterceptor(new SecondInterceptor()).order(100);
registry
.addInterceptor(new AuthenticationInterceptor())
.order(Ordered.HIGHEST_PRECEDENCE)
.excludePathPatterns("/login")
.excludePathPatterns("/static/**");
}
```
### 拦截器与过滤器

### 验证案例
***完成登录逻辑***
```java
@Controller
public class LoginController {
@GetMapping("/login")
public String loginView() {
return "login";
}
@PostMapping("/login")
public String doLogin(String username, String password, HttpSession session) {
if ("admin".equalsIgnoreCase(username) && "123".equalsIgnoreCase(password)) {
session.setAttribute("username", username);
return "index";
} else {
return "redirect:login";
}
}
}
```
***编写Auth注解***
```java
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {
}
```
***使用注解***
添加了Auth注解的表示需要验证之后才能访问
```java
@Auth
@RequestMapping("/a")
public String a() {
return "a";
}
@RequestMapping("/b")
public String b() {
return "b";
}
```
***验证拦截器***
```java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//第三个参数类型是HandlerMethod。里面有个成员method代表方法
HandlerMethod handlerMethod = (HandlerMethod) handler;
Method method = handlerMethod.getMethod();
boolean needAuth = method.isAnnotationPresent(Auth.class);
if (needAuth) {
HttpSession session = request.getSession();
Object username = session.getAttribute("username");
if (ObjectUtils.isEmpty(username)) {
response.sendRedirect("/login");
return false;
}
return true;
} else {
return true;
}
}
```
## 文件上传
### 配置DispatcherServlet
```xml
mvc
org.springframework.web.servlet.DispatcherServlet
c:/tmp
102400
102400000
1024000
```
### 表单
要素如下
- method必须是post
- enctype必须是multipart/form-data
- 有type为file的控件
```html
```
### 控制器方法
要素如下
- 参数类型是MultipartFile
- 参数名必须是表单file域的名字
- 建议用Path参数给transferTo,用File作为参数在jetty里会报异常
```java
@RequestMapping("/upload")
public String upload(MultipartFile myFile) {
//要用Path(java.nio),不能用File,因为jetty下会报文件找不到的异常
Path path = Paths.get("D:\\temp", myFile.getOriginalFilename());
try {
myFile.transferTo(path);
} catch (IOException e) {
e.printStackTrace();
}
return "index";
}
```
### MultipartResolver注册
如果像上面那样在控制器方法参数中直接编写MultipartFile参数的参数,那么是可以不用注册一个MultipartResolver
bean对象的,但如果是把此类型的数据作为一个类的属性,并把类作为控制器方法的参数,那么就必须注册MultipartResolver了,否则就会报不能转换的异常
```java
@Data
public class FileVO {
private MultipartFile myFile;
private int id;
private String name;
}
@RequestMapping("/uploadForAjax2")
@ResponseBody
public String uploadForAjax2(FileVO fileVO) {}
```
配置类注册MultipartResolver,名字必须是multipartResolver
```java
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class AppConfig implements WebMvcConfigurer {
@Bean
public MultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
return multipartResolver;
}
}
```
注册了MultipartResolver解析器之后对MultipartFile作为参数也没有坏的影响,所以,强烈建议注册此解析器
> MultipartFile作为参数是靠RequestParamMethodArgumentResolver去处理的,它可以不需要有MultipartResolver解析器
>
> 解析器的名字在DispatcherServlet中有定义名字
>
> ```java
> public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
> ```
### 上传多个文件
表单里多写几个file控件,名字必须是一样的
```html
```
在后台控制器方法里参数名与file控件名一样,类型是数组或者List都可以
```java
@RequestMapping("/upload2")
public String upload2(MultipartFile[] myFiles) {}
@RequestMapping("/upload3")
public String upload3(List myFiles) {}
```
### ajax文件上传
```html
```
> 前台用axios传递FormData类型的数据时,可以不用设置http 头部的content-type=multipart/form-data,因为axios会自动处理,也就是说上面的post请求可以变为
>
> ```js
> axios.post('http://localhost:8080/uploadForAjax', data)
> .then(res => {
> console.log(res);
> alert(res.data);
> });
> ```
>
>
后台写法
```java
@RequestMapping("/uploadForAjax")
@ResponseBody
public String uploadForAjax(MultipartFile myFile,String name) {
System.out.println("额外的表单数据:" + name);
Path path = Paths.get("D:\\temp", myFile.getOriginalFilename());
try {
myFile.transferTo(path);
} catch (IOException e) {
e.printStackTrace();
}
return "upload ok";
}
```
如果前端传递的数据非常多,那么后台可以用一个类封装一下,比如下面这样
```java
@Data
public class FileVO {
private MultipartFile myFile;
private int id;
private String name;
}
```
这样控制器方法就使用此类型作为参数即可
```java
@RequestMapping("/uploadForAjax2")
@ResponseBody
public String uploadForAjax2(FileVO fileVO) {
System.out.println("额外的表单数据:" + fileVO.getName());
MultipartFile myFile = fileVO.getMyFile();
Path path = Paths.get("D:\\temp", myFile.getOriginalFilename());
try {
myFile.transferTo(path);
} catch (IOException e) {
e.printStackTrace();
}
return "upload ok";
}
```
前端的代码保持不变,只是地址变成http://localhost:8080/uploadForAjax2 即可
## 文件下载
### 直接作为静态资源下载
```html
直接下载
```
对应静态资源配置(在配置类中)
```java
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/public/");
}
```
### 代码实现下载
```java
@RequestMapping("/download")
public ResponseEntity download(String filename) {
String path = "E:\\Image\\" + filename;
InputStreamSource source = new FileSystemResource(path);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(getMediaType(filename));
try {
//让浏览器以另存为的方式来下载文件,而不是直接打开
headers.setContentDispositionFormData("attachment", URLEncoder.encode(filename, "UTF-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
ResponseEntity responseEntity = new ResponseEntity<>(source, headers, HttpStatus.OK);
return responseEntity;
}
private MediaType getMediaType(String filename) {
//guessContentTypeFromName是从文件名猜测其内容类型,如果为null就猜测失败
String midiaType = URLConnection.guessContentTypeFromName(filename);
if (midiaType == null) {
midiaType = MediaType.APPLICATION_OCTET_STREAM_VALUE;
}
// 不要直接实例化MediaType,而要用parseMediaType方法
// 因为直接实例化对/符号没有进行转意操作
return MediaType.parseMediaType(midiaType);
}
```
## cors
参考https://www.ruanyifeng.com/blog/2016/04/cors.html理解什么是跨域请求
### 前端内容
```html
Document
{{emp.id}}
{{emp.empname}}
```
> 前端内容我放在案例中的resources下面了,文件夹名字叫corsdemo
>
> 用vscode打开此文件夹并以open with liver server的形式运行index.html文件就可以测试跨域情况了
### 后端内容
```java
@RestController
public class CorsDemoController {
@CrossOrigin
@RequestMapping("/data1")
public ResponseVO getData(){
EmpVO empVO = new EmpVO(100, "cj");
ResponseVO responseVO = new ResponseVO(200, "ok", empVO);
return responseVO;
}
@RequestMapping("/data2")
public ResponseVO getData2(){
EmpVO empVO = new EmpVO(200, "cj2");
ResponseVO responseVO = new ResponseVO(200, "ok", empVO);
return responseVO;
}
}
```
配置类
```java
@Configuration
@EnableWebMvc
@ComponentScan("com")
public class AppConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedMethods("GET","POST","OPTIONS","PUT","DELETE")
.allowedOrigins("http://localhost:5500","http://127.0.0.1:5500");
}
}
```
### CrossOrigin注解
它可以修饰在方法上,也可以修饰在控制器类上。修饰在类上时,等价于此控制器中的所有方法都添加了CrossOrigin注解
修饰在方法上,对于此方法来说是本地跨域配置,而修饰在类上的注解,对于此类里的方法来说是全局配置,对于配置项可以允许多个值得,原则上是会合并的,而对于配置项只能有一个值得,那么本地的配置会覆盖全局的配置
如果想让许多的控制器都支持跨域,可以给每个要跨域的控制器添加CrossOrigin注解,也可以在配置类里面进行全局配置,这样就不需要在类上加注解了,只在配置类配置就行了
不管是注解还是配置类里面进行配置,跨域的设置是有如下的一些默认值情况的
* allowOrigins=“*”
* allowMethods=“GET,POST,HEAD”,这3个方法就是简单请求支持的3个方法
* allowHeaders="所有的http header“
* maxAge=1800秒 == 30分钟
### 全局配置
```java
public void addCorsMappings(CorsRegistry registry) {
// registry.addMapping("/list"); //相当于在某个控制器方法上的requestMapping值为/list上面添加
//地址是/**,表示所有的请求都配置了跨域
registry.addMapping("/**")
.allowedOrigins("http://127.0.0.1:5500")
.allowedMethods("GET","POST","OPTIONS");
}
```
### 实现原理
上面讲的跨域设置内部其实都是靠`CorsInterceptor`拦截器实现的,除了上面的配置方式实现跨域处理以外,还可以通过配置过滤器`CorsFilter`的方式实现,需要知道的是过滤器的执行是在所有拦截器之前执行的
当你开启了跨域并且也编写了自己的拦截器,而且这个自定义的拦截器注册时配置的优先级高于跨域拦截器,那么如果你的拦截器的preHandle方法返回false会导致跨域拦截器得不到执行,这样就会导致跨域失败。
跨域拦截器在处理复杂请求时,由于会发起预请求,也就是Options请求,跨域拦截器会使用一个PreFlightHandler这个Handler来处理此预请求,此Handler不是HandlerMethod类型。这样当你自己编写的高优先级的拦截器的代码是下面这样编写时,会导致类型转换失败,因而会导致跨域拦截器失效
```java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
HandlerMethod handlerMethod = (HandlerMethod) handler;
return true;
}
```
自己编写的拦截器一般都是针对HandlerMethod这种Handler来处理的,所以最好是编写下面的逻辑以免导致跨域拦截器执行不了
```java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if(!(handler instanceOf HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
return true;
}
```
还有,如果你的高优先级的拦截器像下面这样写,通常也会导致跨域拦截器失效,由于可能进入到else里面返回false导致跨域拦截器不执行让跨域失败
```java
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String someValue = request.getHeader("key");
if("some value".equals(someValue)){
//做一些事情
return true;
}else{
//直接输出响应
return false;
}
}
```
上面说的高优先级的自定义拦截器导致跨域拦截器失效的情况,如果跨域处理不是通过拦截器的方式,而是通过CorsFilter实现跨域就不会出现上述失效的情况,因为过滤器总是在拦截器之前执行
> 关于PreFlightHandler可以在AbstractHandlerMapping类的getCorsHandlerExecutionChain方法的源代码中看到使用情况
## restful
### 什么是restful
http://www.ruanyifeng.com/blog/2011/09/restful.html
https://www.runoob.com/w3cnote/restful-architecture.html
### 地址格式
- 地址都是名字描述
- 地址如果单词过长就用连字符分开 ,比如restful-architecture
- 地址都是全小写
- 使用查询字符串(?)作为额外的参数
下面是一些比较好的地址设计
- https://github.com/git
- https://github.com/git/git
- https://github.com/git/git/blob/master/block-sha1/sha1.h
- https://github.com/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08
- https://github.com/git/git/pulls
- https://github.com/git/git/pulls?state=closed
- http://www.ruanyifeng.com/blog/2011/09/restful.html
### Swagger
https://www.cnblogs.com/liusuixing/p/14427568.html
***添加依赖***
```xml
io.springfox
springfox-swagger-ui
2.9.2
io.springfox
springfox-swagger2
2.9.2
```
***编写swagger配置类***
```java
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.enable(true)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
/**
* ApiInfo:主要返回接口和接口创建者的信息
*/
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("电影系统")
.description("模仿优酷")
.contact(new Contact("联系方式", "https://www.nfit.com/cj/", "123456@qq.com"))
.version("v1.0")
.build();
}
}
```
***导入swagger配置类并处理静态资源***
swagger是一个网页用来显示api信息,这些页面都是静态资源,所以需要配置这些静态资源的处理者,以便可以正确找到
```java
@Configuration
@EnableWebMvc
@ComponentScan("com")
@Import(SwaggerConfig.class) //这里导入
public class AppConfig implements WebMvcConfigurer {
//这里处理静态资源
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/public/");
registry.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}
}
```
***访问***
启动项目后,访问地址:http://localhost:8080/swagger-ui.html 即可
### 后端代码
```java
@RestController
@RequestMapping("/emps")
public class EmpController {
@ApiOperation(value = "获取所有员工", httpMethod = "GET")
@GetMapping("")
public ResponseVO> getAll() {
ResponseVO> responseVO = new ResponseVO<>(200, "ok", list);
return responseVO;
}
@PostMapping("")
public ResponseVO insert(@RequestBody EmpVO empVO) {
return new ResponseVO<>(200, "ok", true);
}
}
```
前端就是常规的ajax请求处理,记得最好是传递json数据就可以了
### 异常处理
```java
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(RuntimeException.class)
public ResponseVO handleRuntimeException(RuntimeException re){
return new ResponseVO(500, re.getMessage(), null);
}
}
```
## 父子容器
web.xml中像下面这样配置就有父子spring容器的效果
```xml
org.springframework.web.context.ContextLoaderListener
contextConfigLocation
config.ParentConfig
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
mvc
org.springframework.web.servlet.DispatcherServlet
contextClass
org.springframework.web.context.support.AnnotationConfigWebApplicationContext
contextConfigLocation
config.AppConfig
mvc
/
```
## 动态注册
动态注册指的是servlet、listener、filter三类组件的动态注册,这是servlet 3推出的新技术,servlet 3之前提供了2种注册技术
- web.xml中进行配置
- 注解的形式
> 不能在一个servlet里面注册另一个servlet,会抛出IllegalStateException
>
>
>
> 下面是addServlet方法上的注释,表明什么时候会抛出IllegalStateException ,也就是说只能在ServletContext还没有初始化时才能动态注册
>
> IllegalStateException if this ServletContext has already been initialized
### 基本用法一
编写ServletContainerInitializer接口的实现类
```java
public class MyServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set> c, ServletContext ctx) throws ServletException {
ServletRegistration.Dynamic registration = ctx.addServlet("xxxx", "com.controller.SecondServlet");
registration.addMapping("/second");
//这里可以给其配置multipart相关内容
//registration.setMultipartConfig(new MultipartConfigElement());
}
}
```
在类路径下创建META-INF文件夹,在此文件夹下再建services文件夹,最后在services文件夹下建立一个文件,名字是ServletContainerInitializer接口的全称

在此文件里写上上面实现类的全称,如果有多个实现类,就一行一个
```
com.MyServletContainerInitializer
```
### 基本用法二
编写下面的实现ServletContainerInitializer实现类,并在其上面添加@HandlesTypes直接,此注解一般指定一个接口类型
```java
@HandlesTypes(MyAppInitializer.class)
public class MyServletContainerInitializer implements ServletContainerInitializer {}
```
接口是可以任意设计的,可以设计用来注册servlet也可以不
```java
public interface MyAppInitializer {
void onBegin(ServletContext sc);
}
```
这样作为MyAppInitializer接口的实现类就只需要实现这个接口,并利用ServletContext参数来完成servlet的注册,而不要在文件里面进行配置了,常见的实现类如下
```java
public class MyAppInit1 implements MyAppInitializer {
@Override
public void onBegin(ServletContext sc) {
System.out.println("------my app init 1");
sc.addServlet("two", "com.controller.SecondServlet").addMapping("/second");
}
}
```
spring
mvc框架中的SpringServletContainerInitializer实现了ServletContainerInitializer接口,其在HandlesTypes注解中指定的接口是WebApplicationInitializer
## 无webxml配置
无webxml配置指的是不在web.xml中进行DispatcherServlet的配置,这是利用servlet 3.0退出的动态注册能力进行的,spring
mvc已经实现了这个,基本只需要继承`AbstractAnnotationConfigDispatcherServletInitializer`来实现配置
### SystemConfig
```java
public class SystemConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
//如果你不想做父子容器,就只需要在下面的方法返回一个配置类即可
// 然后让getServletConfigClasses方法返回null
@Override
protected Class>[] getRootConfigClasses() {
return new Class[]{AppConfig.class};
}
@Override
protected Class>[] getServletConfigClasses() {
return new Class[]{MvcConfig.class};
//return null;
}
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
registration.setMultipartConfig(new MultipartConfigElement("c:/tmp", 10240000, 102400000, 1024000));
}
}
```
### AppConfig
```java
@Configuration
@PropertySource("classpath:db.properties")
@ComponentScan(value = {"com"},
excludeFilters = {
@ComponentScan.Filter(classes = {Controller.class})
})
@EnableTransactionManagement
@MapperScan("com.dao")
public class AppConfig {
//1.DataSource,2.SqlSessionFactory(日志,插件),3事务管理器
@Autowired
private DbConfig dbConfig;
// region dataSource
@Bean
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl(dbConfig.getUrl());
dataSource.setUsername(dbConfig.getUsername());
dataSource.setPassword(dbConfig.getPassword());
dataSource.setDriverClassName(dbConfig.getDriverClassName());
return dataSource;
}
// endregion
//region SqlSessionFactory
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
PathMatchingResourcePatternResolver loader = new PathMatchingResourcePatternResolver();
Resource[] resource = loader.getResources("classpath*:mappers/**/*.xml");
factoryBean.setMapperLocations(resource);
PageInterceptor pageInterceptor = pageInterceptor();
factoryBean.setPlugins(pageInterceptor);
factoryBean.setTypeAliasesPackage("com.entity");
factoryBean.setConfiguration(configuration());
return factoryBean.getObject();
}
private org.apache.ibatis.session.Configuration configuration() {
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setLogImpl(StdOutImpl.class);
return configuration;
}
private PageInterceptor pageInterceptor() {
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("reasonable", "true");
properties.setProperty("helperDialect", "mysql");
pageInterceptor.setProperties(properties);
return pageInterceptor;
}
// endregion
//region 事务管理器
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource());
return transactionManager;
}
//endregion
}
```
### MvcConfig
```java
@Configuration
@EnableWebMvc
@ComponentScan("com.controller")
public class MvcConfig implements WebMvcConfigurer {
// 视图解析器其实可以不用配置
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/", ".jsp");
}
// 前后端分析的情况下,下面的配置也可以不需要
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/public/");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowCredentials(true)
.allowedMethods("GET", "POST", "DELETE", "PUT", "OPTIONS", "HEAD")
.allowedOrigins("http://127.0.0.1:5500");
}
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new DateFormatter("yyyy-MM-dd"));
}
@Override
public void extendMessageConverters(List> converters) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder
.json()
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.build();
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(objectMapper);
converters.add(0, converter);
}
}
```
# 附录
## UriComponentsBuilder
## 获取所有的请求地址
https://blog.csdn.net/weixin_42290901/article/details/115864024
https://blog.csdn.net/kkgbn/article/details/74455702