Spring Web全栈

第一章——Spring入门

核心

依赖注入(DI)

  1. Maven坐标

  1. Maven工程属性

  1. Maven依赖

  1. Maven插件

Maven坐标

<groupId>com.youkeda.course</groupId>

<artifactId>app</artifactId>

<packaging>jar</packaging>

<version>1.0-SNAPSHOT</version>

groupId
类似于一个文件夹
artifactId
类似于一个文件夹名
packaging
把整个工程打包成packagin指定的文件格式
version
对应着版本号
版本号规范
  • SNAPSHOT表示当前程序还处于不稳定的阶段,随时可以再修改

  • RELEASE代表的是稳定,一般正式发布的时候,就会把version改为 RELEASE

三位版本号
  • 第一位代表的是主版本号

  • 第二位代表的是新增功能(数字是几就代表有了几次新增功能的行为)

  • 第三位代表的是bugfix的版本(代表修复bug的次数)

Maven属性配置

<properties>

<java.version>1.8</java.version><maven.compiler.source>${java.version}</maven.compiler.source>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<maven.compiler.target>${java.version}</maven.compiler.target>

</properties>

  • java.version

代表设置一个参数
  • maven.compiler.source

指定Maven编译时候源代码的JDK版本
  • project.build.sourceEncoding

指定的是工程代码源文件的文件编码格式,一般都设置为 UTF-8
  • maven.compiler.target

表示按这个值来进行编译源代码(上面的代表是版本,这个是具体的,例如:JDK1.8)

中间仓库

Maven存放所有的jar的地方

原网站部署在国外,在国内访问不到,用国内的镜像替代

阿里云的镜像服务器:https://developer.aliyun.com/mvn/search

间接依赖

如果一个remote工程依赖了okhttp库,而当前工程 locale依赖了 remote工程,这个时候 locale 工程也会自动依赖了okhttp

插件体系plugins

使得Maven这个工具变得高度可定制,可以无缝的支撑工程化能力

<build>

<plugins>

<plugin>

<groupId>org.apache.maven.plugins</groupId>

<artifactId>maven-compiler-plugin</artifactId>

<version>3.8.1</version>

</plugin>

</plugins>

</build>

这里声明了一个 maven-compiler-plugin插件用于执行maven compile。

Hello Spring

Spring强调的是面向接口编程

packagecom.youkeda;

importorg.springframework.context.ApplicationContext;

importorg.springframework.context.annotation.AnnotationConfigApplicationContext;

importorg.springframework.context.annotation.ComponentScan;

importcom.youkeda.service.MessageService;

/**

* Application

*/

@ComponentScan

publicclassApplication {

publicstaticvoidmain(String[] args) {

ApplicationContextcontext=newAnnotationConfigApplicationContext(Application.class);

MessageServicemsgService=context.getBean(MessageService.class);

System.out.println(msgService.getMessage());

}

}

上下对比一下即可看出,使用Spring不需要关心实现类、如何实例化实现类,直接获取接口就可以

第二章——Spring依赖注入

Java注解

注解能够再编译、运行阶段读取,注解(Annotation)本身也是一个类
  1. Target

用于设定该注解得目标范围,比如说可以作用于类或者方法等。
  • ElementTyoe.TYPE

可以作用于类、接口类、枚举类上

@Service

publicclassMessageServiceImplimplementsMessageService{

publicStringgetMessage() {

return"Hello World!";

}

}

  • ElementType.FIELD

可以作用于类得属性上

publicclassMessageServiceImplimplementsMessageService{

@Autowired

privateWorkspaceServiceworkspaceService;

}

  • ElementType.METHOD

可以作用于类得方法上

publicclassMessageServiceImplimplementsMessageService{

@ResponseBody

publicStringgetMessage() {

return"Hello World!";

}

}

  • ElementType.PARAMETER

可以作用于类的参数

