SpringBoot 一篇搞定(Cookie Session 跳转 内容协商 converter解析器 thymeleaf)


到这一步假设我要处理一些请求过来的特殊参数,或者像Django那样后面的操作,那么我就需要用到这个比较高级的用法,用到servlet提供的一些API接口,例如前面的HttpServletRequest等等。

Cookie处理

设置cookie

这个玩意的话还是很简单的而且昨天的博客也说了怎么玩。

@GetMapping("/change-username")
public String setCookie(HttpServletResponse response) {
    
    
    // 创建一个 cookie对象
    Cookie cookie = new Cookie("username", "Jovan");
    cookie.setMaxAge(7 * 24 * 60 * 60); // 7天过期
    cookie.setSecure(true);  //Https 安全cookie 只通过https传输

    //将cookie对象加入response响应
    response.addCookie(cookie);

    return "Username is changed!";
}

获取cookie

@GetMapping("/all-cookies")
public String readAllCookies(HttpServletRequest request) {
    
    

    Cookie[] cookies = request.getCookies();
    if (cookies != null) {
    
    
        return Arrays.stream(cookies)
                .map(c -> c.getName() + "=" + c.getValue())
                .collect(Collectors.joining(", "));
    }

    return "No cookies";
}

删除Cookie

若要删除cookie,请将Max-Age指令设置为0并取消其值的设置。您还必须传递用于设置它的相同的其他cookie属性。不要将Max-Age指令值设置为-1。否则,浏览器将把它视为会话cookie。

换句话来说

你的一个cookie是这样的

Cookie cookie = new Cookie("username", "Jovan");
cookie.setMaxAge(7 * 24 * 60 * 60); // 7天过期
cookie.setSecure(true);  //Https 安全cookie 只通过https传输

现在删除,你要这样做

Cookie cookie = new Cookie("username",null);
cookie.setMaxAge(0); 
cookie.setSecure(true);  //Https 安全cookie 只通过https传输
response.addCookie(cookie);

小结

这里的话主要就是用了servlet里面的一些原生的API去做,一个是HttpServletRequest还有就是Response。

Session处理

这个Session是啥我想都不用多说了,这个也是需要利用到Cookie的玩意。那么在SpringBoot里面设置也非常简单。

首先HttpSession是我们的Session对象。

这么用呢也相当简单

@Controller
public class IndexController {
    
    
    @GetMapping("/index1")
    public String index1(HttpSession session){
    
    
        session.setAttribute("name","Huterox");
        return "redirect:/index2";
    }

    @GetMapping("/index2")
    @ResponseBody
    public String index2(HttpSession session){
    
    
        String name = (String) session.getAttribute("name");
        return name;
    }
}

这样一来就是先了session处理。

作用域

而且这里都记住这个Session和request里面的参数都是由作用域的,request的作用域:是从当前请求到服务器处理请求结束,包括服务器转发到内部的资源路径,也同样可以访问到request中的内容;

所以如果要从当前资源转发到其他资源中,还需要共享数组,就可以使用request.setAttribute(“名称”,Object obj)共享数据;

另外Session的作用域是一次会话,从打开浏览器到关闭浏览器之前,都可以访问到Session中的数据;作用域更大;

如果是重定向,就不能访问到Request域中的共享数据;可以使用session作用域;

Request参数设置

@Controller
public class IndexController {
    
    
    @GetMapping("/index1")
    public String index1(HttpSession session, HttpServletRequest request){
    
    
        session.setAttribute("name","Huterox");
        request.setAttribute("world","Hello world");

        return "redirect:/index2";
    }

    @GetMapping("/index2")
    @ResponseBody
    public String index2(HttpSession session,HttpServletRequest request){
    
    
        String name = (String) session.getAttribute("name");
        String world = (String) request.getAttribute("world");

        return name+world;
    }
}

这里大概率你得到的值一定是

Huteroxnull

原因很简单我这里用到是跳转,前面说了它们之间是由作用域的!redirect是跳转,意味着当前的请求处理完了那么进入了下一个请求处理,那么这个时候自然作用不了了。

那么在这里的话我们

return “forward:/index2”;

向前就好了。

这个也是servlet里面进行跳转的方式。

