为什么叫随记呢

因为前面的笔记过于详细,从头到尾完整记下来会花掉很多时间…

so这篇就记得随意一点吧

Spring和SpringMVC都只是快速过了一下(跟没看一样 ^_^

Web容器基本配置

image-20230331195718253

Tomcat配置

日志

image-20230331195631263

HTTPS证书

生成秘钥复制到resource目录下

keytool -genkey -alias myhttps -keyalg RSA -keysize 2048 -keystore zhengru_key.p12 -validity 365

application.properties中配置证书

server.ssl.key-alias=myhttps
server.ssl.key-store=classpath:zhengru_key.p12
server.ssl.key-store-password=123456

配置文件名称和路径

位置问题

位置可以在一下四个地方

当然也可以用spring.config.location来指定其他位置

配置文件位置时,值一定要以/结尾

例如:spring.config.location=classpath:/javaboy/

image-20230401131512162

文件名问题

默认是叫application但是也可以指定其他的文件名

spring.config.name来指定

例如:spring.config.name=app.properties

image-20230401132239361

普通的属性注入

spring里的东西,跟springboot没有关系

image-20230401133923691

使用Value注解属性注入,数组也可以注入但是注意要用英文逗号隔开

image-20230401134049944

这里注意要在设置中更改文件编码为UTF-8

类型安全的属性注入

使用@ConfigurationProperties(prefix = "xxx")设置前缀是xxx,就会自动注入

@Component
@PropertySource("classpath:book.properties")
@ConfigurationProperties(prefix = "book")
public class Book {
    private String name;
    private String author;
    private String[] tags;
    //...
}

在配置文件中引用maven配置

需要使用@xxx@,因为会和${}冲突

短命令行参数

java -jar properties-0.0.1-SNAPSHOT.jar

改端口

java -jar properties-0.0.1-SNAPSHOT.jar --server.port=8081

YAML配置

Spring Boot 中的配置文件有两种格式,properties(默认)或者 yaml(yml也可)

属性注入

英文冒号后面需要加一个空格!

注意数组对象注入的形式

book:
  name: 三国演义
  tags:
    - 历史
    - 小说
    - 明代
  authors:
    - name: 罗贯中
      age: 88
    - name: 施耐庵
      age: 99

Profile问题

生产环境切换

spring.profiles.active=dev
spring.profiles.active=prod
spring.profiles.active=test

image-20230401143424135

日志配置

SpringBoot默认的日志实现是Logback

详细配置可以看松哥的这篇文章 点此跳转

SpingBoot+Thymeleaf

松哥的文章 点此跳转

与传统Java模板引擎不同的是,Thymeleaf支持HTML原型

实践

创建项目时就可以添加依赖

image-20230401165235093

# 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

写一个UserController

@Controller
public class UserController {
    @GetMapping("/hello")
    public String index(Model model){
        List<User> users = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            User u = new User();
            u.setId(i);
            u.setUsername("zhengru:"+i);
            u.setAddress("zhengru.top:"+i);
            users.add(u);
        }
        model.addAttribute("users",users);
        return "hello";
    }
}

在template目录下创建hello.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <table border="1">
        <tr th:each = "u : ${users}">
            <td th:text = "${u.id}"></td>
            <td th:text = "${u.username}"></td>
            <td th:text = "${u.address}"></td>
        </tr>
    </table>
</body>
</html>

手动渲染

@SpringBootTest
class ThymeleafApplicationTests {
    @Autowired
    TemplateEngine templateEngine;
    @Test
    void contextLoads() {
        Context ctx = new Context();
        ctx.setVariable("username","邹川瑞");
        ctx.setVariable("position","Java高级开发工程师");
        ctx.setVariable("salary","600000");
        String mail = templateEngine.process("mail", ctx);
        System.out.println(mail);
    }
}

表达式语法

说一下${}、*{}、#{}、@{}

${}

普通引用

*{}

可以实现以下操作

<div th:object="${user}">
    <div th:text="*{username}"></div>
    <div th:text="*{address}"></div>
    <div th:text="*{id}"></div>
</div>

#{}

一般用于国际化

image-20230401211329154

<div th:text="#{hello}"></div>

@{}

<script th:src="@{https://localhost:8080/hello.js}"></script>
<script th:src="@{~/hello.js}"></script>
<script th:src="@{//localhost:8080/hello.js}"></script>
<script th:src="@{//localhost:8080/hello.js(name='zhengru',age=99)}"></script>
<script th:src="@{/hello.js}"></script>

字面量

  • 文本字面量:’one text’, ‘Another one!’,…
  • 数字字面量:0, 34, 3.0, 12.3,…
  • 布尔字面量:true, false
  • Null字面量:null
  • 字面量标记:one, sometext, main,…

如果文本是英文,并且不包含空格、逗号等字符,可以不用加单引号

文本运算

如果字符串中包含变量,也可以使用另一种简单的方式,叫做字面量置换,用 | 代替 '...' + '...',如下:

th:with 定义了一个局部变量 age,在其所在的 div 中可以使用该局部变量

布尔运算

  • 二元运算符:and, or
  • 布尔非(一元运算符):!, not

比较和相等

表达式里的值可以使用 >, <, >=<= 符号比较。==!= 运算符用于检查相等(或者不相等)。注意 XML规定 <> 标签不能用于属性值,所以应当把它们转义为 <>

如果不想转义,也可以使用别名:gt (>);lt (<);ge (>=);le (<=);not (!)。还有 eq (==), neq/ne (!=)

条件运算符

类似于我们 Java 中的三目运算符

内置对象