publicclassMessageServiceImplimplementsMessageService{

publicStringgetMessage(@RequestParam("msg")Stringmsg) {

return"Hello "+msg;

}

}

如果想同时作用于类和方法上:

@Target({ElementType.TYPE,ElementType.METHOD})

  1. Retention

用于声明该注解的生命周期
SOURCE:纯注释作用
CLASS:在编译阶段是有效的
RUNTIME:在运行时有效
  1. Documented

将注解中的元素包含到JavaDoc文档中,一般都会添加这个注解
  1. @interface

声明当前的Java类型是Annotation
  1. Annotation

String value() default "";

有点像类的属性一样,约定了属性的类型和属性名称(默认是value,在引用的时候可以省略),default代表的是默认值。

value是默认属性名称可以缩写

import org.springframework.stereotype.Service;

@Service(value="Demo")

public class Demo {

}

等同于

import org.springframework.stereotype.Service;

@Service("Demo")

public class Demo {

}

静态代码块

有时候,为了让系统能够自动执行一些代码,可以采用静态代码块的方式

public class Hello {

static {

Song song = new Song();

... ...

... ...

}

... ...

}

没有参数也没有返回值,当系统加载这个类的时候,会自动执行静态代码块中的代码,无论被new多少次,静态代码块都只执行一次

Spring Bean(上)

Ioc(控制反转)容器是Spring框架最核心的组件

所有的Java对象都会通过IoC容器转变为Bean(Spring对象的一种称呼)

基本上所有的Bean都是由接口+实现类完成的,用户想要获取Bean的实例直接从IoC容器获取就可以了,不需要关心实现类

一种是基于XML、一种是基于Annotation方案,目前主流是Annotation

启动IoC容器的代码:

ApplicationContext context =

new AnnotationConfigApplicationContext("fm.douban");

会自动加载包fm.douban下的所有Bean

AnnotationConfigApplicationContext这个类的构造函数有两种

  • 根据包名实例化(AnnotationConfigApplicationContext("fm.douban"))

  • 根据自定义包扫描行为实例化

(AnnotationConfigApplicationContext(Class clazz))

官方声明的Spring Bean注解:

  • org.springframework.stereotype.Service(通用)

  • org.springframework.stereotype.Component(Service)

  • org.springframework.stereotype.Controller(Web)

  • org.springframework.stereotype.Repository(持久化)

Spring Bean(下)

前一步是容器启动,后一步就是依赖注入

@Autowired

private SongService songService;

@Service和 @Autowired是相辅相成的,如果没有加 @Service,就意味着没有标记成Spring Bean,那么即使加了 @Autowired也无法注入实例

Spring Resource(上)

用于Spring的读文件、写文件区域

Resource内的文件是无法通过正常的读文件读取的,因为在Maven执行package的时候,会把resources目录下的文件一起打包进jar包里

classpath

classpath指定的文件不能解析成File对象,但是可以解析成InputStream,我们可以借助Java IO就可以读出来了

classpath的根目录是从 /开始代表的是 src/main/java或者 src/main/resources目录

读文件依赖

<dependency>

<groupId>commons-io</groupId>

<artifactId>commons-io</artifactId>

<version>2.6</version>

</dependency>

public class Test {

public static void main(String[] args) {

// 读取 classpath 的内容

InputStream in = Test.class.getClassLoader().getResourceAsStream("data.json");

// 使用 commons-io 库读取文本

try {

String content = IOUtils.toString(in, "utf-8");

System.out.println(content);

} catch (IOException e) {

// IOUtils.toString 有可能会抛出异常,需要我们捕获一下

e.printStackTrace();

}

}

}

从Java运行的类加载器实例中查找文件,Test.class指的当前的Test.java编译后的Java class文件。是一种固定的写法

Spring Resource(下)

作用

在Spring种通过 org.springframework.core.io.ResourceLoader服务来提供任意文件的读写,你可以在任意的Spring Bean中引入 ResourceLoader

@Autowired

private ResourceLoader loader;