页面跳转

首先这个方式有很多最简单的方式就是

return "forward:/xxx";

或者

return "redirect:/xxx";

但是二者是有区别。

forward的意思是向前的、(按新地址)转寄、促进、前锋的意思,而redirect的意思是改变方向、重新寄送。

这个英文意思基本上就是在Spring当作的意思。

通过Response

我们也可以通过这个来搞定

    @GetMapping("/index1")
    public void index1(HttpSession session, HttpServletResponse response) throws IOException {
    
    
        session.setAttribute("name","Huterox");
        response.sendRedirect("/index2");
//        return "redirect:/index2";
        
    }

但是这样方式有作用域的问题,要么你用session搞定,或者放行,这边可以使用request放行可以保证作用域参数丢失的问题。

通过Request

request.getRequestDispatcher("/index2").forward(request,response);

不过这里也是只用

内容协商

首先:

内容协商的核心之一就是,(Converter)类型转换器!这玩应通过反射等手段实现自动类型的加载转换,同时结合不同的依赖实现不同的类型转换。这一点在实际的操作过程当中相当重要这就意味着可以实现一S端(server)对应不同的客户端

这是很不错的点感觉除了数据库操作麻烦一点其他的要完爆Django

Json返回

首先SpringBoot由于自己在搭建start-web场景的时候自己自带了一个Json的数据转换,所以的话默认在Spring Boot里面就是返回一个Json对象的。


    @GetMapping("/jsonget")
    @ResponseBody
    public String index2(){
    
    
		User user = new User();
        return user;
    }
}

XML 返回

这边我们需要先导入依赖

 <dependency>
            <groupId>com.fasterxml.jackson.dataformat</groupId>
            <artifactId>jackson-dataformat-xml</artifactId>
</dependency>

之后我们的服务器后根据客户端请求头

Accept: text/css,*/*;q=0.1

这个字段的要求返回对应的参数。也就是说如果目标客户端需要服务端返回xml那么现在,你只需要搞好这个配置,然后啥也不用管了。

基本原理说明

  • 1、判断当前响应头中是否已经有确定的媒体类型。MediaType

  • 2、获取客户端(PostMan、浏览器)支持接收的内容类型。(获取客户端Accept请求头字段)

  • 3、遍历循环所有当前系统的 MessageConverter,看谁支持操作这个对象(Person)

  • 4、找到支持操作Person的converter,把converter支持的媒体类型统计出来。

  • 5、客户端需要【application/xml】。服务端能力【json、xml,】

  • 6、进行内容协商的最佳匹配媒体类型

  • 7、用 支持 将对象转为 最佳匹配媒体类型 的converter。调用它进行转化 。

自定义类型转换器

通过前面的原理我们大致知道了其核心其实就是converter所以我们可以自己定义一个。

由于这一步是SpringMVC的活,所以我梦要想要实现这样的效果,自定义MVC的内容,那么我们就必须在我们的配置类里面这样搞

在配置类当作添加 WebMvcConfigurer

之后我们重写一些WebMvcConfigurer里面的一些方法。

那么现在先自定义返回类型


/**
 * 自定义的Converter
 */
public class HMessageConverter implements HttpMessageConverter<Person> {
    
    

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
    
    
        return false;
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
    
    
        return clazz.isAssignableFrom(Person.class);
    }

    /**
     * 服务器要统计所有MessageConverter都能写出哪些内容类型
     * <p>
     * application/x-Huterox
     *
     * @return
     */
    @Override
    public List<MediaType> getSupportedMediaTypes() {
    
    
        return MediaType.parseMediaTypes("application/x-Huterox");
        //类型,自定义的类型名称
    }

    @Override
    public Person read(Class<? extends Person> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
    
    
        return null;
    }

    @Override
    public void write(Person person, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
    
    
        //自定义协议数据的写出
        String data = person.getUserName() + ";" + person.getAge() + ";" + person.getBirth();
        //写出去
        OutputStream body = outputMessage.getBody();
        body.write(data.getBytes());
    }
}

重写方法

@Configuration(proxyBeanMethods = false)
public class WebConfig /*implements WebMvcConfigurer*/ {
    
    