基本内置对象:

  • #ctx:上下文对象。
  • #vars: 上下文变量。
  • #locale:上下文区域设置。
  • #request:(仅在 Web 上下文中)HttpServletRequest 对象。
  • #response:(仅在 Web 上下文中)HttpServletResponse 对象。
  • #session:(仅在 Web 上下文中)HttpSession 对象。
  • #servletContext:(仅在 Web 上下文中)ServletContext 对象。

实用内置对象:

  • #execInfo:有关正在处理的模板的信息。
  • #messages:在变量表达式中获取外部化消息的方法,与使用#{…}语法获得的方式相同。
  • #uris:转义URL / URI部分的方法
  • #conversions:执行配置的转换服务(如果有)的方法。
  • #dates:java.util.Date对象的方法:格式化,组件提取等
  • #calendars:类似于#dates但是java.util.Calendar对象。
  • #numbers:用于格式化数字对象的方法。
  • #strings:String对象的方法:contains,startsWith,prepending / appending等
  • #objects:一般对象的方法。
  • #bools:布尔评估的方法。
  • #arrays:数组方法。
  • #lists:列表的方法。
  • #sets:集合的方法。
  • #maps:地图方法。
  • #aggregates:在数组或集合上创建聚合的方法。
  • #ids:处理可能重复的id属性的方法(例如,作为迭代的结果)。

设置属性值

<img th:attr="src=@{/1.png},title=${user.username},alt=${user.username}">
<img src="/myapp/1.png" title="javaboy" alt="javaboy">
<img th:src="@{/1.png}" th:alt="${user.username}" th:title="${user.username}">
<img th:src="@{/1.png}" th:alt-title="${user.username}">

遍历

<table border="1">
    <tr th:each="u : ${users}">
        <td th:text="${u.username}"></td>
        <td th:text="${u.address}"></td>
    </tr>
</table>

还有很多细节可以看松哥的文章

分支语句

th:if

th:unless

th:switch…

本地变量

这个我们前面已经涉及到了,使用 th:with 可以定义一个本地变量

内联

<div>hello [[${user.username}]]</div>

[[...]] 对应于 th:text (结果会是转义的 HTML)

[(...)]对应于 th:text,它不会执行任何的 HTML 转义

<div th:with="str='hello <strong>javaboy</strong>'">
    <div>[[${str}]]</div>
    <div>[(${str})]</div>
</div>

显示效果

image-20230401220303266

详细看松哥的文章 点此跳转

SpingBoot+Freemarker

文章地址 点此跳转

FreeMarkerProperties中则配置了Freemarker的基本信息,例如模板位置在 classpath:/templates/ ,再例如模板后缀为 .ftl,那么这些配置我们以后都可以在application.properties中进行修改

Json

SpringMVC框架中已经自动配置了jacksongsonHTTPMessageConverter

只需要添加依赖就能使用

序列化:对象 -> JSON(响应JSON)

反序列化:JSON -> 对象(请求参数是JSON)

Jackson

指定属性序列化/反序列化时的名称

@JsonProperty("name")
private String username;

忽略字段

忽略某一个字段

@JsonIgnore
private Integer id;

忽略某些字段

@JsonIgnoreProperties({"id","username"})
public class User {
    private Integer id;
    private String username;
    private String address;
}

日期格式化

局部配置

@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai")
private Date birthday;

全局配置

@Configuration
public class WebMvcConfig {
    @Bean
    ObjectMapper ObjectMapper(){
        ObjectMapper om = new ObjectMapper();
        om.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        return om;
    }
}

Gson

就放两个图因为我也没试过

一个是application.properties

image-20230403192630985

另一个是WebMvcConfig

image-20230403193038355

FastJson

需要自己配,可以在方法中或者是自己配一个bean

image-20230403193308308

静态资源

一共有五个位置可以放置静态资源(优先级)

  1. classpath:/META-INF/resources/
  2. classpath:/resources/
  3. classpath:/static/
  4. classpath:/public/
  5. /

第五个用的比较少

自定义配置

application.properties

第一行配置表示定义资源位置,第二行配置表示定义请求 URL 规则