接口

public interface FileService {

String getContent(String name);

}

实现类

import fm.douban.service.FileService;

import org.apache.commons.io.IOUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.core.io.Resource;

import org.springframework.core.io.ResourceLoader;

import org.springframework.stereotype.Service;

import java.io.IOException;

import java.io.InputStream;

@Service

public class FileServiceImpl implements FileService {

@Autowired

private ResourceLoader loader;

@Override

public String getContent(String name) {

try {

InputStream in = loader.getResource(name).getInputStream();

return IOUtils.toString(in,"utf-8");

} catch (IOException e) {

return null;

}

}

}

服务调用

FileService fileService = context.getBean(FileService.class);

String content = fileService.getContent("classpath:data/urls.txt");

System.out.println(content);

再次利用该代码读取本地文件

String content2 = fileService.getContent("file:mywork/readme.md");

System.out.println(content2);

file:mywork/readme.md代表的就是读取工程目录下文件 mywork/readme.md

还可以读取远程文件

String content2 = fileService.getContent("https://www.zhihu.com/question/34786516/answer/822686390");

System.out.println(content2);

总结

把本地文件、classpath文件、远程文件都封装成Resource对象来统一加载。

Spring bean的生命周期(Lifecycle)

init方法,可以是任意名称,主要是注解,代表该方法在Spring Bean启动后自动执行

import javax.annotation.PostConstruct;

@Service

public class SubjectServiceImpl implements SubjectService {

@PostConstruct

public void init(){

System.out.println("启动啦");

}

}

Spring MVC

面向服务的框架Sping Boot

Spirng Controller

三个核心点:

  1. Bean的配置:Controller注解运用

  1. 网络资源的加载:加载网页

  1. 网址路由的配置:RequestMapping注解运用

Controller注解

import org.springframework.stereotype.Controller;

@Controller

public class HelloControl {

}

加载网页

RequestMapping注解

对于Web服务器来说,必须要实现的一个能力就是解析URL,并提供资源内容给调用者。这个过程我们一般称为路由

import org.springframework.stereotype.Controller;

import org.springframework.web.bind.annotation.RequestMapping;

@Controller

public class HelloControl {

@RequestMapping("/hello")

public String say(){

return "html/hello.html";

}

}

Get Request(一)

平常我们浏览网站、看视频、看图片用的都是get协议

获取Http URL参数

@RequestMapping("/songlist")

public String index( @RequestParam("listId") String id){

return "html/songList.html";

}

https://域名/songlist?ListId=xxxx

注意不是写的id,看的是括号内的名称

Get Request(二)

@GetMapping

import org.springframework.web.bind.annotation.*;

@GetMapping("/songlist")

public String index(@RequestParam("id") String id,@RequestParam("pageNum") int pageNum){

return "html/songList.html";

}

多个参数之间用 &进行分割

非必须传递参数

@GetMapping("/songlist")

public String index(@RequestParam(name="pageNum",required = false) int pageNum,@RequestParam("id") String id){

return "html/songList.html";

}

输出JSON数据

@GetMapping("/api/foos")

@ResponseBody

public String getFoos(@RequestParam("id") String id) {

return "ID: " + id;

}

第四章——Spring Template

Thymeleaf入门

模板用法
Maven依赖

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-thymeleaf</artifactId>

</dependency>

数据传递

引入Model对象,就可以通过这个Model对象传递到页面中

@Controller

public class SongListControl {

@Autowired

private SongListService songListService;

@RequestMapping("/songlist")

public String index(@RequestParam("id")String id,Model model){

SongList songList = songListService.get(id);

//传递歌单对象到模板当中

//第一个 songList 是模板中使用的变量名

// 第二个 songList 是当前的对象实例

model.addAttribute("songList",songList);

return "songList";

}

}

系统会自动匹配,所以不加后缀html也没关系

最后的文件内容

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8" />

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<meta http-equiv="X-UA-Compatible" content="ie=edge" />