    //WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
    
    
        return new WebMvcConfigurer() {
    
    
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
                converters.add(new HMessageConverter());
            }
        }
        
    }

通过特定参数获取返回类型

在这里面除了浏览器自带的参数接受形式外,SpringBoot还支持带参数返回,例如

127.0.0.1:8000/index?format=xml

不过在这里需要先开启这个功能

spring:
    contentnegotiation:
      favor-parameter: true  #开启请求参数内容协商模式

现在我们想让我们自己定义的类型也能够通过参数获取,那么同样的我们现在也需要重写。

@Configuration(proxyBeanMethods = false)
public class WebConfig /*implements WebMvcConfigurer*/ {
    
    

    //WebMvcConfigurer定制化SpringMVC的功能
    @Bean
    public WebMvcConfigurer webMvcConfigurer() {
    
    
        return new WebMvcConfigurer() {
    
    
            @Override
            public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    
    
                converters.add(new HMessageConverter());
            }
        }
        
         @Override
            public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    
    
                //Map<String, MediaType> mediaTypes
                Map<String, MediaType> mediaTypes = new HashMap<>();
                mediaTypes.put("json", MediaType.APPLICATION_JSON);
                mediaTypes.put("xml", MediaType.APPLICATION_XML);
                mediaTypes.put("H", MediaType.parseMediaType("application/x-Huterox"));
                //指定支持解析哪些参数对应的哪些媒体类型
                ParameterContentNegotiationStrategy parameterStrategy = new ParameterContentNegotiationStrategy(mediaTypes);
//                parameterStrategy.setParameterName("ff");改变参数名字 ?ff=xml

                HeaderContentNegotiationStrategy headeStrategy = new HeaderContentNegotiationStrategy();

                configurer.strategies(Arrays.asList(parameterStrategy, headeStrategy));
            }

        
        
        
    }

这样一来就完成了完整的功能。

参数解析

这个也是很好玩很厉害的东西。

举个例子就是昨天的。

@RestController
public class HelloController {
    
    
    @PostMapping("/hello")
    public String hello(User user) {
    
    
        return user.toString();
    }
}

根据获取的参数SpringBoot会自动封装一个指定的对象。

原理解析

一说到这个又是涉及到自定义的问题了。

首先参数返回到springboot后,准确的来说是springMVC 先进入它的参数解析器-HandlerMethodArgumentResolver

  • HandlerMapping中找到能处理请求的Handler(Controller.method())

  • 为当前Handler 找一个适配器 HandlerAdapter; RequestMappingHandlerAdapter

  • 适配器执行目标方法并确定方法参数的每一个值

之后参数解析器-HandlerMethodArgumentResolver

确定将要执行的目标方法的每一个参数的值是什么;

SpringMVC目标方法能写多少种参数类型。取决于参数解析器。

  • 当前解析器是否支持解析这种参数
  • 支持就调用 resolveArgument

这样一来就完成了对对象的封装。

所以这里面的核心还是说,返回的参数格式里面和我们定义的Bean里面 的属性是否对得到,OK才能进行一系列的操作。

示例

例如我这里有两个Bean

@Data
public class Person {
    
    

    private String userName;
    private Integer age;
    private Date birth;
    private Pet pet;

}


@Data
public class Pet {
    
    

    private String name;
    private Integer age;

}


现在有这样的表单

<form action="/saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    生日: <input name="birth" value="2019/12/10"/> <br/>
    宠物姓名:<input name="pet.name" value="阿猫"/><br/>
    宠物年龄:<input name="pet.age" value="5"/>
    <input type="submit" value="保存"/>
</form>
    @PostMapping("/GetPerson")
    public Person saveuser(Person person) {
    
    

        return person;
    }

现在ok

自定义解析器

现在我把表单这样改一下

<form action="/saveuser" method="post">
    姓名: <input name="userName" value="zhangsan"/> <br/>
    年龄: <input name="age" value="18"/> <br/>
    生日: <input name="birth" value="2019/12/10"/> <br/>
    宠物: <input name="pet" value="啊猫,3"/>
    <input type="submit" value="保存"/>
</form>

显然虽然有pet但是没有具体的映射关系,解析器解析不了。