spring.web.resources.static-locations=classpath:/
spring.mvc.static-path-pattern=/**

Java代码定义

这里按照我的理解就是在用addResourceHandler映射实际资源的位置也就是addResourceLocations

我可以通过/123/01.html来访问/zhengru/01.html这个资源

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/123/**").addResourceLocations("classpath:/zhengru/");
    }
}

文件上传

单文件上传

写一个简单的页面

<form action="/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="file">
  <input type="submit">
</form>

写一个controller

@RestController
public class FileUploadController {
    SimpleDateFormat sdf = new SimpleDateFormat("/yyyy/MM/dd/");
    @PostMapping("/upload")
    public String upload(MultipartFile file, HttpServletRequest req){
        String realPath = req.getServletContext().getRealPath("/");
        String format = sdf.format(new Date());
        String path = realPath + format;
        File folder = new File(path);
        if (!folder.exists()){
            folder.mkdirs();
        }
        String oldName = file.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
        try {
            file.transferTo(new File(folder,newName));
            String s = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName;
            return s;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
}

可以在application.properties中配置其他信息

比如限制单个文件的大小和限制所有文件的大小

spring.servlet.multipart.max-file-size=1MB
spring.servlet.multipart.max-request-size=10MB

多文件上传

合并多文件上传

<form action="/upload2" method="post" enctype="multipart/form-data">
  <input type="file" name="files" multiple>
  <input type="submit">
</form>

部分代码

try {
    for (MultipartFile file : files) {
        String oldName = file.getOriginalFilename();
        String newName = UUID.randomUUID().toString() + oldName.substring(oldName.lastIndexOf("."));
        file.transferTo(new File(folder,newName));
        String s = req.getScheme() + "://" + req.getServerName() + ":" + req.getServerPort() + format + newName;
        System.out.println(s);
    }
} catch (IOException e) {
    e.printStackTrace();
}

独立多文件上传

<form action="/upload3" method="post" enctype="multipart/form-data">
    <input type="file" name="file1">
    <input type="file" name="file2">
    <input type="file" name="file3">
    <input type="submit">
</form>
public String upload(MultipartFile file1,MultipartFile file2,MultipartFile file3, HttpServletRequest req){
	//...
}

Ajax上传

记得引入jQuery

<div id="result"></div>
<input type="file" id="file">
<input type="button" value="上传" onclick="uploadFile()">
<script language="JavaScript">
    function uploadFile(){
        var file = $("#file")[0].files[0];
        var formData = new FormData();
        formData.append("file",file);
        $.ajax({
            type:'post',
            url:'/upload',
            processData:false,
            contentType:false,
            data:formData,
            success:function (msg) {
                $("#result").html(msg)
            }
        })
    }
</script>

ControllerAdvice注解的使用

全局异常处理

//@ControllerAdvice
@RestControllerAdvice
public class MyGlobalException {
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public ModelAndView customException(MaxUploadSizeExceededException e) throws IOException {
        ModelAndView mv = new ModelAndView("myerror");
        mv.addObject("error",e.getMessage());
        return mv;
    }
}

全局数据绑定

ModelAttribute

后面用到了ModelAttribute注解,是MVC里的内容,先学一下

主要有两个作用

  1. 在数据回显时,给变量定义别名
  2. 定义全局数据

当用户访问当前Controller中的任意一个方法,在返回数据时,都会将添加了@ModelAttribute注解的方法的返回值,一起返回给前端

定义全局数据

@ControllerAdvice
public class MyGlobalData {
    @ModelAttribute("info")
    public Map<String,String> mydata(){
        Map<String,String> info = new HashMap<>();
        info.put("username","dzr");
        info.put("address","zhengru.top");
        return info;
    }
}
@RestController
public class HelloController {
    @GetMapping("/hello")
    public void hello(Model model){
        Map<String,Object> asMap = model.asMap();
        Map<String,String> info = (Map<String,String>) asMap.get("info");
        Set<String> keySet = info.keySet();
        for (String s : keySet) {
            System.out.println(s + "————" + info.get(s));
        }
    }
}

全局数据预处理

@ControllerAdvice
public class MyGlobalData {
    @InitBinder("a")
    public void a(WebDataBinder binder){
        binder.setFieldDefaultPrefix("a.");
    }

    @InitBinder("b")
    public void b(WebDataBinder binder){
        binder.setFieldDefaultPrefix("b.");
    }
}
@RestController
public class BookController {
    @PostMapping("/book")
    public void addBook(@ModelAttribute("b") Book book,@ModelAttribute("a") Author author){
        System.out.println(book);
        System.out.println(author);
    }
}

关于异常

异常页面定义

404问题自动寻找异常页面的优先级,其他同理

  1. templates/error/404.html
  2. static/error/404.html
  3. templates/error/4xx.html
  4. static/error/4xx.html

使用动态显示异常

<table border="1px">
  <tr>
    <td>path</td>
    <td th:text="${path}"></td>
  </tr>
  <tr>
    <td>error</td>
    <td th:text="${error}"></td>
  </tr>
  <tr>
    <td>message</td>
    <td th:text="${message}"></td>
  </tr>
  <tr>
    <td>timestamp</td>
    <td th:text="${timestamp}"></td>
  </tr>
  <tr>
    <td>status</td>
    <td th:text="${status}"></td>
  </tr>
</table>

自定义异常

@Component
public class MyErrorAtributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, ErrorAttributeOptions options) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, options);
        if ((Integer) map.get("status") == 404){
            map.put("message","页面不存在");
        }
        return map;
    }
}

跨域问题

第一种方式可以使用@CrossOrigin注解来让某一个类或某一个方法接收某个域的请求

@CrossOrigin(value = "http://localhost:8081/",maxAge = 1800)

第二种方式可以使用全局配置来一次性解决

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
//    @Override
//    public void addCorsMappings(CorsRegistry registry) {
//        registry.addMapping("/**")
//                .allowedHeaders("*")
//                .allowedMethods("*")
//                .allowedOrigins("http://localhost:8081/")
//                .maxAge(1800);
//    }
    @Bean
    CorsFilter corsFilter(){
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration cfg = new CorsConfiguration();
        cfg.addAllowedOrigin("http://localhost:8081");
        cfg.addAllowedMethod("*");
        source.registerCorsConfiguration("/**",cfg);
        return new CorsFilter(source);
    }
}

拦截器

  • preHandle
    • 返回false,请求将不再继续执行
  • postHandle
    • Controller执行之后被调用
  • afterCompletion
    • preHandle返回true才会执行
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        System.out.println("postHandle");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        System.out.println("afterCompletion");
    }
}
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MyInterceptor()).addPathPatterns("/**")
                .excludePathPatterns("/hello");
    }
}
@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "hello1";
    }
    @GetMapping("hello2")
    public String hello2(){
        return "hello2";
    }
}

系统启动任务

CommandLineRunner

@Component
@Order(100)
public class MyCommandLineRunner01 implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("args1 = " + Arrays.toString(args));
    }
}
@Component
@Order(99)
public class MyCommandLineRunner02 implements CommandLineRunner {
    @Override
    public void run(String... args) throws Exception {
        System.out.println("args2 = " + Arrays.toString(args));
    }
}

ApplicationRunner

两个用法差异不大,主要体现在对参数的处理上 ,ApplicationRunner的参数类型更加丰富一些

@Component
@Order(98)
public class MyApplicationRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        List<String> nonOptionArgs = args.getNonOptionArgs();
        System.out.println("nonOptionArgs = " + nonOptionArgs);
        Set<String> optionNames = args.getOptionNames();
        for (String optionName : optionNames) {
            System.out.println(optionName + "-->" + args.getOptionValues(optionName));
        }
        String[] sourceArgs = args.getSourceArgs();
        System.out.println("sourceArgs = " + Arrays.toString(sourceArgs));
    }
}

整合Web组件

Servlet

@WebServlet("/hello")
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("MyServlet");
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

Filter

@WebFilter("/*")
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}

Listener

@WebListener
public class MyListener extends RequestContextListener {
    @Override
    public void requestInitialized(ServletRequestEvent requestEvent) {
        System.out.println("requestInitialized");
    }

    @Override
    public void requestDestroyed(ServletRequestEvent requestEvent) {
        System.out.println("requestDestroyed");
    }
}

扫描

@SpringBootApplication
@ServletComponentScan("top.zhengru.webcomponent")	//记得加
public class WebcomponentApplication {
    public static void main(String[] args) {
        SpringApplication.run(WebcomponentApplication.class, args);
    }
}

注册过滤器

除了用WebFilterComponent注解以外,还可以用Configuration

并且这个方法能够解决无法同时指定拦截路径和优先级的问题

@Configuration
public class FilterConfiguration {
    @Bean
    FilterRegistrationBean<MyFilter04> filter04FilterRegistrationBean04(){
        FilterRegistrationBean<MyFilter04> bean = new FilterRegistrationBean<>();
        bean.setOrder(90);
        bean.setFilter(new MyFilter04());
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
    @Bean
    FilterRegistrationBean<MyFilter05> filter05FilterRegistrationBean05(){
        FilterRegistrationBean<MyFilter05> bean = new FilterRegistrationBean<>();
        bean.setOrder(89);
        bean.setFilter(new MyFilter05());
        bean.setUrlPatterns(Arrays.asList("/*"));
        return bean;
    }
}

参数类型转换

如果是key/value的形式传入的参数,会使用自定义类型转换器处理(比如下面的MyDateConverter)

如果是传入json则可以使用@RequestBody注解,不需要类型转换器,通过HttpMessageConverter转换为对象

@RestController
public class UserController {
    @PostMapping("/user")
    public void addUser(User user){
        System.out.println(user);
    }

    @PostMapping("/user2")
    public void addUser2(@RequestBody User user){
        System.out.println(user);
    }
}
@Component
public class MyDateConverter implements Converter<String,Date> {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    @Override
    public Date convert(String source) {
        try {
            return sdf.parse(source);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }
}

项目首页和角标

优先找静态然后再找动态

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/index").setViewName("index");
    }
}

角标就在那五个地方放一个favicon.ico即可,不需要其他的配置

AOP

@Component
@Aspect
public class LogAspect {
    @Pointcut("execution(* top.zhengru.aop.Service.*.*(..))")
    public void pc1(){
    }
    @Before("pc1()")
    public void before(JoinPoint jp){
        String name = jp.getSignature().getName();
        System.out.println(name + "方法开始执行了");
    }

    @After("pc1()")
    public void after(JoinPoint jp){
        String name = jp.getSignature().getName();
        System.out.println(name + "方法结束了");
    }

    @AfterReturning(value = "pc1()",returning = "s")
    public void afterReturning(JoinPoint jp,String s){
        String name = jp.getSignature().getName();
        System.out.println(name + "方法返回值是 " + s);
    }

    @AfterThrowing(value = "pc1()",throwing = "e")
    public void afterThrowing(JoinPoint jp,Exception e){
        String name = jp.getSignature().getName();
        System.out.println(name + "方法抛出了异常 " + e);
    }

    @Around("pc1()")
    public Object around(ProceedingJoinPoint pjp){
        try {
            Object proceed = pjp.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }
}

JdbcTemplate

GeneratedKeyHolder来获取主键

@Service
public class UserService {
    @Autowired
    JdbcTemplate jdbcTemplate;

    public int addUser(User user){
        int result = jdbcTemplate.update("insert into user (username,address) values (?,?)",user.getUsername(),user.getAddress());
        return result;
    }

    public int addUser2(User user){
        GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
        int res = jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
                PreparedStatement ps = con.prepareStatement("insert into user (username,address) values (?,?)", Statement.RETURN_GENERATED_KEYS);
                ps.setString(1,user.getUsername());
                ps.setString(2,user.getAddress());
                return ps;
            }
        },keyHolder);
        user.setId(keyHolder.getKey().longValue());
        return res;
    }

    public List<User> getAllUser(){
        List<User> list = jdbcTemplate.query("select * from user", new RowMapper<User>() {
            @Override
            public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                String username = rs.getString("username");
                String address = rs.getString("address");
                long id = rs.getLong("id");
                User user = new User();
                user.setId(id);
                user.setUsername(username);
                user.setAddress(address);
                return user;
            }
        });
        return list;
    }
}

配置多数据源

image-20230408131738856

@Configuration
public class DataSourceConfig {
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.one")
    DataSource dsOne() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.two")
    DataSource dsTwo() {
        return DruidDataSourceBuilder.create().build();
    }
}
@Configuration
public class JdbcTemplateConfig {
    @Bean
    JdbcTemplate jdbcTemplateOne(@Qualifier("dsOne") DataSource dsOne) {
        return new JdbcTemplate(dsOne);
    }

    @Bean
    JdbcTemplate jdbcTemplateTwo(@Qualifier("dsTwo") DataSource dsTwo) {
        return new JdbcTemplate(dsTwo);
    }
}

MyBatis

可以在MybatisApplication前面加上@MapperScan注解全局配置扫描Mapper

其他的地方都差不多

@Autowired
UserMapper userMapper;

XML的话可以在application.properties中配置mapper文件的目录

#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml

配置多数据源

有点麻烦,下面是松哥的pdf截图

image-20230408155731369

image-20230408155748537

image-20230408155757488

image-20230408155813674

Redis

redis还没学就先简单看了一下

@SpringBootTest
class RedisApplicationTests {
    @Autowired
    RedisTemplate redisTemplate;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Test
    void contextLoads() {
        User user = new User();
        user.setUsername("dzr");
        user.setAddress("zhengru.top");
        ValueOperations ops = redisTemplate.opsForValue();
        ops.set("u",user);
        User u = (User) ops.get("u");
        System.out.println(u);
    }

    @Test
    void test1(){
        ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
        ops.set("dzr","zhengru.top");
        String dzr = ops.get("dzr");
        System.out.println("dzr = " + dzr);
    }
}

Session共享

分别在80808081端口开启服务,发现8081可以共享8080session,因为Spring Session就是使用 Spring 中的代理过滤器,将所有的 Session 操作拦截下来,自动的将数据 同步到 Redis 中,或者自动的从 Redis 中读取数据

@RestController
public class HelloController {
    @Value("${server.port}")
    Integer port;

    @GetMapping("/set")
    public String set(HttpSession session){
        session.setAttribute("dzr","zhengru.top");
        return String.valueOf(port);
    }

    @GetMapping("/get")
    public String get(HttpSession session){
        String dzr = (String) session.getAttribute("dzr");
        return dzr + ":" + port;
    }
}

Redis处理接口幂等性

通过拦截器,检测有没有@AutoIdempotent注解,如果有则checkTokenredis中没有token就返回token不存在,相应的,如果先前已经生成了token,就返回hello

这里放出核心的部分

@Service
public class RedisService {
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    public boolean setEx(String key,String value,Long expireTime){
        boolean result = false;
        try {
            ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
            ops.set(key,value);
            stringRedisTemplate.expire(key,expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e){
            e.printStackTrace();
        }
        return result;
    }

    public boolean exists(String key){
        return stringRedisTemplate.hasKey(key);
    }

    public boolean remove(String key){
        if (exists(key)){
            return stringRedisTemplate.delete(key);
        }
        return false;
    }
}
public class TokenService {
    @Autowired
    RedisService redisService;

    public String createToken(){
        String uuid = UUID.randomUUID().toString();
        redisService.setEx(uuid,uuid,10000L);
        return uuid;
    }

    public boolean checkToken(HttpServletRequest request) throws IdempotentException {
        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)){
            token = request.getParameter("token");
            if (StringUtils.isEmpty(token)){
                throw new IdempotentException("Token不存在");
            }
        }
        if (!redisService.exists(token)){
            throw new IdempotentException("重复操作");
        }
        boolean remove = redisService.remove(token);
        if (!remove) {
            throw new IdempotentException("重复操作");
        }
        return true;

    }
}
@Component
public class IdempotentInterceptor implements HandlerInterceptor {
    @Autowired
    TokenService tokenService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)){
            return true;
        }
        Method method = ((HandlerMethod) handler).getMethod();
        AutoIdempotent idempotent = method.getAnnotation(AutoIdempotent.class);
        if (idempotent != null) {
            try {
                return tokenService.checkToken(request);
            } catch (IdempotentException e){
                throw e;
            }
        }
        return true;
    }
}

image-20230410105621556

RESTful

在SpringBoot中构建RESTful非常容易

用法

查询

get

http://localhost:8080/users/1

添加

post/json添加数据

http://localhost:8080/users

修改

put/json

http://localhost:8080/users/8

分页查询

http://localhost:8080/users?page=0&size=3&sort=id,desc

实现

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String address;
    //...
}
public interface UserDao extends JpaRepository<User,Long> {
}

Spring Cache

@SpringBootApplication
@EnableCaching	//开启缓存功能
public class CacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(CacheApplication.class, args);
    }

}

@CacheConfig

这个注解在类上使用,用来描述该类中所有方法使用的缓存名称,当然也可以不使用该注解,直接在具体的缓存注解上配置名称

@Service
@CacheConfig(cacheNames = "c1")
public class UserService {
}

@Cacheable

这个注解一般加在查询方法上,表示将一个方法的返回值缓存起来,默认情况下,缓存的key就是方法的参数,缓存的value就是方法的返回值

@Cacheable(key = "#id")
public User getUserById(Integer id,String username) {
    System.out.println("getUserById");
    return getUserFromDBById(id);
}

@CachePut

这个注解一般加在更新方法上,当数据库中的数据更新后,缓存中的数据也要跟着更新,使用该注解,可以将方法的返回值自动更新到已经存在的key上

@CachePut(key = "#user.id")
public User updateUserById(User user) {
    return user;
}

@CacheEvict

这个注解一般加在删除方法上,当数据库中的数据删除后,相关的缓存数据也要自动清除,该注解在使用的时候也可以配置按照某种条件删除(condition属性)或者或者配置清除所有缓存(allEntries属性)

@CacheEvict()
public void deleteUserById(Integer id) {
    //在这里执行删除操作, 删除是去数据库中删除
}

SpringSecurity

这里的安全内容准备单独写一篇,所以先跳过了

WebSocket

下面是使用WebSocket实现的一个聊天室(多人)

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker("/topic");
    }
}
@Controller
public class GreetingController {
    @MessageMapping("/hello")
    @SendTo("/topic/greetings")
    public Message greeting(Message message){
       return message;
    }

}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="/webjars/jquery/3.5.1/dist/jquery.min.js"></script>
    <script src="/webjars/sockjs-client/1.1.2/sockjs.min.js"></script>
    <script src="/webjars/stomp-websocket/2.3.3/stomp.min.js"></script>
</head>
<body>
<div>
    <label for="username">请输入用户名:</label>
    <input type="text" id="username" placeholder="用户名">
</div>
<div>
    <input type="button" value="连接" id="connect">
    <input type="button" value="断开连接" id="disconnect" disabled="disabled">
</div>
<div id="chat">

</div>
<div>
    <label for="content">请输入聊天内容:</label>
    <input type="text" id="content" placeholder="聊天内容">
</div>
<button id="send">发送</button>
<script>
    var stompClient;
    $(function (){
        $("#connect").click(function (){
            connect();
            $("#send").click(function (){
                stompClient.send("/hello",{},JSON.stringify({'name':$("#username").val(),'content':$("#content").val()}))
            })
        })
    })
    function connect(){
        if (!$("#username").val()){
            return;
        }
        var soketjs = new SockJS("/chat");
        stompClient = Stomp.over(soketjs);
        stompClient.connect({},function (frame){
            setConnect(true)
            stompClient.subscribe("/topic/greetings",function (greeting){
                var msgContent = JSON.parse(greeting.body);
                $("#chat").append("<div>" + msgContent.name + ":" + msgContent.content + "</div>")
            })
        })
    }
    function setConnect(connected){
        $("#connect").prop("disabled",connected);
        $("#disconnect").prop("disabled",!connected)
    }
</script>
</body>
</html>

ActiveMQ

spring.activemq.broker-url=tcp://127.0.0.1:61616
spring.activemq.packages.trust-all=true
spring.activemq.user=admin
spring.activemq.password=admin
@SpringBootApplication
public class JmsApplication {

    public static void main(String[] args) {
        SpringApplication.run(JmsApplication.class, args);
    }

    @Bean
    Queue queue(){
        return new ActiveMQQueue("dzr-queue");
    }

}
@Component
public class JmsComponent {
    @Autowired
    JmsMessagingTemplate jmsMessagingTemplate;
    @Autowired
    Queue queue;

    public void send(Message message){
        jmsMessagingTemplate.convertAndSend(this.queue,message);
    }

    @JmsListener(destination = "dzr-queue")
    public void receive(Message msg){
        System.out.println("msg = " + msg);
    }
}
@SpringBootTest
class JmsApplicationTests {
    @Autowired
    JmsComponent jmsComponent;

    @Test
    void contextLoads() {
        Message message = new Message();
        message.setContent("hello dzr");
        message.setDate(new Date());
        jmsComponent.send(message);
    }

}

RabbitMQ

Direct模式

这里注释的部分可以省略

@Configuration
public class DirectConfig {
    @Bean
    Queue directQueue(){
        return new Queue("dzr-queue");
    }

//    @Bean
//    DirectExchange directExchange(){
//        return new DirectExchange("dzr-direct",true,false);
//    }
//
//    @Bean
//    Binding directBinding(){
//        return BindingBuilder.bind(directQueue()).to(directExchange()).with("direct");
//    }
}
@Component
public class DirectReceiver {
    @RabbitListener(queues = "dzr-queue")
    public void handler(String msg){
        System.out.println("msg = " + msg);
    }
}
@Test
void contextLoads() {
    rabbitTemplate.convertAndSend("dzr-queue","hello,dzr!");
}

Fanout模式

@Configuration
public class FanoutConfig {
    @Bean
    Queue queueOne(){
        return new Queue("queue-one");
    }

    @Bean
    Queue queueTwo(){
        return new Queue("queue-two");
    }

    @Bean
    FanoutExchange fanoutExchange(){
        return new FanoutExchange("dzr",true,false);
    }

    @Bean
    Binding bindingOne(){
        return BindingBuilder.bind(queueOne()).to(fanoutExchange());
    }

    @Bean
    Binding bindingTwo(){
        return BindingBuilder.bind(queueTwo()).to(fanoutExchange());
    }

}
@Component
public class FaoutReceiver {
    @RabbitListener(queues = "queue-one")
    public void handler1(String msg){
        System.out.println("msg = " + msg);
    }

    @RabbitListener(queues = "queue-two")
    public void handler2(String msg){
        System.out.println("msg = " + msg);
    }

}
@Test
void contextLoads() {
    rabbitTemplate.convertAndSend("dzr-fanout",null,"hello dzr!");
}

Topic模式

@Configuration
public class TopicConfig {
    @Bean
    Queue xiaomi(){
        return new Queue("xiaomi");
    }

    @Bean
    Queue huawei(){
        return new Queue("huawei");
    }
    @Bean
    Queue phone(){
        return new Queue("phone");
    }
    @Bean
    TopicExchange topicExchange(){
        return new TopicExchange("dzr-topic",true,false);
    }

    @Bean
    Binding xiaomiBinding(){
        return BindingBuilder.bind(xiaomi()).to(topicExchange()).with("xiaomi.#");
    }

    @Bean
    Binding huaweiBinding(){
        return BindingBuilder.bind(huawei()).to(topicExchange()).with("huawei.#");
    }

    @Bean
    Binding phoneBinding(){
        return BindingBuilder.bind(phone()).to(topicExchange()).with("#.phone.#");
    }

}
@Component
public class TopicReceiver {
    @RabbitListener(queues = "phone")
    public void handler1(String msg){
        System.out.println("msg:phone = " + msg);
    }

    @RabbitListener(queues = "xiaomi")
    public void handler2(String msg){
        System.out.println("msg:xiaomi = " + msg);
    }

    @RabbitListener(queues = "huawei")
    public void handler3(String msg){
        System.out.println("msg:huawei = " + msg);
    }
}
@Test
void contextLoads() {
    rabbitTemplate.convertAndSend("dzr-topic","xiaomi.news","小米新闻");
    rabbitTemplate.convertAndSend("dzr-topic","xiaomi.phone","小米手机");
    rabbitTemplate.convertAndSend("dzr-topic","huawei.phone","华为手机");
}

Header模式

用的比较少,节约篇幅就不放代码了

Mail

下面的username就是发件人邮箱,passwordPOP3/SMTP协议的授权码(不是邮箱密码!)

spring.mail.host=smtp.163.com
spring.mail.port=25
spring.mail.username=***
spring.mail.password=***
spring.mail.default-encoding=UTF-8
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.debug=true

contextLoads是普通邮件,test1是附件邮件,test2是图片资源邮件,test3是发送freemaker模板

package top.zhengru.mail;

import freemarker.core.Configurable;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import top.zhengru.mail.model.User;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Date;

@SpringBootTest
class MailApplicationTests {

    @Autowired
    JavaMailSender javaMailSender;

    @Test
    void contextLoads() {
        SimpleMailMessage simMsg = new SimpleMailMessage();
        simMsg.setFrom("***");
        simMsg.setTo("***");
        simMsg.setSentDate(new Date());
        simMsg.setSubject("邮件主题-测试邮件");
        simMsg.setText("邮件内容-测试邮件");
        javaMailSender.send(simMsg);
    }

    @Test
    void test1() throws MessagingException {
        File file = new File("C:/Users/董政儒/Desktop/头像.jpg");
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setFrom("***");
        helper.setTo("***");
        helper.setSentDate(new Date());
        helper.setSubject("邮件主题-测试邮件");
        helper.setText("邮件内容-测试邮件");
        helper.addAttachment(file.getName(),file);
        javaMailSender.send(mimeMessage);
    }

    @Test
    void test2() throws MessagingException {
        File file = new File("C:/Users/董政儒/Desktop/头像.jpg");
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setFrom("***");
        helper.setTo("***");
        helper.setSentDate(new Date());
        helper.setSubject("邮件主题-测试邮件");
        helper.setText("<div>hello,这是一封带图片资源的邮件...</div><div><img src='cid:p01' /></div>",true);
        helper.addInline("p01",file);
        javaMailSender.send(mimeMessage);
    }

    @Test
    void test3() throws MessagingException, IOException, TemplateException {
        MimeMessage mimeMessage = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
        helper.setFrom("***");
        helper.setTo("***");
        helper.setSentDate(new Date());
        helper.setSubject("邮件主题-测试邮件");
        Configuration cfg = new Configuration(Configuration.getVersion());
        cfg.setClassLoaderForTemplateLoading(MailApplicationTests.class.getClassLoader(),"mail");
        Template template = cfg.getTemplate("mail.ftl");
        User user = new User();
        user.setUsername("***");
        user.setCompany("***");
        user.setPosition("***");
        user.setSalary(8000.0);
        StringWriter out = new StringWriter();
        template.process(user,out);
        helper.setText(out.toString(), true);
        javaMailSender.send(mimeMessage);
    }
}

定时任务

@Scheduled

记得在@SpringBootApplication下加@EnableScheduled

  • fixedDelay
    • 控制方法执行的间隔时间,是以上一次方法执行完开始算起,如上一次方法执行阻塞住了,那么直到上一次执行完,并间隔给定的时间后,执行下一次
  • fixedRate
    • 是按照一定的速率执行,是从上一次方法执行开始的时间算起,如果上一次方法阻塞住了,下一次也是不会执行,但是在阻塞这段时间内累计应该执行的次数,当不再阻塞时,一下子把这些全部执行掉,而后再按照固定速率继续执行
  • initialDelay
    • 如:@Scheduled(initialDelay = 10000,fixedRate = 15000,这个定时器就是在上一个的基础上加了一个initialDelay = 10000 意思就是在容器启动后,延迟10秒后再执行一次定时器,以后每15秒再执行一次该定时器
  • cron
    • 表达式可以定制化执行任务,但是执行的方式是与fixedDelay相近的,也是会按照上一次方法结束时间开始算起
@Component
public class MySchedule {
    @Scheduled(fixedDelay = 1000)
    public void fixedDelay(){
        System.out.println("fixedDelay:" + new Date());
    }

    @Scheduled(fixedRate = 1000)
    public void fixedRate(){
        System.out.println("fixedRate:" + new Date());
    }

    @Scheduled(initialDelay = 1000,fixedRate = 1000)
    public void initDelay(){
        System.out.println("initDelay:" + new Date());
    }

    @Scheduled(cron = "0/5 * * * * *")
    public void cron(){
        System.out.println("cron:" + new Date());
    }
}

@Quartz

@Component
public class MyJob01 {
    public void sayHello(){
        System.out.println("MyJob01:" + new Date());
    }
}
@Component
public class MyJob02 extends QuartzJobBean {
    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
        System.out.println("MyJob02:" + name + ":" + new Date());
    }
}
@Configuration
public class QuartzConfig {
    @Bean
    MethodInvokingJobDetailFactoryBean jobDetail01(){
        MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
        bean.setTargetBeanName("myJob01");
        bean.setTargetMethod("sayHello");
        return bean;
    }

    @Bean
    JobDetailFactoryBean jobDetail02(){
        JobDetailFactoryBean bean = new JobDetailFactoryBean();
        bean.setJobClass(MyJob02.class);
        JobDataMap map = new JobDataMap();
        map.put("name","dzr");
        bean.setJobDataMap(map);
        return bean;
    }

    @Bean
    SimpleTriggerFactoryBean simpleTriggerFactoryBean(){
        SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
        bean.setJobDetail(jobDetail01().getObject());
        bean.setRepeatCount(3);
        bean.setStartDelay(1000);
        bean.setRepeatInterval(1000);
        return bean;
    }

    @Bean
    CronTriggerFactoryBean cronTriggerFactoryBean(){
        CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
        bean.setJobDetail(jobDetail02().getObject());
        bean.setCronExpression("0/5 * * * * ?");
        return bean;
    }

    @Bean
    SchedulerFactoryBean schedulerFactoryBean(){
        SchedulerFactoryBean bean = new SchedulerFactoryBean();
        bean.setTriggers(simpleTriggerFactoryBean().getObject(),cronTriggerFactoryBean().getObject());
        return bean;
    }
}

Swagger

第一次跑的时候报错了,查了一下Swagger3.0不兼容SpringBoot2.6.x及以上的版本,于是就把SpringBoot版本降到了2.5.6

建议还是使用Swagger2.x?

  • @ApiIgnore
    • 忽略这个接口
  • @ApiResponses
    • 响应码描述
  • @ApiOperation
    • 接口名称及描述
  • @ApiImplicitParams
    • 参数类型、描述
@RestController
public class UserController {
    @GetMapping("/hello")
    @ApiIgnore
    public String hello(){
        return "hello";
    }

    @ApiResponses({
            @ApiResponse(responseCode = "200",description = "请求成功"),
            @ApiResponse(responseCode = "500",description = "请求失败")
    })
    @GetMapping("/user/{id}")
    @ApiOperation(value = "查询用户",notes = "根据id查询用户")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "body",name = "id",value = "用户 id",required = true)
    })
    public String getUserById(@PathVariable Integer id){
        return "user:" + id;
    }
    
    @PostMapping("/user")
    @Parameter(name = "user")
    public String addUser(@RequestBody User user){
        return user.toString();
    }
}
@ApiModel(value = "用户实体类",description = "这个类定义了用户所有的属性")
public class User {
    @ApiModelProperty(value = "用户ID")
    private Long id;
    @ApiModelProperty(value = "用户名")
    private String username;
    @ApiModelProperty(value = "用户地址")
    private String address;
    //...
}

数据校验

  • @Null 被注解的元素必须为 null
  • @NotNull 被注解的元素必须不为 null
  • @AssertTrue 被注解的元素必须为 true
  • @AssertFalse 被注解的元素必须为 false
  • @Min(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值
  • @Max(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值
  • @DecimalMin(value) 被注解的元素必须是一个数字,其值必须大于等于指定的最小值
  • @DecimalMax(value) 被注解的元素必须是一个数字,其值必须小于等于指定的最大值
  • @Size(max=, min=) 被注解的元素的大小必须在指定的范围内
  • @Digits (integer, fraction) 被注解的元素必须是一个数字,其值必须在可接受的范围内
  • @Past 被注解的元素必须是一个过去的日期
  • @Future 被注解的元素必须是一个将来的日期
  • @Pattern(regex=,flag=) 被注解的元素必须符合指定的正则表达式
  • @NotBlank(message =) 验证字符串非 null,且长度必须大于0
  • @Email 被注解的元素必须是电子邮箱地址
  • @Length(min=,max=) 被注解的字符串的大小必须在指定的范围内
  • @NotEmpty 被注解的字符串的必须非空
  • @Range(min=,max=,message=) 被注解的元素必须在合适的范围内

普通校验

public class User {
    private Long id;
    @Size(min = 2,max = 5,message = "{user.username.size}")
    private String username;
    @NotNull
    private String address;
    //...
}

这里的message可以在resource目录下创建一个ValidationMessages.properties文件,在里面定义提示,例如:

user.username.size=username长度介于2-5

读取校验结果,BindingResult里如果有信息,即校验失败

@RestController
public class UserController {
    @PostMapping("/user")
    public void addUser(@Validated User user, BindingResult result){
        if (result != null && result.hasErrors()){
            List<ObjectError> allErrors = result.getAllErrors();
            for (ObjectError error : allErrors) {
                System.out.println("error.getDefaultMessage() = " + error.getDefaultMessage());
            }
        }else {
            System.out.println(user.toString());
        }

    }
}

分组校验

创建两个接口然后就可以在校验条件注解里加入group

image-20230413113029629

@Size(min = 2,max = 5,message = "{user.username.size}",groups = {ValidationGroup1.class})
private String username;
@NotNull(groups = {ValidationGroup1.class, ValidationGroup2.class})
private String address;

接着就可以在校验注解里加入分组

public void addUser(@Validated(ValidationGroup1.class) User user, BindingResult result){
	//...
}

应用监控

先鸽一下…

打包问题

可执行jar和可依赖jar同时存在

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <classifier>exec</classifier>
            </configuration>
        </plugin>
    </plugins>
</build>

暂时就先到这,如果有补充会继续更新