spring mvc文档阅读笔记——02

一、Asynchronous Requests(异步请求)

(一)阻塞和非阻塞,同步和异步

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  1. 同步异步指的是通信模式,即被调用者结果返回时通知进程/线程的一种通知机制。涉及回调函数

  2. 阻塞和非阻塞指的是调用结果返回前进程/线程的状态。涉及线程挂起

(二)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 属性

  1. origins
    特定来源的允许来源列表,例如“https://domain1.com“,或”*“表示所有原点。
    飞行前实际CORS请求的访问控制允许源响应标头中列出了匹配的源。
    默认情况下,允许所有原点。
    注意:CORS检查使用“Forwarded”(RFC 7239)、“X-Forwarded-Host”、“X-Forwarded-Port”和“X-Forward-Proto”标头(如果存在)中的值,以反映客户端发起的地址。考虑使用ForwardedHeaderFilter,以便从中心位置选择是提取和使用,还是丢弃此类标头。有关此筛选器的更多信息,请参阅Spring Framework参考。
  2. allowedHeaders
    实际请求中允许的请求头列表,可能为“*”以允许所有头。
    允许的标头列在飞行前请求的访问控制允许标头响应标头中。
    根据CORS规范,如果头名称是缓存控制、内容语言、过期、上次修改或Pragma之一,则不需要列出。
    默认情况下,允许所有请求的标头。
  3. exposedHeaders
    用户代理将允许客户端访问实际响应的响应标头列表,而不是“简单”标头,即缓存控制、内容语言、内容类型、过期、上次修改或Pragma,
    暴露的标头列在实际CORS请求的访问控制暴露标头响应标头中。
    特殊值“*”允许为非认证请求公开所有标头。
    默认情况下,没有显示标题。
  4. maxAge
    飞行前响应的缓存持续时间的最大期限(秒)。
    此属性控制飞行前请求的Access Control Max Age响应标头的值。
    将其设置为合理的值可以减少浏览器所需的飞行前请求/响应交互的数量。负值表示未定义。
    默认设置为1800秒(30分钟)。
  5. allowCredentials
    浏览器是否应向带注释的端点发送凭据,如cookie以及跨域请求。已配置的值设置在飞行前请求的访问控制允许凭据响应标头上。
    注意:请注意,此选项与配置的域建立了高度信任,并通过暴露敏感的用户特定信息(如cookie和CSRF令牌)增加了web应用程序的表面攻击。
    默认情况下,未设置此项,在这种情况下,也未设置Access Control Allow Credentials标头,因此不允许使用凭据。
  6. 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(类型转换)

在这里插入图片描述

类型转换方式有:

  1. 通过数据绑定@InitBinder注解。
    参考上一篇内容的数据绑定@InitBinder
  2. 通过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;
    }
}
  1. 通过注解@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(视图控制器)

  1. 如果只需要实现简单的页面跳转,可以通过配置addViewControllers 实现。不需要写一些controller专门用来跳转页面。比如:请求路径/index 只需要跳转到index.html页面,不需要处理业务逻辑,那么你可以试试这个配置。
  2. 快速配置重定向
  3. 简单配置一个路径只返回一个状态码
@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请求响应
在这里插入图片描述

使用过滤器:参考过滤器配置的两种方法

  1. 使用注解@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("过滤器被销毁了");
    }
}
  1. 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

八、模板引擎

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值