<link rel="stylesheet" href="/css/songList.css" />

<title>豆瓣歌单</title>

</head>

<body>

<h1 th:text="${songList.name}"></h1>

</body>

</html>

虽然其后缀也是html,但它其实不是HTML文件,而是thymeleaf模板

xmlns:th=" http://www.thymeleaf.org"的作用是,在写代码时,让软件能够识别thymeleaf语法

只有在工程的 src/main/resources/templates下面放置了文件,就表示需要使用Thymeleaf,这些文件是模板。放在 src/main/resources/static目录下的就不是模板,是静态文件

Thymeleaf变量

模板变量

th:text这个属性就是Thymeleaf自定义的HTML标签属性

会动态替换掉html标签的内部内容

对象变量

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8" />

</head>

<body>

<span th:text="${sl.id}"></span>

<span th:text="${sl.name}"></span>

</body>

</html>

当传入是对象的时候,也可以通过 .来调用其属性

Thymeleaf循环语句

<ul th:each="song : ${songs}">

<li th:text="${song.name}">歌曲名称</li>

</ul>

打印列表的索引值

<ul th:each="song,it: ${songs}">

<li>

<span th:text="${it.count}"></span>

<span th:text="${song.name}"></span>

</li>

</ul>

  • it.index

返回对象的index,从0开始算
  • it.count

当前对象的index,从1开始算
  • it.size

获得列表长度
  • it.current

当前迭代变量
  • it.even/odd

布尔值,当前循环是偶数/奇数
  • it.first

布尔值,当前循环是否是第一个
  • it.last

布尔值,当前循环是否是最后一个

Thymeleaf表达式

<span th:text="'00:00/'+${totalTime}"></span>

利用 + 完成字符串拼接

字符串拼接优化

<span th:text="|00:00/${totalTime}|"></span>

数据转化

依赖

<dependency>

<groupId>org.thymeleaf.extras</groupId>

<artifactId>thymeleaf-extras-java8time</artifactId>

<version>3.0.4.RELEASE</version>

</dependency>

代码

<p th:text="${#dates.format(dateVar, 'yyyy-MM-dd')}"></p>

<p th:text="${#dates.format(dateVar, 'yyyy年MM月dd日')}"></p>

strings
内联表达式

将变量写在HTML中

<span>Hello [[${msg}]]</span>

th:text和 [[]]两种写法都可以,看个人习惯

Thymeleaf条件语句

<span th:if="${user.sex == 'male'}">男</span>

strings逻辑判断

isEmpty