那么在这里也是我们是定制MVC里面的东西我们需要进入到前面的webconfigurer里面去

  @Bean
    public WebMvcConfigurer webMvcConfigurer() {
    
    
        return new WebMvcConfigurer() {
    
               

			@Override
            public void addFormatters(FormatterRegistry registry) {
    
    
                registry.addConverter(new Converter<String, Pet>() {
    
    

                    @Override
                    public Pet convert(String source) {
    
    
                        // 啊猫,3
                        if (!StringUtils.isEmpty(source)) {
    
    
                            Pet pet = new Pet();
                            String[] split = source.split(",");
                            pet.setName(split[0]);
                            pet.setAge(Integer.parseInt(split[1]));
                            return pet;
                        }
                        return null;
                    }
                });
            }
        };
    }
}

这样一来就可以解析pet了,因为pet这玩意他是找得到的,只是没法解析。

视图处理

现在终于到了万众瞩目的示图了,看到了熟悉的template这个文件夹

那么同样在这边处理视图的话需要用到对应的引擎,进行解析。这个我们这边先使用的还是

thymeleaf

那么对于视图的处理分一下流程。

1、目标方法处理的过程中,所有数据都会被放在 ModelAndViewContainer 里面。包括数据和视图地址

2、方法的参数是一个自定义类型对象(从请求参数中确定的),把他重新放在 ModelAndViewContainer

**3、任何目标方法执行完成以后都会返回 ModelAndView(**数据和视图地址)。

4.processDispatchResult 处理派发结果(页面改如何响应)

使用thymeleaf

加入依赖(如果你IDEA选了就不用了)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

基本语法

(此部分内容参照尚硅谷提供的笔记,对部分内容进行筛选)

1、表达式

表达式名字 语法 用途
变量取值 ${…} 获取请求域、session域、对象等值
选择变量 *{…} 获取上下文对象值
消息 #{…} 获取国际化等值
链接 @{…} 生成链接
片段表达式 ~{…} jsp:include 作用,引入公共页面片段

2、字面量

文本值: ‘one text’ , ‘Another one!’ **,…**数字: 0 , 34 , 3.0 , 12.3 **,…**布尔值: true , false

空值: null

变量: one,two,… 变量不能有空格

3、文本操作

字符串拼接: +

变量替换: |The name is ${name}|

4、数学运算

运算符: + , - , * , / , %

5、布尔运算

运算符: and , or

一元运算: ! , not

6、比较运算

比较: > , < , >= , <= ( gt , lt , ge , le **)**等式: == , != ( eq , ne )

7、条件运算

If-then: (if) ? (then)

If-then-else: (if) ? (then) : (else)

Default: (value) ?: (defaultvalue)

设置属性值-th:attr

<form action="subscribe.html" th:attr="action=@{/subscribe}">
  <fieldset>
    <input type="text" name="email" />
    <input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>
  </fieldset>
</form>
<img src="../../images/gtvglogo.png"  th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" />

多个值

th:text

改变标签内容

迭代

<tr th:each="prod : ${prods}">
        <td th:text="${prod.name}">Onions</td>
        <td th:text="${prod.price}">2.41</td>
        <td th:text="${prod.inStock}? #{true} : #{false}">yes</td>
</tr>


条件运算

<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">view
</a>
<div th:switch="${user.role}">
  <p th:case="'admin'">User is an administrator</p>
  <p th:case="#{roles.manager}">User is a manager</p>
  <p th:case="*">User is some other thing</p>
</div>

进入页面使用

首先在使用的时候需要进行导入命名空间

xmlns:th="http://www.thymeleaf.org

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
<h1 th:text="${msg}">首页</h1>

</h2>
</body>
</html>

初步使用

首先准备index.html

在template文件夹下面

然后编写

    @GetMapping("/index")
    public String index(){
    
    
        return "index";
    }

不需要加入index.html

获取示图参数

这边我们主要是通过Model来处理的。

model.addAttribute(“users”,users);

    @GetMapping("/index")
    public String index(Model model){
    
    
         model.addAttribute("msg","Hello哈");
        return "index";
    }

猜你喜欢

转载自blog.csdn.net/FUTEROX/article/details/120924861