目录标题
- 一、Asynchronous Requests(异步请求)
- (一)阻塞和非阻塞,同步和异步
- (二)DeferredResult
- (三)Callable
- 二、跨域请求CORS
- (一)实现跨域请求的方式
- (二)判断是否跨域
- (三)@CrossOrigin实现跨域
- (四)@CrossOrigin 属性
- (五)Java config实现跨域
- (六)XML 配置实现跨域
- (七)Spring Security 完全CORS支持
- 三、HTTP缓存
- 四、MVC Config
- (一)addFormatters(类型转换)
- (二)getValidator(校验、验证)
- (三)addInterceptors(拦截器)
- (四)configureContentNegotiation(内容协商)
- (五)configureAsyncSupport(异步请求配置)
- (六)addViewControllers(视图控制器)
- (七)configureViewResolvers(视图解析器)
- (八)addResourceHandlers(静态资源处理器)
- (九)其他
- 五、过滤器
- 六、RestTemplate
- (一)get
- (二)post
- (三)Exchange
- 七、WebSocket
- 八、模板引擎
一、Asynchronous Requests(异步请求)
(一)阻塞和非阻塞,同步和异步
-
同步异步指的是通信模式,即被调用者结果返回时通知进程/线程的一种通知机制。涉及回调函数
-
阻塞和非阻塞指的是调用结果返回前进程/线程的状态。涉及线程挂起
(二)DeferredResult
DeferredResult 方式主要通过setResult(“123”);方法实现异步。当设置了setResult(“123”);后。才会响应请求结果给用户。可以通过其他线程设置Result的值。
@RequestMapping(value = "/async/demo")
@ResponseBody
public DeferredResult<String> async(){
// 创建 DeferredResult,设置超时时间 60s。通过构造函数可以设置超时时间。new DeferredResult<>((long)(60*1000));
DeferredResult<String> deferredResult = new DeferredResult<>();
new Thread(()->{
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
deferredResult.setResult("123");
System.out.println("DeferredResult设置值成功。可以响应请求了。");
}).start();
System.out.println("DeferredResult中有值才会响应请求:"+deferredResult.hasResult());
System.out.println("主线程结束,异步等待DeferredResult值设置。。。end!!!");
return deferredResult;
}
值得注意的是:setResult(“index”); 可以返回一个视图。去掉@ResponseBody 注解。会返回视图。
返回一个页面:
@RequestMapping(value = "/async/demo")
//@ResponseBody
public DeferredResult<String> async(){
// 创建 DeferredResult,设置超时时间 60s。通过构造函数可以设置超时时间。new DeferredResult<>((long)(60*1000));
DeferredResult<String> deferredResult = new DeferredResult<>();
new Thread(()->{
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//index是一个页面
deferredResult.setResult("index");
System.out.println("DeferredResult设置值成功。可以响应请求了。");
}).start();
System.out.println("DeferredResult中有值才会响应请求:"+deferredResult.hasResult());
System.out.println("主线程结束,异步等待DeferredResult值设置。。。end!!!");
return deferredResult;
}
(三)Callable
Callable 的使用方式与DeferredResult差不多,只有子线程中return了才会响应给用户。同样也能返回一个视图。
@ResponseBody
@RequestMapping("/async02")
public Callable<String> async01() {
System.out.println("主线程start...." + Thread.currentThread()+"-"+System.currentTimeMillis());
Callable<String> callable = new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("callable线程start...." + Thread.currentThread() + "-" + System.currentTimeMillis());
Thread.sleep(3000);
System.out.println("callable线程end...." + Thread.currentThread() + "-" + System.currentTimeMillis());
return "ok";
}
};
System.out.println("主线程end...." + Thread.currentThread()+"-"+System.currentTimeMillis());
return callable;
}
二、跨域请求CORS
Spring MVC允许你处理CORS(跨源资源共享)。
(一)实现跨域请求的方式
实现跨域请求的方式(让url支持跨域请求。):配置xml、java config 和使用注解。
(二)判断是否跨域
(三)@CrossOrigin实现跨域
@RestController
@RequestMapping("/account")
public class AccountController {
@CrossOrigin
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
@CrossOrigin 注解支持类级别和方法级别。
//表示只有http://domain2.com这个域能调用以下url
@CrossOrigin(origins = "http://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
(四)@CrossOrigin 属性
- origins
特定来源的允许来源列表,例如“https://domain1.com“,或”*“表示所有原点。
飞行前实际CORS请求的访问控制允许源响应标头中列出了匹配的源。
默认情况下,允许所有原点。
注意:CORS检查使用“Forwarded”(RFC 7239)、“X-Forwarded-Host”、“X-Forwarded-Port”和“X-Forward-Proto”标头(如果存在)中的值,以反映客户端发起的地址。考虑使用ForwardedHeaderFilter,以便从中心位置选择是提取和使用,还是丢弃此类标头。有关此筛选器的更多信息,请参阅Spring Framework参考。 - allowedHeaders
实际请求中允许的请求头列表,可能为“*”以允许所有头。
允许的标头列在飞行前请求的访问控制允许标头响应标头中。
根据CORS规范,如果头名称是缓存控制、内容语言、过期、上次修改或Pragma之一,则不需要列出。
默认情况下,允许所有请求的标头。 - exposedHeaders
用户代理将允许客户端访问实际响应的响应标头列表,而不是“简单”标头,即缓存控制、内容语言、内容类型、过期、上次修改或Pragma,
暴露的标头列在实际CORS请求的访问控制暴露标头响应标头中。
特殊值“*”允许为非认证请求公开所有标头。
默认情况下,没有显示标题。 - maxAge
飞行前响应的缓存持续时间的最大期限(秒)。
此属性控制飞行前请求的Access Control Max Age响应标头的值。
将其设置为合理的值可以减少浏览器所需的飞行前请求/响应交互的数量。负值表示未定义。
默认设置为1800秒(30分钟)。 - allowCredentials
浏览器是否应向带注释的端点发送凭据,如cookie以及跨域请求。已配置的值设置在飞行前请求的访问控制允许凭据响应标头上。
注意:请注意,此选项与配置的域建立了高度信任,并通过暴露敏感的用户特定信息(如cookie和CSRF令牌)增加了web应用程序的表面攻击。
默认情况下,未设置此项,在这种情况下,也未设置Access Control Allow Credentials标头,因此不允许使用凭据。 - methods
支持的HTTP请求方法列表。(get\post等)
默认情况下,支持的方法与控制器方法映射到的方法相同。
(五)Java config实现跨域
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
//这里配置的东西和注解的属性对应的意思一样。
registry.addMapping("/api/**")
.allowedOrigins("http://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
// Add more mappings...
}
}
(六)XML 配置实现跨域
<mvc:cors>
<mvc:mapping path="/api/**"
allowed-origins="http://domain1.com, http://domain2.com"
allowed-methods="GET, PUT"
allowed-headers="header1, header2, header3"
exposed-headers="header1, header2" allow-credentials="true"
max-age="123" />
<mvc:mapping path="/resources/**"
allowed-origins="http://domain1.com" />
</mvc:cors>
(七)Spring Security 完全CORS支持
三、HTTP缓存
HTTP 缓存,对于前端的性能优化方面来讲,是非常关键的,从缓存中读取数据和直接向服务器请求数据,完全就是一个在天上,一个在地下。
我们最熟悉的是 HTTP 服务器响应返回状态码 304,304 代表表示告诉浏览器,本地有缓存数据,可直接从本地获取,无需从服务器获取浪费时间
。
@RestController
public class HttpCacheController {
private int i = 0;
private int version = 0;
@GetMapping("/httpCache")
public ResponseEntity<String> httpCache() {
i++;
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.SECONDS))
// 如果版本不变,那么不会刷新body的值
.eTag("version"+version)
.body("i="+i);
}
@GetMapping("/setVersion/{version}")
public String setVersion(@PathVariable int version) {
this.version = version;
return "version"+version;
}
}
第一次请求:响应码:200。响应结果i=1
再次请求:响应码 304 。响应结果i=1 (正常是i=2)
改变version。
再次请求:响应码 200。响应结果i=3 (刷新body)
四、MVC Config
Java config 配置 spring mvc 有两种方式一种是实现 WebMvcConfigurer 接口(推荐)
;一种是继承WebMvcConfigurerAdapter(已废弃)
。
@Configuration
//@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
注意 如果使用了这个注解@EnableWebMvc,那么会默认取消spring boot一些默认配置,也就是完全接管mvc配置。一般不使用这个注解。不用这个注解会叠加配置,优先使用自己配置的。
(一)addFormatters(类型转换)
类型转换方式有:
- 通过数据绑定@InitBinder注解。
参考上一篇内容的数据绑定@InitBinder - 通过java config 配置mvc
/**
* @author lihua
* @date 2022/12/28 11:38
**/
@Configuration
//@EnableWebMvc
public class WebConfig implements WebMvcConfigurer{
@Override
public void addFormatters(FormatterRegistry registry) {
//registry.addParser();
// registry.addPrinter();
registry.addFormatter(new Formatter<User>() {
@Override
public String print(User object, Locale locale) {
System.out.println(object.toString());
return object.toString();
}
@Override
public User parse(String text, Locale locale) throws ParseException {
User user =new User();
//将id-1|name-lihua解析为user
System.out.println(text);
//这里的|需要转义
String[] split = text.split("\\|");
for (String s : split) {
String[] split1 = s.split("-");
if ("id".equals(split1[0])){
user.setId(split1[1]);
}else if ("name".equals(split1[0])){
user.setName(split1[1]);
}else {
}
}
return user;
}
});
}
}
class User{
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
@RestController
class TestConfigController{
//http://localhost:8080/testAddFormatters/id-1|name-lihua
@GetMapping("/testAddFormatters/{user}")
public User testAddFormatters(@PathVariable("user") User user){
if(Objects.isNull(user)){
return null;
}
return user;
}
}
- 通过注解@NumberFormat,@DateTimeFormat
@DateTimeFormat:
//标注到实体类上,或者controller的方法参数上
class User{
private String id;
private String name;
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
private Date date;
}
//http://localhost:8080/testAddFormatters/2023-1-11 10:54:20
@GetMapping("/testAddFormatters/{loginTime}")
public Date testAddFormatters(@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") @PathVariable Date loginTime){
Date date = loginTime;
return date;
}
注意这个注解是将前台传入的时间字符串转换成Date类型。不能将Data类型转换成时间字符串给前台使用,如果你需要转换成时间字符串给前台,你可以试试@JsonFormat(pattern=“yyyy-MM-dd”,timezone = “GMT+8”)注解。
-
注解@JsonFormat主要是后台到前台的时间格式的转换
-
注解@DataFormAT主要是前后到后台的时间格式的转换
@NumberFormat:
@NumberFormat可以应用于任何JDK Number类型,如Double和Long。
//在实体类上使用
class User{
private String id;
private String name;
@NumberFormat(style= NumberFormat.Style.NUMBER,pattern="#,###")
private int total;
@NumberFormat(style= NumberFormat.Style.PERCENT)
private double discount;
@NumberFormat(style= NumberFormat.Style.CURRENCY)
private double money;
}
//在controller方法上使用,http://localhost:8080/testAddFormatters2/5,500,0
@GetMapping("/testAddFormatters2/{total}")
public @NumberFormat(style= NumberFormat.Style.NUMBER,pattern="#,###") String testAddFormatters2(@NumberFormat(style= NumberFormat.Style.NUMBER,pattern="#,###") @PathVariable Long total){
System.out.println(total);
return"5,500";
}
(二)getValidator(校验、验证)
没有理解如果使用,官网介绍较少。估计跟Spring Boot集成的hibernate validator框架用法一样。
过滤器和拦截器的区别:
1.过滤器是servlet中的对象,拦截器是框架中的对象
2.过滤器实现Filter接口
对象,拦截器是实现HandleInterceptor
3.过滤器是用来设置request,response参数、属性,侧重对数据的过滤
;拦截器是用来验证请求的,能截断请求
。
4.过滤器是在拦截器之前执行的
5.过滤器是tomcat服务器创建的对象,拦截器是springmvc容器创建的对象
6.过滤器是一个执行时间点;拦截器是三个执行时间点
7.过滤器可以处理jsp、js、html等;拦截器是侧重拦截Controller的对象,如果你的请求不能被DispatcherServlet接收,这个请求不会执行拦截器的内容
8.拦截器拦截普通类方法执行,过滤器过滤servlet请求响应
(三)addInterceptors(拦截器)
/**
* @author lihua
* @date 2022/12/28 11:38
**/
@Configuration
//@EnableWebMvc
public class WebConfig implements WebMvcConfigurer{
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("1");
//当返回true时才,请求才能通过
String userName=null;
Cookie[] cookies = request.getCookies();
for (Cookie cookie : cookies) {
if("userName".equals(cookie.getName())){
userName= cookie.getValue();
break;
}
}
String isLogin = (String) request.getSession().getAttribute("login-" + userName);
if(Objects.isNull(isLogin)){
//重定向
response.sendRedirect("/mvcConfig/login");
return false;
}
//返回true才会执行请求对应的controller 处理方法。
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("2");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("3");
}
}).addPathPatterns("/**").excludePathPatterns("/mvcConfig/index","/mvcConfig/login").order(1); //拦截除了index的所有请求
}
}
@Data
@ToString
class User{
private String id;
private String userName;
}
@Controller
@RequestMapping("/mvcConfig")
class TestConfigController{
@GetMapping("/login")
public String loginIndex(){
return "login";
}
@GetMapping("/index")
public String index(){
return "index";
}
@PostMapping("/login")
@ResponseBody
public String login(User user,HttpServletRequest request){
if(Objects.isNull(user)){
user = new User();
user.setUserName("lihua");
}
Cookie cookie = new Cookie("userName",user.getUserName());
cookie.setMaxAge(60*60*24);
request.getSession().setAttribute("login-"+user.getUserName(),"true");
return "ok";
}
}
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>login</title>
</head>
<body>
<h1>login-index</h1>
<form action="/mvcConfig/login" method="post">
First name: <input type="text" name="userName"><br>
Last name: <input type="text" name="lname"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
配置拦截器时可以通过以下方法配置拦截规则。
- order:拦截器执行顺序,数值越小越早执行。
- excludePathPatterns:哪些路径不需要拦截,比如/index, /login(首页和登录界面)
注意可以使用通配符*、** ,多个路径通过逗号(,)分隔
。 - addPathPatterns:拦截哪些。
注意可以使用通配符*、** ,多个路径通过逗号(,)分隔
。
(四)configureContentNegotiation(内容协商)
理解HTTP内容协商
客户端在请求时可以与服务端协商需要返回的内容类型(application/json、text/html、application/xml等)。
1、客户端指定内容类型的方式有后缀 .json . xml(如:http://localhost:8080/mvcConfig/contentNegotiation
.json
;http://localhost:8080/mvcConfig/contentNegotiation.html
)
2、请求参数 (如:http://localhost:8080/mvcConfig/contentNegotiation?format=xml
)
3、 HTTP请求头Accept
4、其他方式
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
// 自定义策略
configurer.favorParameter(true)// 是否开启请求参数来决定mediaType,默认false
.ignoreAcceptHeader(true)// 不检查Accept请求头
.parameterName("mediaType") //指定一个名字用来接收内容类型(不设置默认是:format) http://localhost:8080/mvcConfig/contentNegotiation?mediaType=json
.defaultContentType(MediaType.APPLICATION_JSON)// 设置默认的MediaType
.mediaType("html", MediaType.TEXT_HTML)// 请求以.html结尾的会被当成MediaType.TEXT_HTML
.mediaType("json", MediaType.APPLICATION_JSON)// 请求以.json结尾的会被当成MediaType.APPLICATION_JSON
.mediaType("xml", MediaType.APPLICATION_ATOM_XML);// 请求以.xml结尾的会被当成MediaType.APPLICATION_ATOM_XML
}
(五)configureAsyncSupport(异步请求配置)
可以结合Asynchronous Requests(异步请求)一起使用
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
// 注册callable拦截器
configurer.registerCallableInterceptors(new CallableProcessingInterceptor() {
@Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, Callable<T> task) throws Exception {
}
@Override
public <T> void preProcess(NativeWebRequest request, Callable<T> task) throws Exception {
}
@Override
public <T> void postProcess(NativeWebRequest request, Callable<T> task, Object concurrentResult) throws Exception {
}
@Override
public <T> Object handleTimeout(NativeWebRequest request, Callable<T> task) throws Exception {
return null;
}
@Override
public <T> Object handleError(NativeWebRequest request, Callable<T> task, Throwable t) throws Exception {
return null;
}
@Override
public <T> void afterCompletion(NativeWebRequest request, Callable<T> task) throws Exception {
}
});
// 注册deferredResult拦截器
configurer.registerDeferredResultInterceptors(new DeferredResultProcessingInterceptor() {
@Override
public <T> void beforeConcurrentHandling(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
}
@Override
public <T> void preProcess(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
}
@Override
public <T> void postProcess(NativeWebRequest request, DeferredResult<T> deferredResult, Object concurrentResult) throws Exception {
}
@Override
public <T> boolean handleTimeout(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
return false;
}
@Override
public <T> boolean handleError(NativeWebRequest request, DeferredResult<T> deferredResult, Throwable t) throws Exception {
return false;
}
@Override
public <T> void afterCompletion(NativeWebRequest request, DeferredResult<T> deferredResult) throws Exception {
}
});
// 异步请求设置默认超时时间,单位毫秒
configurer.setDefaultTimeout(1000);
// 设定异步请求线程池callable等, spring默认线程不可重用
configurer.setTaskExecutor(new ThreadPoolTaskExecutor());
}
(六)addViewControllers(视图控制器)
- 如果只需要实现简单的
页面跳转
,可以通过配置addViewControllers 实现。不需要写一些controller专门用来跳转页面。比如:请求路径/index
只需要跳转到index.html
页面,不需要处理业务逻辑,那么你可以试试这个配置。 - 快速配置
重定向
- 简单配置一个路径只
返回一个状态码
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//重定向
registry.addRedirectViewController("/mvcConfig/index","/index");
//请求/mvcConfig/login 返回login.html视图(绑定视图)
registry.addViewController("/mvcConfig/login").setViewName("login");
//访问路径返回指定状态码,Get请求
registry.addStatusController("/mvcConfig/code", HttpStatus.OK);
}
(七)configureViewResolvers(视图解析器)
官网配置jsp视图解析器的一个例子:
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
//允许使用ContentNegotiatingViewResolver前置所有其他配置的视图解析器,并根据客户端请求的媒体类型(例如在Accept标头中)在所有选定视中进行选择。如果多次调用,则提供的默认视图将添加到可能已配置的任何其他默认视图中。使用Jackson2ObjectMapperBuilder提供的默认配置构建一个新的MappingJackson2JsonView,并将内容类型设置为application/json。
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}
registry.jsp(); 源码如下
:
public UrlBasedViewResolverRegistration jsp() {
return jsp("/WEB-INF/", ".jsp");
}
public UrlBasedViewResolverRegistration jsp(String prefix, String suffix) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(prefix);
resolver.setSuffix(suffix);
this.viewResolvers.add(resolver);
return new UrlBasedViewResolverRegistration(resolver);
}
如果你想要配置
thymeleaf 模板引擎的视图解析器
,spring boot项目推荐你在配置文件:application.properties
中配置。常见配置如下:
application.properties
# THYMELEAF (ThymeleafAutoConfiguration)
# 开启模板缓存(默认值: true )
spring.thymeleaf.cache=true
# 检查模板是否存在,然后再呈现
spring.thymeleaf.check-template=true
# 检查模板位置是否正确(默认值 :true )
spring.thymeleaf.check-template-location=true
#Content-Type 的值(默认值: text/html )
spring.thymeleaf.content-type=text/html
# 开启 MVC Thymeleaf 视图解析(默认值: true )
spring.thymeleaf.enabled=true
# 模板编码
spring.thymeleaf.encoding=UTF-8
# 要被排除在解析之外的视图名称列表,⽤逗号分隔
spring.thymeleaf.excluded-view-names=
# 要运⽤于模板之上的模板模式。另⻅ StandardTemplate-ModeHandlers( 默认值: HTML5)
spring.thymeleaf.mode=HTML5
# 在构建 URL 时添加到视图名称前的前缀(默认值: classpath:/templates/ )
spring.thymeleaf.prefix=classpath:/templates/
# 在构建 URL 时添加到视图名称后的后缀(默认值: .html )
spring.thymeleaf.suffix=.html
如果你需要java config的方式配置
可以参考下面:
@Configuration
@EnableWebMvc
@ComponentScan
public class WebViewConfig implements WebMvcConfigurer {
/**
* @Description: 注册jsp视图解析器
*/
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/"); //配置放置jsp文件夹
resolver.setSuffix(".jsp");
resolver.setViewNames("jsp/*"); //重要 setViewNames 通过它识别为jsp页面引擎
resolver.setOrder(2);
return resolver;
}
/**
* @Description: 注册html视图解析器
*/
@Bean
public ITemplateResolver templateResolver() {
SpringResourceTemplateResolver templateResolver = new SpringResourceTemplateResolver();
templateResolver.setTemplateMode("HTML");
templateResolver.setPrefix("classpath:/templates/");
templateResolver.setSuffix(".html");
templateResolver.setCharacterEncoding("utf-8");
templateResolver.setCacheable(false);
return templateResolver;
}
/**
* @Description: 将自定义tml视图解析器添加到模板引擎并主持到ioc
*/
@Bean
public SpringTemplateEngine templateEngine() {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver());
return templateEngine;
}
/**
* @Description: Thymeleaf视图解析器配置
*/
@Bean
public ThymeleafViewResolver viewResolverThymeLeaf() {
ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
viewResolver.setTemplateEngine(templateEngine());
viewResolver.setCharacterEncoding("utf-8");
viewResolver.setViewNames(new String[]{"thymeleaf"});
viewResolver.setOrder(1);
return viewResolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* @Description: 配置静态文件映射
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/**").addResourceLocations("/WEB-INF/static/");
}
参考Springboot2.x配置thymeleaf和jsp双视图解析器
(八)addResourceHandlers(静态资源处理器)
静态资源处理器可以将请求的url解析成静态文件(.txt、.jpg、.mp4等)。比如:访问http://localhost:8080/file/yes.png
就能获取到 C盘下的一张图片C:/file/yes.png
。
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/file/**").addResourceLocations("file:C:/file/");
}
注意:配置好静态资源处理器后,会存在跨域问题。
跨域是无法访问到静态资源
的。可以参考Java config实现跨域 解决。
配置静态资源访问路径的跨域支持可能因为拦截器失效。参考以下解决内容来源
@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
// 跨域配置
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "HEAD", "DELETE", "OPTION")));
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
// 有多个filter时此处可设置改CorsFilter的优先执行顺序,保证CorsFilter在其他过滤器之前执行(避免其他过滤器执行异常,导致CorsFilter没执行,从而导致跨域失效)
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
(九)其他
SpringBoot配置接口:WebMvcConfigurer
五、过滤器
过滤器和拦截器的区别:
1.过滤器是servlet中的对象,拦截器是框架中的对象
2.过滤器实现Filter接口
对象,拦截器是实现HandleInterceptor
3.过滤器是用来设置request,response参数、属性,侧重对数据的过滤
;拦截器是用来验证请求的,能截断请求
。
4.过滤器是在拦截器之前执行的
5.过滤器是tomcat服务器创建的对象,拦截器是springmvc容器创建的对象
6.过滤器是一个执行时间点;拦截器是三个执行时间点
7.过滤器可以处理jsp、js、html等;拦截器是侧重拦截Controller的对象,如果你的请求不能被DispatcherServlet接收,这个请求不会执行拦截器的内容
8.拦截器拦截普通类方法执行,过滤器过滤servlet请求响应
使用过滤器:参考过滤器配置的两种方法
- 使用注解@Order(1) + @WebFilter(filterName = “myFilter1”,urlPatterns = {“/*”})
@Order(1)
@WebFilter(filterName = "myFilter1",urlPatterns = {"/*"})
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化过滤器");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("进入目标资源之前先干点啥");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("处理一下服务端返回的response");
}
@Override
public void destroy() {
System.out.println("过滤器被销毁了");
}
}
- java config 配置
public class MyFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("初始化过滤器");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("进入目标资源之前先干点啥");
filterChain.doFilter(servletRequest,servletResponse);
System.out.println("处理一下服务端返回的response");
}
@Override
public void destroy() {
System.out.println("过滤器被销毁了");
}
}
@Configuration
public class FilterConfig {
@Bean
public FilterRegistrationBean registFilter(){
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new MyFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setName("Filter1");
registrationBean.setOrder(1);
return registrationBean;
}
}
六、RestTemplate
RestTemplate官网
(一)get
get请求一般有以下参数:
- url–请求的地址
- responseType–返回值的类型
- uriVariables–请求url上的请求url参数。比如:/get/2/{id}/{name} 或者 /get/2?id={id}&name={name}
package com.lihua.springbootweb.controller;
/**
* @author lihua
* @date 2023/1/29 9:11
**/
@Controller
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
//----------------get--------------- 返回 Object 类型。
@GetMapping("/get/1")
@ResponseBody
public String get1(){
return "/get/1-> ok";
}
/**
* 不带请求参数的get请求。
* getForObject 返回 一个Object
* @return
*/
@GetMapping("/restTemplate/get/1")
@ResponseBody
public String restTemplateGet1(){
String url = "http://127.0.0.1:8080/get/1";
String forObject = restTemplate.getForObject(url, String.class);
return forObject;
}
@GetMapping("/get/2/{id}/{name}")
@ResponseBody
public String get2(@PathVariable("name") String name,@PathVariable("id") String id){
return "/get/2-> ok。name="+name +", id="+id;
}
/**
* 带请求参数的get请求。
* getForObject 返回 一个Object
* @return
*/
@GetMapping("/restTemplate/get/2")
@ResponseBody
public String restTemplateGet2(){
String name="lihua";
String id="111";
//注意,这里会根据url的占位符{} 将对于的参数注入到url上
String url = "http://127.0.0.1:8080/get/2/{id}/{name}";
// String url = "http://127.0.0.1:8080/get/2?id={id}&name={name}";
String forObject = restTemplate.getForObject(url, String.class,id,name);
//当然你也传递一个map类型的url参数。
//Map<String,String> requestPram = new HashMap<>();
//requestPram.put("id","111");
//requestPram.put("name","lihua");
//String forObject1 = restTemplate.getForObject(url, String.class, requestPram);
return forObject;
}
//----------------get--------------- 返回 Entity 类型。
@GetMapping("/get/3")
@ResponseBody
public String get3(){
return "/get/3-> ok";
}
/**
* 不带请求参数的get请求。
* getForEntity 返回 一个Entity
*
* getForEntity 也是有三个重载的方法,用法基本和getForObject一致
*
* @return
*/
@GetMapping("/restTemplate/get/3")
@ResponseBody
public String restTemplateGet3(){
String url = "http://127.0.0.1:8080/get/3";
ResponseEntity<String> forEntity = restTemplate.getForEntity(url, String.class);
String body = forEntity.getBody();
System.out.println(forEntity.getStatusCode());
System.out.println(forEntity.toString());
HttpHeaders headers = forEntity.getHeaders();
return body;
}
}
(二)post
翻译:通过将给定对象POST到URI模板来创建新资源,并返回在响应中找到的表示。
URI模板变量使用给定的映射展开。
请求参数可以是HttpEntity,以便向请求添加额外的HTTP头。
实体的主体或请求本身可以是创建多部分请求的MultiValueMap。MultiValueMap中的值可以是表示部件主体的任何Object,也可以是表示具有主体和头部的部件的HttpEntity。
参数:
- url–请求的地址
- request–post请求携带的内容。比如请求参数和请求头
- responseType–返回值的类型
- uriVariables–请求url上的请求url参数。比如:/get/2/{id}/{name} 或者 /get/2?id={id}&name={name}
Family.java
/**
* @author lihua
* @date 2022/12/27 17:15
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Family {
private String name;
private int num;
}
package com.lihua.springbootweb.controller;
/**
* @author lihua
* @date 2023/1/29 9:11
**/
@Controller
public class RestTemplateController {
@Autowired
private RestTemplate restTemplate;
//----------------post--------------- 返回 Object 类型。
@PostMapping("/post/1/{v}")
@ResponseBody
public String post1(@RequestBody Family family,@PathVariable("v") String v){
System.out.println(v);
System.out.println(family);
return "/post/1-> ok";
}
/**
* post请求。
* postForObject 返回 一个Object
* @return
*/
@PostMapping("/restTemplate/post/1")
@ResponseBody
public String restTemplatePost1(){
String url = "http://127.0.0.1:8080/post/1/{v}";
String v="1";
//方式一:无请求头
HashMap<String, String> request1 = new HashMap<>();
request1.put("name","lihua");
request1.put("num","123");
//String s = restTemplate.postForObject(url, request1, String.class, v);
//方式二:有请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HashMap<String, Object> body1 = new HashMap<>();
body1.put("name","lihua");
body1.put("num","123");
HttpEntity<HashMap<String, Object>> httpEntity = new HttpEntity<>(body1,headers);
//String s1 = restTemplate.postForObject(url, httpEntity, String.class, v);
//方式三:java Obj
Family family = new Family();
family.setName("lihua");
family.setNum(11);
String s2 = restTemplate.postForObject(url, family, String.class, v);
return s2;
}
}
如果不需要uriVariables参数可以删除掉。postForEntity的用法跟postForObject基本一致,只是返回值不一样。
注意:并不是post请求一定要使用MultiValueMap 类型存放请求参数(MultiValueMap<String,0bject> paramMap = new LinkedMultiValueMap<>( );)。需要根据你调用的请求的参数决定。
MultiValueMap 与 Map的区别。MultiValueMap 是这样定义的:public interface MultiValueMap<K, V> extends Map<K, List< V >> 也就是一个key有多个value。Map一个key只有一个value。
服务提供者的参数如果是对象类型需要使用@RequestBody
修饰。否则数据无法绑定成功。
服务提供者:
@PostMapping("/post/1/{v}")
@ResponseBody
public String post1(@RequestBody Family family,@PathVariable("v") String v){
System.out.println(v);
System.out.println(family);
return "/post/1-> ok";
}
使用 MultiValueMap的例子。
@RequestMapping("/producer")
@RestController
public class ProducerController {
@PostMapping("/upload")
public JSONObject upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
//处理文件上传业务代码
return new JSONObject();
}
}
@RequestMapping("/consumer")
@RestController
public class ConsumerController {
@PostMapping("/invokeUpload")
public JSONObject invokeUpload(){
RestTemplate restTemplate = new RestTemplate();
String url = "http://127.0.0.1:8080/producer/upload";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
File file = new File("/Users/zk/Downloads/readme.txt");
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("file", new FileSystemResource(file));
form.add("filename",file.getName());
HttpEntity<MultiValueMap<String, Object>> files = new HttpEntity<>(form, headers);
JSONObject result = restTemplate.postForObject(url, files, JSONObject.class);
return result;
}
}
(三)Exchange
Exchange方式既可以访问get、也可以访问post。
可以查看postForObject 方法的源码,发现postForObject方法是通过调用exchange方法实现的。
@Component
public class RestTemplateUtils {
private static RestTemplate restTemplate;
public RestTemplateUtils(RestTemplate restTemplate) {
RestTemplateUtils.restTemplate = restTemplate;
}
public static String sendSimple(String url) {
return sendSimple(url, null, HttpMethod.GET, new HttpHeaders());
}
public static String sendSimple(String url, Map<String, ?> urlParam) {
return sendSimple(url, urlParam, HttpMethod.GET);
}
public static String sendSimple(String url, Map<String, ?> urlParam, HttpHeaders headers) {
return sendSimple(url, urlParam, HttpMethod.GET, headers);
}
public static String sendSimple(String url, Map<String, ?> urlParam, HttpMethod method) {
return sendSimple(url, urlParam, method, new HttpHeaders());
}
/**
* 发送简单请求,不含body
*
* @param url url
* @param urlParam 用?和&拼接在url后面的参数
* @param method 请求方式
* @param headers 请求头
* @return body
*/
public static String sendSimple(String url, Map<String, ?> urlParam, HttpMethod method, HttpHeaders headers) {
if (urlParam == null) {
urlParam = new HashMap<>(0);
}
// url参数拼接
url = handleUrlParam(url, urlParam);
HttpEntity<MultiValueMap<String, ?>> requestEntity = new HttpEntity<>(null, headers);
return restTemplate.exchange(url, method, requestEntity, String.class, urlParam).getBody();
}
public static String sendForm(String url, Map<String, ?> body) {
return sendForm(url, null, body, HttpMethod.POST, new HttpHeaders());
}
public static String sendForm(String url, Map<String, ?> urlParam, Map<String, ?> body) {
return sendForm(url, urlParam, body, HttpMethod.POST, new HttpHeaders());
}
public static String sendForm(String url, Map<String, ?> urlParam, Map<String, ?> body, HttpMethod method) {
return sendForm(url, urlParam, body, method, new HttpHeaders());
}
public static String sendForm(String url, Map<String, ?> urlParam, Map<String, ?> body,
HttpMethod method, HttpHeaders headers) {
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
return send(url, urlParam, body, method, headers);
}
public static String sendJson(String url, Map<String, ?> body) {
return sendJson(url, null, body, HttpMethod.POST, new HttpHeaders());
}
public static String sendJson(String url, Map<String, ?> urlParam, Map<String, ?> body) {
return sendJson(url, urlParam, body, HttpMethod.POST, new HttpHeaders());
}
public static String sendJson(String url, Map<String, ?> urlParam, Map<String, ?> body, HttpMethod method) {
return sendJson(url, urlParam, body, method, new HttpHeaders());
}
public static String sendJson(String url, Map<String, ?> urlParam, Map<String, ?> body,
HttpMethod method, HttpHeaders headers) {
headers.setContentType(MediaType.APPLICATION_JSON);
return send(url, urlParam, body, method, headers);
}
/**
* 复杂请求发送
*
* @param url url
* @param urlParam 用?和&拼接在url后面的参数
* @param body 请求体
* @param method 请求方式
* @param headers 请求头
* @return body
*/
public static String send(String url, Map<String, ?> urlParam, Map<String, ?> body, HttpMethod method,
HttpHeaders headers) {
if (urlParam == null) {
urlParam = new HashMap<>(0);
}
// url参数拼接
url = handleUrlParam(url, urlParam);
HttpEntity<Map<String, ?>> requestEntity = null;
if (Objects.equals(headers.getContentType(), MediaType.APPLICATION_JSON)) {
requestEntity = new HttpEntity<>(body, headers);
}
if (Objects.equals(headers.getContentType(), MediaType.APPLICATION_FORM_URLENCODED)) {
// body参数处理
MultiValueMap<String, Object> param = new LinkedMultiValueMap<>();
Iterator<? extends Map.Entry<String, ?>> iterator = body.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, ?> next = iterator.next();
param.add(next.getKey(), next.getValue());
}
requestEntity = new HttpEntity<>(param, headers);
}
return restTemplate.exchange(url, method, requestEntity, String.class, urlParam).getBody();
}
/**
* url参数拼接
*
* @param url
* @param urlParam
* @return
*/
private static String handleUrlParam(String url, Map<String, ?> urlParam) {
if (urlParam == null || urlParam.isEmpty()) {
return url;
}
Iterator<? extends Map.Entry<String, ?>> iterator = urlParam.entrySet().iterator();
StringBuilder urlBuilder = new StringBuilder(url);
urlBuilder.append("?");
while (iterator.hasNext()) {
Map.Entry<String, ?> entry = iterator.next();
urlBuilder.append(entry.getKey()).append("={").append(entry.getKey()).append("}").append("&");
}
urlBuilder.deleteCharAt(urlBuilder.length() - 1);
return urlBuilder.toString();
}
}
七、WebSocket
spring boot Websocket(使用笔记)
最全面的SpringMVC教程(六)——WebSocket