${#strings.isEmpty(name)}

数组类

${#strings.arrayIsEmpty(name)}

集合类

${#strings.listIsEmpty(name)}

contains

${#strings.contains(name,'abc')}

第五章Spring Template进阶

Thymeleaf表单

@PostMapping的不同点在于只接收http method为post请求的数据

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org">

<head>

<meta charset="UTF-8" />

<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<meta http-equiv="X-UA-Compatible" content="ie=edge" />

<title>添加书籍</title>

</head>

<body>

<h2>添加书籍</h2>

<form action="/book/save" method="POST">

<div>

<label>书的名称:</label>

<input type="text" name="name">

</div>

<div>

<label>书的作者:</label>

<input type="text" name="author">

</div>

<div>

<label>书的描述:</label>

<textarea name="desc"></textarea>

</div>

<div>

<label>书的编号:</label>

<input type="text" name="isbn">

</div>

<div>

<label>书的价格:</label>

<input type="text" name="price">

</div>

<div>

<label>书的封面:</label>

<input type="text" name="pictureUrl">

</div>

<div>

<button type="submit">注册</button>

</div>

</form>

</body>

</html>

除了form属性调整,还需要修改inpu的name属性,属性和Book类的属性名一致

Spring Validation(一)

JSR 380

JSR的意思是Java规范提案

Maven依赖

<dependency>

<groupId>jakarta.validation</groupId>

<artifactId>jakarta.validation-api</artifactId>

<version>2.0.1</version>

</dependency>

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-validation</artifactId>

</dependency>

Validation注解
  • @NotNull

不允许为null对象
  • @AssertTrue

是否为true
  • @Size

约定字符串的长度
  • @Min

字符串的最小长度
  • @Max

字符串的最大长度
  • @Email

是否是邮箱格式
  • @NotEmpty

不允许为null或者为空
  • @NotBlank

不允许为null和空格

package com.bookstore.model;

import javax.validation.constraints.*;

public class User {

@NotEmpty(message = "名称不能为 null")

private String name;

@Min(value = 18, message = "你的年龄必须大于等于18岁")

@Max(value = 150, message = "你的年龄必须小于等于150岁")

private int age;

@NotEmpty(message = "邮箱必须输入")

@Email(message = "邮箱不正确")

private String email;

// standard setters and getters

}

执行检验

package com.bookstore.control;

import com.bookstore.model.User;

import org.springframework.stereotype.Controller;

import org.springframework.validation.BindingResult;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.PostMapping;

import javax.validation.Valid;

@Controller

public class UserControl {

@GetMapping("/user/add.html")

public String addUser() {

return "user/addUser";

}

@PostMapping("/user/save")

public String saveUser(@Valid User user, BindingResult errors) {

if (errors.hasErrors()) {

// 如果校验不通过,返回用户编辑页面

return "user/addUser";

}

// 校验通过,返回成功页面

return "user/addUserSuccess";

}

}

第一个参数增加了注解@Valid,第二个参数是errors

Spring Validation(二)

让错误的原因显现出来

th:object

可以用于替换对象,就不需要每次再次对象了,可以直接操作它的属性

<form action="/user/save" th:object="${user}" method="POST">

...

</form>

th:classappend

动态管理表单的样式

<div th:classappend="${#fields.hasErrors('name')} ? 'error' : ''">

</div>

如果错误信息里有name字段,上面的代码会生成

<div class="error">

</div>

th:errors

显示出错误信息,这个会自动取出错误信息来

<p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>

th:field

一般错误的时候,我们还希望显示上一次输入的内容

<div th:classappend="${#fields.hasErrors('age')} ? 'error' : ''">

<label>年龄:</label>

<input type="text" th:field="*{age}" />

<p th:if="${#fields.hasErrors('age')}" th:errors="*{age}"></p>

</div>

Thymeleaf布局(Layout)

解决模板复用的问题

使用 th:include + th:replace方案来完成布局的开发

<div class="container" th:include="::content">页面正文内容</div>

::content指的是选择器,指的就是加载当前页面的 th:fragment的值

th:replace="layout"

这个一旦声明后,也买你会被替换成layout的内容,这个“layout”指的是 templates/layout.html

th:fragment="content"

<div th:fragment="content">

</div>

<!DOCTYPE html>

<html lang="en" xmlns:th="http://www.thymeleaf.org"

th:replace="layout">

<div th:fragment="content">

<h2>用户列表</h2>

<div>

<a href="/user/add.html">添加用户</a>

</div>

<table>

<thead>

<tr>

<th>

用户名称

</th>

<th>

用户年龄

</th>

<th>

用户邮箱

</th>

</tr>

</thead>

<tbody>

<tr th:each="user : ${users}">

<td th:text="${user.name}"></td>

<td th:text="${user.age}"></td>

<td th:text="${user.email}"></td>

</tr>

</tbody>

</table>

</div>

</html>

第六章——Spring Boot入门

Spring Boot ComponentScan

fm.douban.service、fm.douban.service.impl不是 fm.douban.app的子包,所以不会自动扫描,也不会实例化 Bean

解决办法

@SpringBootApplication(scanBasePackages={"fm.douban.app", "fm.douban.service"})

public class AppApplication {

public static void main(String[] args) {

SpringApplication.run(AppApplication.class, args);

}

}

加一个参数,告知系统需要额外扫描的包

另一种写法

@ComponentScan({"fm.service", "fm.app"})

public class SpringConfiguration {

... ...

}

@RestController等同于使用 @Controller的类的方法上添加 @ResponseBody注解,效果是一样的。

Spring Boot Logger

日志系统的两大优势

  1. 可以轻松的控制日志是否输出

  1. 可以灵活的配置日志的细节,例如输出格式,自动附带输出日志发生的时间

1. 配置

在 src/main/resources/目录下,增加日志级别配置

logging.level.root=info

表示所有日志(root)都为 info级别

logging.level.fm.douban.app=info

表示 fm.douban.app包及其子包种的所有的类都输出 info级别的日志

级别的作用

不会输出更低优先级的日志

2.编码

实例化对象

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;

@RestController

public class SongListControl {

private static final Logger LOG = LoggerFactory.getLogger(SongListControl.class);

@PostConstruct

public void init(){

LOG.info("SongListControl 启动啦");

}

}

写明当前的类,方便之后追踪日志是在哪里输出的

Spring Boot Properties

配置的主要作用,是把可变的内容从代码中剥离出来,在做到不修改代码的情况下,方便的修改这些可变的或常变得内容。这个过程称之为避免硬编码,做到解耦

自定义配置项

song.name=God is a girl

使用配置项

import org.springframework.beans.factory.annotation.Value;

public class SongListControl {

@Value("${song.name}")

private String songName;

}

只需要使用 @Value注解即可

第七章——Spring Session

Cookie

在客户端
读Cookie

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletRequest;

@RequestMapping("/songlist")

public Map index(HttpServletRequest request) {

Map returnData = new HashMap();

returnData.put("result", "this is song list");

returnData.put("author", songAuthor);

Cookie[] cookies = request.getCookies();

returnData.put("cookies", cookies);

return returnData;

}

使用注解读取cookie

import org.springframework.web.bind.annotation.CookieValue;

@RequestMapping("/songlist")

public Map index(@CookieValue("JSESSIONID") String jSessionId) {

Map returnData = new HashMap();

returnData.put("result", "this is song list");

returnData.put("author", songAuthor);

returnData.put("JSESSIONID", jSessionId);

return returnData;

}

写Cookie

import javax.servlet.http.Cookie;

import javax.servlet.http.HttpServletResponse;

@RequestMapping("/songlist")

public Map index(HttpServletResponse response) {

Map returnData = new HashMap();

returnData.put("result", "this is song list");

returnData.put("name", songName);

Cookie cookie = new Cookie("sessionId","CookieTestInfo");

// 设置的是 cookie 的域名,就是会在哪个域名下生成 cookie 值

cookie.setDomain("youkeda.com");

// 是 cookie 的路径,一般就是写到 / ,不会写其他路径的

cookie.setPath("/");

// 设置cookie 的最大存活时间,-1 代表随浏览器的有效期,也就是浏览器关闭掉,这个 cookie 就失效了。

cookie.setMaxAge(-1);

// 设置是否只能服务器修改,浏览器端不能修改,安全有保障

cookie.setHttpOnly(false);

response.addCookie(cookie);

returnData.put("message", "add cookie successful");

return returnData;

}

Spring Session API

不存放在客户端,而是存放在服务端,名字为JSESSIONID的cookie是专门用来记录用户session的

读操作

request.getSession()

session.getAttribute("userLoginInfo")

登录信息类

必须实现序列化接口 Serializable

import java.io.Serializable;

public class UserLoginInfo implements Serializable {

private String userId;

private String userName;

}

读操作

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

@RequestMapping("/songlist")

public Map index(HttpServletRequest request, HttpServletResponse response) {

Map returnData = new HashMap();

returnData.put("result", "this is song list");

// 取得 HttpSession 对象

HttpSession session = request.getSession();

// 读取登录信息

UserLoginInfo userLoginInfo = (UserLoginInfo)session.getAttribute("userLoginInfo");

if (userLoginInfo == null) {

// 未登录

returnData.put("loginInfo", "not login");

} else {

// 已登录

returnData.put("loginInfo", "already login");

}

return returnData;

}

写操作

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

@RequestMapping("/loginmock")

public Map loginMock(HttpServletRequest request, HttpServletResponse response) {

Map returnData = new HashMap();

// 假设对比用户名和密码成功

// 仅演示的登录信息对象

UserLoginInfo userLoginInfo = new UserLoginInfo();

userLoginInfo.setUserId("12334445576788");

userLoginInfo.setUserName("ZhangSan");

// 取得 HttpSession 对象

HttpSession session = request.getSession();

// 写入登录信息

session.setAttribute("userLoginInfo", userLoginInfo);

returnData.put("message", "login successful");

return returnData;

}

知识点 Cookie放在客户端,一般不能超过 4kb,而 Session存放在服务器端,没有限制

Spring Session 配置

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

@Configuration

public class SpringHttpSessionConfig {

@Bean

public TestBean testBean() {

return new TestBean();

}

}

@Configuration注解,表示这是一个配置类,系统会自动扫描并处理
在方法上添加 @Bean驻俄界,表示把此方法返回的对象实例注册成 Bean

Session配置

依赖

<!-- spring session 支持 -->

<dependency>

<groupId>org.springframework.session</groupId>

<artifactId>spring-session-core</artifactId>

</dependency>

配置类

在类上额外加一个注解 @EnableSpringHttpSession,开启 session。然后,注册两个 bean:

  • CookieSerializer:读写Cookies中的SessionId信息

  • MapSessionRepository:Session信息在服务器上的存储仓库

import org.springframework.session.MapSessionRepository;

import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;

import org.springframework.session.web.http.CookieSerializer;

import org.springframework.session.web.http.DefaultCookieSerializer;

import java.util.concurrent.ConcurrentHashMap;

@Configuration

@EnableSpringHttpSession

public class SpringHttpSessionConfig {

@Bean

public CookieSerializer cookieSerializer() {

DefaultCookieSerializer serializer = new DefaultCookieSerializer();

serializer.setCookieName("JSESSIONID");

// 用正则表达式配置匹配的域名,可以兼容 localhost、127.0.0.1 等各种场景

serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");

serializer.setCookiePath("/");

serializer.setUseHttpOnlyCookie(false);

// 最大生命周期的单位是秒

serializer.setCookieMaxAge(24 * 60 * 60);

return serializer;

}

// 当前存在内存中

@Bean

public MapSessionRepository sessionRepository() {

return new MapSessionRepository(new ConcurrentHashMap<>());

}

}

Spring Request 拦截器

创建拦截器

必须实现 HandlerInterceptor接口

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import org.springframework.web.servlet.HandlerInterceptor;

import org.springframework.web.servlet.ModelAndView;

public class InterceptorDemo implements HandlerInterceptor {

// Controller方法执行之前

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

// 只有返回true才会继续向下执行,返回false取消当前请求

return true;

}

//Controller方法执行之后

@Override

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,

ModelAndView modelAndView) throws Exception {

}

// 整个请求完成后(包括Thymeleaf渲染完毕)

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

}

}

preHandle() 方法的参数中有 HttpServletRequest和 HttpServletResponse,可以像 control一样使用 Session

实现WebMvcConfigurer

创建一个类实现 WebMvcConfigurer,并实现 addInterceptors()方法。用于管理拦截器

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration

public class WebAppConfigurerDemo implements WebMvcConfigurer {

@Override

public void addInterceptors(InterceptorRegistry registry) {

// 多个拦截器组成一个拦截器链

// 仅演示,设置所有 url 都拦截

registry.addInterceptor(new UserInterceptor()).addPathPatterns("/**");

}

}

addPathPatterns("/**")表示拦截所有的URL,可以用 `excludePathPatterns() 方法排除某些 `URL

第八章——Spring Data入门

MongoDB配置

依赖

<dependency>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-starter-data-mongodb</artifactId>

</dependency>

配置

# 购买的云服务器的公网 IP

spring.data.mongodb.host=192.168.0.1

# MongoDB 服务的端口号

spring.data.mongodb.port=27017

# 创建的数据库及用户名和密码

spring.data.mongodb.database=practice

Spring Data CRUD

新增数据

import org.springframework.data.mongodb.core.MongoTemplate;

@Autowired

private MongoTemplate mongoTemplate;

public void test() {

Song song = new Song();

song.setSubjectId("s001");

song.setLyrics("...");

song.setName("成都");

mongoTemplate.insert(song);

}

查询数据

mongoTemplate.findById(songId, Song.class)

修改数据

// 修改 id=1 的数据

Query query = new Query(Criteria.where("id").is("1"));

// 把歌名修改为 “new name”

Update updateData = new Update();

updateData.set("name", "new name");

// 执行修改,修改返回结果的是一个对象

UpdateResult result = mongoTemplate.updateFirst(query, updateData, Song.class);

// 修改的记录数大于 0 ,表示修改成功

System.out.println("修改的数据记录数量:" + result.getModifiedCount());

删除数据

Song song = new Song();

song.setId(songId);

// 执行删除

DeleteResult result = mongoTemplate.remove(song);

// 删除的记录数大于 0 ,表示删除成功

System.out.println("删除的数据记录数量:" + result.getDeletedCount());

Spring Data Query

上面是主键查询,现在的是多条件查询

import org.springframework.data.mongodb.core.query.Query;

import org.springframework.data.mongodb.core.query.Criteria;

public List<Song> list(Song songParam) {

// 总条件

Criteria criteria = new Criteria();

// 可能有多个子条件

List<Criteria> subCris = new ArrayList();

if (StringUtils.hasText(songParam.getName())) {

subCris.add(Criteria.where("name").is(songParam.getName()));

}

if (StringUtils.hasText(songParam.getLyrics())) {

subCris.add(Criteria.where("lyrics").is(songParam.getLyrics()));

}

if (StringUtils.hasText(songParam.getSubjectId())) {

subCris.add(Criteria.where("subjectId").is(songParam.getSubjectId()));

}

// 必须至少有一个查询条件

if (subCris.isEmpty()) {

LOG.error("input song query param is not correct.");

return null;

}

// 三个子条件以 and 关键词连接成总条件对象,相当于 name='' and lyrics='' and subjectId=''

criteria.andOperator(subCris.toArray(new Criteria[]{}));

// 条件对象构建查询对象

Query query = new Query(criteria);

// 仅演示:由于很多同学都在运行演示程序,所以需要限定输出,以免查询数据量太大

query.limit(10);

List<Song> songs = mongoTemplate.find(query, Song.class);

return songs;

}

Spring Data分页

构建

  1. 构建分页条件对象

Pageable pageable = PageRequest.of(0 , 20);

query.with(pageable);

第一个参数是页码,第二个参数是每页的数量
  1. 还需要查询总数,才能进一步计算出总共多少页,实现完整的分页功能

  • 调用count(query, XXX.class)查询总数,第一个是查询条件,第二个参数表示查询什么样的对象

  • 根据结果、分页条件、总数三个数据,构建分页器对象

importorg.springframework.data.domain.Page;

importorg.springframework.data.repository.support.PageableExecutionUtils;

// 总数

longcount=mongoTemplate.count(query, Song.class);

// 构建分页器

Page<Song>pageResult=PageableExecutionUtils.getPage(songs, pageable, newLongSupplier() {

@Override

publiclonggetAsLong() {

returncount;

}

});

注意对应的返回值应该重构为 Page<Song>表示返回分页结果对象

使用

Page<Song>songResult=songService.list(queryParam);

List<Song>songs=songResult.getContent();

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值