Spring Boot

瞻山识璞,临川知珠

官方文档

1. 概述

  1. 定义

SpringBoot是一个基于Spring4.0的javaweb快速开发框架,策略:开箱即用和约定优于配置,作者:Pivotal团队

  1. 与SSM区别
序号 SSM SpringBoot
1 Tomcat:war包 内嵌Tomcat :jar包
  1. 微服务

微服务是一种高内聚,低耦合架构风格,James Lewis,Martin Fowler

  1. SpringBoot 2.7.8要求
工具 Version
jdk Java 8
Spring Framework 5.3.25
Maven 3.5+
Gradle 6.8.x, 6.9.x, and 7.x
Tomcat 9.0 4.0
Jetty 9.4 3.1
Jetty 10.0 4.0
Undertow 2.0 4.0

2. 项目结构分析

2.1 创建方式

2.2 项目结构

1、程序的主启动类(程序的主入口)

2、一个 application.properties 配置文件(SpringBoot的核心配置文件)

3、一个 测试类

4、一个 pom.xml

  • pom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!--有一个父项目 控制版本与打包-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.xxy</groupId>
<artifactId>helloworld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>helloworld</name>
<description>first project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<!--所有的SpringBoot依赖都是spring-boot-starter开头-->
<dependencies>
<!--Web依赖:tomact.dispatcherServlet.xml-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--热部署开发工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

<build>
<!--jar包插件-->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

2.3 修改配置

  1. 修改端口
1
server.port=8081
  1. 修改运行图标
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
                              __
/\ .-" /
/ ; .' .'
: :/ .'
\ ;-.'
.--""""--..__/ `.
.' .' `o \
/ ` ;
: \ :
.-; -. `.__.-'
: ; \ , ;
'._: ; : (
\/ .__ ; \ `-.
没有bug ; "-,/_..--"`-..__)
'""--.._:
////////////////////////////////

3. 原理初探

3.1 自动配置

  • pom.xml文件

    • spring-boot-starter-parent 核心依赖在父工程中spring-boot-dependencies,锁定了版本,不需要写版本
1
2
3
4
5
6
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
  • 启动器【springBoot 启动场景】
    • spring-boot-starter,我们需要什么功能,找到对应的启动器就可以了,starter,比如spring-boot-starter-web就是自动导入web环境所有依赖
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
  • 主程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.xxy.helloworld;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//@SpringBootApplication 标注这是一个Spring Boot的应用,启动类
@SpringBootApplication
public class HelloworldApplication {

public static void main(String[] args) {
//将springBoot应用启动
//run 开启了一个服务
//SpringApplication 判断普通项目还是web项目,初始化器,监听器,找到Main的运行方法或者主类
SpringApplication.run(HelloworldApplication.class, args);
}

}
  • @SpringBootApplication下的注解

    1. springboot配置@SpringBootConfiguration

    2. spring自动配置 全面接管SpringMVC的配置@EnableAutoConfiguration

    3. springboot自动配置扫描:有判断条件,只有导入了先对应的start,自动装配才会生效@ComponentScan

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @org.springframework.boot.SpringBootConfiguration  //springboot的配置
    - @Configuration //spring 配置类
    -- @Component //说明者也是一个Spring的组件
    @org.springframework.boot.autoconfigure.EnableAutoConfiguration //自动配置ioc注入类
    - @AutoConfigurationPackage //自动配置包
    --@Import({Registrar.class}) //自动配置 ‘包注册’
    - @Import({AutoConfigurationImportSelector.class})//自动配置导入选择器
    //获取所有(候选)的配置
    -- protected List<String> getCandidateConfigurations AnnotationMetadata metadata, AnnotationAttributes attributes) {
    List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
    Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");//自动配置中的核心文件
    return configurations;
    }
  • 结论:

springboot所有自动配置都是启动时进行加载:spring.factories,里面有判断条件,条件不满足,就会无效,有了启动器,自动装配才会生效;

自动装配的原理

  • SpringBoot启动会加载大量的自动配置类
  • 寻找我们的功能是否在默认自动配置中
  • 查看自动配置类中配置了哪些组件 (没有就需要手动配置)
  • 添加组件时会从properties类中获取一部分属性
  • XX AutoConfigurartion:给容器添加组件
  • XX Properties:封装配置文件的属性,.yaml
  • 可以通过yml文件debug: true来查看自动配置的生效

3.2 SpringApplication类

  1. SpringApplication做了下面四件事
  • 推断应用的类型是普通的项目还是Web项目

  • 查找并加载所有可用初始化器 , 设置到initializers属性中

  • 找出所有的应用程序监听器,设置到listeners属性中

  • 推断并设置main方法的定义类,找到运行的主类

1
2
3
4
5
6
7
public SpringApplication(ResourceLoader resourceLoader, Class... primarySources) {
// ......
this.webApplicationType = WebApplicationType.deduceFromClasspath();
this.setInitializers(this.getSpringFactoriesInstances();
this.setListeners(this.getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = this.deduceMainApplicationClass();
}
  1. run():
    ①配置环境参数
    ②推断并设置main方法的定义类,找到运行的主类
    ③run方法里面有一些监听器,这些监听器是全局存在的,它的作用是获取上下文处理一些bean,所有的bean无论是加载还是生产初始化多存在。

3.3 自动配置加深

  1. 自动配置原理

Spring Boot通过@EnableAutoConfiguration注解开启自动配置,对jar包下的spring.factories文件进行扫描,这个文件中包含了可以进行自动配置的类,当满足@Condition注解指定的条件时,便在依赖的支持下进行实例化,注册到Spring容器中。
通俗的来讲,springboot的自动配置就是用注解来对一些常规的配置做默认配置,简化xml配置内容,使你的项目能够快速运行。

  1. @Conditional派生注解
序号 注解名称 作用
1 @Conditional 作用(判断是否满足当前指定条件)
2 @ConditionalOnJava 系统的java版本是否符合要求
3 @ConditionalOnBean 容器中存在指定Bean
4 @ConditionalOnMissingBean 容器中不存在指定Bean
5 @ConditionalOnExpression 满足SpEL表达式指定
6 @ConditionalOnClass 系统中有指定的类
7 @ConditionalOnMissingClass 系统中没有指定的类
8 @ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
9 @ConditionalOnProperty 系统中指定的属性是否有指定的值
10 @ConditionalOnResource 类路径下是否存在指定资源文件
11 @ConditionalOnWebApplication 当前是web环境
12 @ConditionalOnNotWebApplication 当前不是web环境
13 @ConditionalOnJndi JNDI存在指定项

4. 注入配置文件

结论:用yaml更好,除了SPEL表达式;如果yml和properties同时都配置了端口,默认会使用properties配置文件的!

4.1 导入文件处理器

1
2
3
4
5
6
<!--	防止@ConfigurationProperties(prefix = "person")注入报红-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>

4.2 编写配置文件

  • application.properties文件
1
2
# Spring Boot配置文件,修改SpringBoot自动配置的默认值
# 格式 key=value
  • application.yaml文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Spring Boot yaml配置
# 规范: key:空格value
# yaml一般是utf-8格式,可以注入配置文件

# 设置服务器端口号
server:
port: 8081

# person对象赋值
person:
name: lisi${random.uuid}
age: ${random.int}
isHappy: false
birth: 2000/10/22
maps: {k1: v1,k2: v2}
# hello: jj
lists:
- code
- music
- girl
dog:
name: ${person.hello:hello}_旺财
age: 3

# 数组
pets: [cat,dog,pig]
# 行内规范对象
student: {name: hh, age: dd}

4.3 进行绑定注入

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
private String name;
private int age;
private boolean isHappy;
private Date birth;
private Map<String,Object> maps;
private List<Object> lists;
private Dog dog;
//get、set、toString方法
}

1
2
3
4
5
6
7
8
9
10
11
12
@Component
//加载指定的配置文件
@PropertySource(value = "classpath:dog.properties")
public class Dog {
//通过SPEL表达式取出配置文件的值
@Value("${name}")
private String name;
@Value("5")
private int age;
//get\set\toString方法
}

4.4 松散绑定

yaml可以是-和_和驼峰式命名相互绑定,根据set方法赋值

4.5 JSR303校验

@Validated//Spring数据验证 @Valid:JDK提供的(标准JSR-303规范),不知道用哪个可以直接找源码javax.validation.constraints

1
2
3
4
5
<!--jsr303验证-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
约束注解名称 约束注解说明
@Null 用于验证对象为null
@NotNull 用于对象不能为null,无法查检长度为0的字符串
@NotBlank 只用于String类型上,不能为null且trim()之后的size>0
@NotEmpty 用于集合类、String类不能为null,且size>0。但是带有空格的字符串校验不出来
@Size 用于对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length 用于String对象的大小必须在指定的范围内
@Pattern 用于String对象是否符合正则表达式的规则
@Email 用于String对象是否符合邮箱格式
@Min 用于Number和String对象是否大等于指定的值
@Max 用于Number和String对象是否小等于指定的值
@AssertTrue 用于Boolean对象是否为true
@AssertFalse 用于Boolean对象是否为false
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后

4.6 配置文件yaml优先级(从高到低);互补配置;可以通过spring.config.location改变默认位置

– file:./config/

– file:./

–classpath:/config/

–classpath:/

4.7 配置文件服务端口配置(建议)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 设置服务器端口号
server:
port: 8081
spring:
profiles:
active: dev
---
server:
port: 8082
spring:
profiles: dev
active: dev
---
server:
port: 8083
spring:
profiles: test
---

5. SpringBoot Web开发

5.1 静态资源

  • WebMvcAutoConfiguration类源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//源码
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (!this.resourceProperties.isAddMappings()) { //判断是否自定义
logger.debug("Default resource handling disabled");
} else {
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
if (!registry.hasMappingForPattern("/webjars/**")) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}//第一个静态资源的位置

String staticPathPattern = this.mvcProperties.getStaticPathPattern();//获取静态资源的路径
if (!registry.hasMappingForPattern(staticPathPattern)) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl));
}

}
}
1
2
3
4
5
6
<!--引入jquery包-->
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>

静态资源放的位置:(优先级从高到低),自己定义路径

1
2
# 自定义静态资源位置
spring.mvc.static-path-pattern=/hello/,classpath:/xxy/
  1. 导入依赖包,classpath:/META-INF/resources/webjars/
  2. classpath:/resources/
  3. classpath:/static/(默认)
  4. classpath:/public/
  5. /**

5.2 首页

  • 源码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Bean
public WelcomePageHandlerMapping welcomePageHandlerMapping(ApplicationContext applicationContext, FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) {
WelcomePageHandlerMapping welcomePageHandlerMapping = new WelcomePageHandlerMapping(new TemplateAvailabilityProviders(applicationContext), applicationContext, this.getWelcomePage(), this.mvcProperties.getStaticPathPattern());
welcomePageHandlerMapping.setInterceptors(this.getInterceptors(mvcConversionService, mvcResourceUrlProvider));
welcomePageHandlerMapping.setCorsConfigurations(this.getCorsConfigurations());
return welcomePageHandlerMapping;
}

private Optional<Resource> getWelcomePage() {
String[] locations = WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}

private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.xxy.controller;

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

//在templates目录下的所有页面,只能通过Controller来跳转
//需要模板引擎的支持 thymeleaf依赖
@Controller
public class IndexController {
@RequestMapping("/index")
public String index() {
return "index";
}
}

5.3 模板引擎(比较少用,大多数前后端分离,用来渲染,vue使用pug)

Thymeleaf官网

1
2
3
4
5
<!--导入Thymeleaf模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

5.4 装配扩展SpringMVC

  • 自定义视图解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.xxy.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Locale;

//扩展Mvc
@Configuration
//一旦标注@EnableWebMvc将全面接管Mvc,从容器中获取所有的webmvcconfig;
/*@EnableWebMvc*/
//如果想自定义,只要写这个组件,然后交给Springboot自动配置
public class MyMvcConfig implements WebMvcConfigurer {
// public interface ViewResolver() 实现了视图解析器接口的类,我们就可以把它看作视图解析器
@Bean
public ViewResolver MyViewResolver() {
return new MyViewResolver();
}
//静态内部类自定义了一个视图解析器
public static class MyViewResolver implements ViewResolver {

@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}

  • 视图跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.xxy.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

//扩展SpringMVC,官方建议
@Configuration
public class MymvcConfig1 implements WebMvcConfigurer {
//视图跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/xxy").setViewName("index");
}
}

5.5 增删改查

  1. controlller Restful风格
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@Controller
public class EmployeeController {
@Autowired
private EmployeeDao employeeDao;
@Autowired
private DepartmentDao departmentDao;

@GetMapping("/selectEmployeeByAll")
public String selectEmployeeByAll(Model model) {
Collection<Employee> employeeList = employeeDao.selectEmployeeByAll();
model.addAttribute("employeeList", employeeList);
return "table1";
}

@GetMapping("/toInsertEmployee")
public String toInsertEmployee(Model model) {
Collection<Department> departments = departmentDao.departmentCollection();
model.addAttribute("departments", departments);
return "insertemployee";
}

@PostMapping("/toInsertEmployee")
public String toInsertEmployee(Employee employee) {
System.out.println(employee);
employeeDao.insertEmployee(employee);
return "redirect:/selectEmployeeByAll";
}

@GetMapping("/update/{employeeId}")
public String update(@PathVariable("employeeId") Integer employeeId, Model model) {
Employee employee = employeeDao.selectEmployeeById(employeeId);
model.addAttribute("employee", employee);
Collection<Department> departments = departmentDao.departmentCollection();
model.addAttribute("departments", departments);
return "updateemployee";
}

@PutMapping("/update")
public String update(Employee employee) {
employeeDao.insertEmployee(employee);
return "redirect:/selectEmployeeByAll";
}
@GetMapping("/delete/{employeeId}")
public String delete(@PathVariable("employeeId") Integer employeeId) {
System.out.println(employeeId);
employeeDao.deleteEmployeeById(employeeId);
return "redirect:/selectEmployeeByAll";
}
}
  1. 错误信息

在templates建立error包,然后404,500文件

5.6 国际化

  1. 在资源文件夹建立i18u文件夹
  2. 建立login.properties和login_zh_CN.properties
  3. 他们会自动合并为一个文件夹中,新建login_en_US.properties文件
  4. login_en_US.properties写入内容进行翻译
  5. 自定义组件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.xxy.config;

import org.apache.tomcat.util.descriptor.LocalResolver;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.LocaleResolver;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Locale;

/**
* @author xxy
* @version 1.0
* @date 2023-02-06-3:36
*/
public class LocalI18n implements LocaleResolver {
@Override
public Locale resolveLocale(HttpServletRequest httpServletRequest) {
String l = httpServletRequest.getParameter("l");
System.out.println(l);
Locale locale = Locale.getDefault();
if (!StringUtils.isEmpty(l)) {
String[] split = l.split("_");
locale = new Locale(split[0], split[1]);

}
return locale;
}

@Override
public void setLocale(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Locale locale) {

}
}
  1. 注册bean到Spring容器
1
2
3
4
@Bean
public LocaleResolver localI18n() {
return new LocalI18n();
}
  1. 前端渲染显示

6. Data

6.1 SpringData

对于数据访问层,无论是 SQL(关系型数据库) 还是 NOSQL(非关系型数据库),Spring Boot 底层都是采用 Spring Data 的方式进行统一处理

6.2 整合JDBC

  1. 导入依赖
1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
  1. 进行配置
1
2
3
4
5
6
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/temp1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: 'root'
  1. 使用原始jdbc进行开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@SpringBootTest
class Springdemo1ApplicationTests {
@Autowired
private DataSource dataSource;
@Test
void contextLoads() {
}
@Test
public void testJdbc() throws SQLException {
System.out.println(dataSource.getClass());
String sql = "";
//1. 加载驱动
//2. 获取连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//3. 创建操作对象Statement,PreparedStatement,CallableStatement
// PreparedStatement ps = connection.prepareStatement(sql);
//4. 执行SQl
// int i = ps.executeUpdate();
//5. 处理结果集
// System.out.println(i);
//6. 释放资源,结果集,操作对象,连接
// ps.close();
connection.close();
}
}
  • 使用SpringBoot封装的jdbcTemplate
1
2
3
4
5
6
7
8
9
10
11
12
13
@Controller
public class JdbcController {
@Autowired
private JdbcTemplate jdbcTemplate;
//查询数据库所有信息
@GetMapping("/userList")
@ResponseBody
public List<Map<String,Object>> userList() {
String sql = "select * from user";
List<Map<String,Object>> list_map = jdbcTemplate.queryForList(sql);
return list_map;
}
}

6.3 整合JDBC数据连接池

  1. 概述

HikariDataSource 号称 Java WEB 当前速度最快的数据源,相比于传统的 C3P0 、DBCP、Tomcat jdbc 等连接池更加优秀;Druid 是阿里巴巴开源平台上一个数据库连接池实现,结合了 C3P0、DBCP 等 DB 池的优点,同时加入了日志监控。可以说 Hikari 与 Driud 都是当前 Java Web 上最优秀的数据源

  1. 导入依赖
1
2
3
4
5
6
7
8
9
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
  1. 配置
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/temp1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: 'root'
druid:
db-type: com.alibaba.druid-spring-boot-starter
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
#initialSize: 5
#minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j2:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
#filters: stat,wall,log4j2
#配置StatFilter (SQL监控配置)
filters: stat,wall,log4j2
filter:
stat:
enabled: true #开启 SQL 监控
#slow-sql-millis: 1000 #慢查询
#log-slow-sql: true #记录慢查询 SQL
#配置WallFilter (防火墙配置)
wall:
enabled: true #开启防火墙
config:
update-allow: true #允许更新操作
drop-table-allow: false #禁止删表操作
insert-allow: true #允许插入操作
delete-allow: true #删除数据操作
log4j2:
enabled: true
statement-execute-query-after-log-enabled: true
statement-executable-sql-log-enable: true
statement-close-after-log-enabled: true
result-set-open-after-log-enabled: true
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
  1. 进行扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Configuration
public class DruidConfig {
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}

//后台监控
@Bean
public ServletRegistrationBean servletRegistrationBean() {
//后台有人登录
ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
bean.addInitParameter("loginUsername","admin");
bean.addInitParameter("loginPassword","123");
bean.addInitParameter("allow","");
// bean.addInitParameter("xxy","192.168.1.101");
return bean;
}
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.addInitParameter("exclusions","*.js,*.css,/druid/*");
return bean;
}
}

6.4 整合Mybatis

  1. 概述
MyBatis-Spring-Boot-Starter MyBatis-Spring Spring Boot Java
3.0 3.0 3.0 17 或更高
2.3 2.1 2.5 - 2.7 8 或更高
2.2 2.0(2.0.6 以上可开启所有特性) 2.5 - 2.7 8 或更高
2.1 2.0(2.0.6 以上可开启所有特性) 2.1 - 2.4 8 或更高
  1. 导入依赖
1
2
3
4
5
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.1</version>
</dependency>
  1. 进行配置
1
2
3
4
5
6
7
8
9
10
11
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/temp1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: 'root'
druid:
db-type: com.alibaba.druid-spring-boot-starter
mybatis:
type-aliases-package: com.xxy.entity
mapper-locations: [classpath:mapper/*.xml]
  1. 进行开发模块
  • User
1
2
3
4
5
6
7
8
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
private String id;
private String name;
private String email;
}
  • UserMapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.xxy.mapper;

import com.xxy.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
* @author xxy
* @version 1.0
* @date 2023-02-11-3:53
*/
@Mapper
@Repository
public interface UserMapper {
/**
* 查询所有用户
* @return List<User>
*/
List<User> selectUserByAll();

/**
* 通过id查询用户
* @param id id
* @return User
*/
User selectUserById(String id);

/**
* 插入用户
* @param user 用户
* @return int
*/
int insertUser(User user);

/**
* 删除用户
* @param id 用户id
* @return int
*/
int deleteUser(String id);

/**
* 更新用户
* @param user 用户
* @return int
*/
int updateUser(User user);
}
  • UserMapper.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xxy.mapper.UserMapper">

<!-- <resultMap id="userResultMap" type="com.xxy.entity.User">-->
<!-- <id property="id" column="id"></id>-->
<!-- <result property="name" column="name"></result>-->
<!-- <result property="email" column="email"></result>-->
<!-- </resultMap>-->

<select id="selectUserByAll" resultType="User">
select id,name,email from user
</select>
<select id="selectUserById" resultType="User" parameterType="String">
select id,name,email from user where id = #{id}
</select>
<insert id="insertUser" parameterType="User">
insert into user(id,name,email) values(#{id},#{name},#{email})
</insert>
<delete id="deleteUser" parameterType="String">
delete from user where id = #{id}
</delete>
<update id="updateUser" parameterType="User">
update user set name=#{name},email = #{email} where id = #{id}
</update>

</mapper>
  • UserService
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package com.xxy.service;

import com.xxy.entity.User;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @author xxy
* @version 1.0
* @date 2023-02-11-4:11
*/

public interface UserService {
/**
* 查询所有用户
* @return List<User>
*/
List<User> selectUserByAll();

/**
* 通过id查询用户
* @param id id
* @return User
*/
User selectUserById(String id);

/**
* 插入用户
* @param user 用户
* @return int
*/
int insertUser(User user);

/**
* 删除用户
* @param id 用户id
* @return int
*/
int deleteUser(String id);

/**
* 更新用户
* @param user 用户
* @return int
*/
int updateUser(User user);
}
  • UserServiceImpl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.xxy.service;

import com.xxy.entity.User;
import com.xxy.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
* @author xxy
* @version 1.0
* @date 2023-02-11-4:11
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<User> selectUserByAll() {
return userMapper.selectUserByAll();
}

@Override
public User selectUserById(String id) {
return userMapper.selectUserById(id);
}

@Override
public int insertUser(User user) {
return userMapper.insertUser(user);
}

@Override
public int deleteUser(String id) {
return userMapper.deleteUser(id);
}

@Override
public int updateUser(User user) {
return userMapper.updateUser(user);
}
}
  • UserController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
package com.xxy.controller;

import com.xxy.entity.User;
import com.xxy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
* @author xxy
* @version 1.0
* @date 2023-02-11-4:30
*/
@Controller
public class UserController {
@Autowired
private UserService userService;

@GetMapping("/select")
@ResponseBody
public List<User> selectUserByAll() {
List<User> userList = userService.selectUserByAll();
for (User user : userList) {
System.out.println(user.toString());
}
return userList;
}

@GetMapping("/select/{id}")
@ResponseBody
public User selectUserById(@PathVariable("id") String id) {
User user = userService.selectUserById(id);
System.out.println(user);
return user;
}

@GetMapping("/insert/{id}")
@ResponseBody
public String insertUser(@PathVariable("id") String id,User user) {
user.setId(id);
user.setName("xxy");
user.setEmail("test!@qq.com");
int i = userService.insertUser(user);
if (i > 0) {
System.out.println("insertOk"+ i);
return "insertOk" + i;
} else {
System.out.println("insert失败" + i);
return "insert失败" + i;
}
}

@GetMapping("deleteUser/{id}")
@ResponseBody
public String deleteUser(@PathVariable("id") String id) {
int i = userService.deleteUser(id);
if (i > 0) {
return "d成功" + i;
} else {
return "d失败" + i;
}
}

@GetMapping("/update/{id}/{name}")
@ResponseBody
public String updateUser(@PathVariable("id") String id,@PathVariable("name") String name,User user) {
user.setId(id);
user.setName(name);
user.setEmail("jj@qq.com");
int i = userService.updateUser(user);
if (i > 0) {
return "updatesuccess" + i;
} else {
return "updatefault" + i;
}
}

}

7. SpringSecurity

7.1 简述

一个安全的框架,其实通过过滤器和拦截器也可以实现,主要是用户认证和授权

  • 用户认证

在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等。

  • 用户授权

在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

7.2 使用

  1. 导依赖
1
2
3
4
5
<!--SpringSecurity -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. 配置和扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
//@EnableWebSecurity:开启WebSecurity模式
@EnableWebSecurity
//WebSecurityConfigurerAdapter:自定义Security策略
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/level1/**").hasAnyRole("vip1")
.antMatchers("/level2/**").hasAnyRole("vip2")
.antMatchers("/level3/**").hasAnyRole("vip3");

//记住我
http.rememberMe();
// 开启自动配置的登录功能
// /login 请求来到登录页
// /login?error 重定向到这里表示登录失败
http.formLogin().usernameParameter("name").passwordParameter("pwd").loginPage("/toLogin1");

//关闭csrf功能:跨站请求伪造,默认只能通过post方式提交logout请求
http.csrf().disable();
//开启自动配置的注销的功能
// /logout 注销请求
// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");
}

//定义认证规则
@Override
//AuthenticationManagerBuilder:自定义认证策略
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//在内存中定义,也可以在jdbc中去拿....
//Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
//要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
//spring security 官方推荐的是使用bcrypt加密方式。

auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
.and()
.withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
.and()
.withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
}


}
  1. 开发
1
2
3
4
5
6
7
8
9
10
11
@Controller
public class SecurityController {
@GetMapping("/views/index")
public String index() {
return "views/index";
}
@RequestMapping("/{leavel}/{id}")
public String leavels(@PathVariable("leavel") String leavel,@PathVariable("id") int id) {
return "views" + leavel + id;
}
}

8.Shiro

8.1 简介

  1. 定义

Apache Shiro是一个强大且易用的Java安全框架,可以完成身份验证、授权、密码和会话管理

  1. 功能
  • Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储
  • Web Support:Web支持,可以非常容易的集成到Web环境;
  • Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
  • Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去;
  • Testing:提供测试支持;
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
  • Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
  1. 外部架构
  • Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
  • Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

8.2 认证

  1. 导入依赖
1
2
3
4
5
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-web-starter</artifactId>
<version>1.11.0</version>
</dependency>
  1. 扩展或配置
  • UserRealm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class UserRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权doGetAuthorizationInfo");
return null;
}
//验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证doGetAuthenticationInfo");
String userName = "root";
String password = "password";
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
if (!userToken.getUsername().equals(userName)) {
return null;
}
return new SimpleAuthenticationInfo("",password,"");
}
}

  • ShiroConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@Configuration
public class ShiroConfig {
//1.创建reaml对象,需要自定义类
@Bean(name = "userRealm")
public UserRealm userRealm() {
return new UserRealm();
}
//2.DefaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
//3.ShiroFilterFactoryBean
@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean getFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/**
anon:无需认证就可以访问
authc:必须认证才可访问
user:必须拥有 记住我才能用
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
**/
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/shiro/user/insert","anon");
filterMap.put("/shiro/user/update","authc");
bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/shiro/login");
return bean;
}
}

  1. 开发
  • ShiroController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Controller
public class ShiroController {
@RequestMapping({"/shiro","/shiro/index"})
public String toIndex(Model model) {
model.addAttribute("msg","shiro");
return "shiro/index";
}
@RequestMapping("/shiro/login")
public String toLogin() {
return "shiro/login";
}
@RequestMapping("/shiro/user/insert")
public String add() {
return "shiro/user/insert";
}
@RequestMapping("/shiro/user/update")
public String update() {
return "shiro/user/update";
}
@RequestMapping("/shiro/logining")
public String login(String username,String password,Model model) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
return "shiro/user/update";
} catch (UnknownAccountException e) {
model.addAttribute("msg","用户名错误");
return "shiro/login";
} catch (IncorrectCredentialsException e) {
model.addAttribute("msg","密码错误");
return "shiro/login";
}
}

}

  1. 前端
  • templeats文件夹新建shiro文件夹,在shiro文件夹新建user文件夹
  • shiro文件夹主页index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
首页
<p th:text="${msg}"></p>
<a th:href="@{/shiro/user/insert}">增加</a>|
<a th:href="@{/shiro/user/update}">修改</a>
</body>
</html>
  • shiro文件夹login.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!doctype html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="">
<meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors">
<meta name="generator" content="Hugo 0.80.0">
<title>Signin Template · Bootstrap v4.6</title>

<link rel="canonical" href="https://getbootstrap.com/docs/4.6/examples/sign-in/">
<!-- Custom styles for this template -->
<link href="../static/css/signin.css" rel="stylesheet">
</head>
<body class="text-center">
shiro 登录
<form class="form-signin" th:action="@{/shiro/logining}">
<input type="hidden" th:text="${msg}">
<h1 class="h3 mb-3 font-weight-normal" th:text="#{login.tip}">Please sign in</h1>
<label for="inputEmail" class="sr-only">Email address</label>
<input type="text" name ="username" id="inputEmail" class="form-control" placeholder="Email address" required autofocus>
<label for="inputPassword" class="sr-only">Password</label>
<input type="password" name="password" id="inputPassword" class="form-control" placeholder="Password" required>
<div class="checkbox mb-3">
<label>
<input type="checkbox" value="remember-me"> Remember me
</label>
</div>
<button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
<p class="mt-5 mb-3 text-muted">&copy; 2017-2021</p>
<a th:href="@{/xxy?l='zh_CN'}">中文</a>
<a th:href="@{/xxy?l='en_US'}">English</a>
</form>
</body>
</html>
  • user文件夹更新和增加insert.html、update.html
1
2
3
4
5
6
7
8
9
10
<!doctype html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
</head>
<body class="text-center">
更新/增加
</body>
</html>

8.3 整合Mybatis

  • 修改UserRealm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class UserRealm extends AuthorizingRealm {
@Resource
private UserService userService;

//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("授权doGetAuthorizationInfo");
return null;
}

//验证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证doGetAuthenticationInfo");
// String userName = "root";
// String password = "password";
UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
User user = userService.selectUserById(userToken.getUsername());
if (user == null) {
return null;
}
// if (!userToken.getUsername().equals(userName)) {
// return null;
// }
return new SimpleAuthenticationInfo("", user.getName(), "");
}
}

8.4 授权

  1. 前端
  • update.html
1
2
3
4
5
6
7
8
9
<!doctype html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
</head>
<body class="text-center">
更新|<a th:href="@{/shiro/user/insert}">增加</a>|
</body>
</html>
  1. ShiroConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Configuration
public class ShiroConfig {
//1.创建reaml对象,需要自定义类
@Bean(name = "userRealm")
public UserRealm userRealm() {
return new UserRealm();
}
//2.DefaultWebSecurityManager
@Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(userRealm);
return securityManager;
}
//3.ShiroFilterFactoryBean
@Bean(name = "shiroFilterFactoryBean")
public ShiroFilterFactoryBean getFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置过滤器
/**
anon:无需认证就可以访问
authc:必须认证才可访问
user:必须拥有 记住我才能用
perms:拥有对某个资源的权限才能访问
role:拥有某个角色权限才能访问
**/
Map<String, String> filterMap = new LinkedHashMap<>();
// filterMap.put("/shiro/user/insert","anon");
filterMap.put("/shiro/user/insert","perms[user:add]");
filterMap.put("/shiro/user/update","authc");
bean.setFilterChainDefinitionMap(filterMap);
bean.setLoginUrl("/shiro/login");
bean.setUnauthorizedUrl("/unAuthorized");
return bean;
}
}
  • controller
1
2
3
4
5
@RequestMapping("/unAuthorized")
@ResponseBody
public String unAuthorized() {
return "没有权限";
}

9. Swagger

9.1 简述

  1. 定义

Swagger 是一个规范且完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。

  1. 优点
  • 支持多种语言
  • 直接运行,在线测试API接口(其实就是controller requsetmapping)
  1. 注解
序号 注解名称 功能 备注
1 @Api() 用于类,标识这个类是swagger的资源
2 @ApiOperation() 用于方法,描述 Controller类中的 method接口
3 @ApiParam() 用于参数,单个参数描述,与 @ApiImplicitParam不同的是,他是写在参数左侧的
4 @ApiModel() 用于类,表示对类进行说明,用于参数用实体类接收
5 @ApiModelProperty() 用于方法,字段,表示对model属性的说明或者数据操作更改
6 @ApiIgnore() 用于类,忽略该 Controller,指不对当前类做扫描
7 @ApiImplicitParam() 用于方法,表示单独的请求参数
8 @ApiImplicitParams() 用于方法,包含多个 @ApiImplicitParam
9 @ApiResponse 用于方法,描述单个出参信息
10 @ApiResponses 用于方法,包含多个@ApiResponse
11 @ApiError 用于方法,接口错误所返回的信息

9.2 SpringBoot使用

  1. 导入依赖
1
2
3
4
5
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

  1. 配置或者扩展
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.xxy.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;

import static springfox.documentation.service.ApiInfo.DEFAULT_CONTACT;

/**
* @author xxy
* @version 1.0
* @date 2023-03-02-22:51
*/
@Configuration
@EnableSwagger2
public class Swagger2Config {
@Bean
public Docket createRestApi(Environment environment) {
Profiles profiles = Profiles.of("dev","test");
boolean flag = environment.acceptsProfiles(profiles);
return new Docket(DocumentationType.SWAGGER_2)
.groupName("标准接口")
.apiInfo(apiInfo())
.enable(flag)
.select()
// any() // 扫描所有,项目中的所有接口都会被扫描到
// none() // 不扫描接口
//// 通过方法上的注解扫描,如withMethodAnnotation(GetMapping.class)只扫描get请求
// withMethodAnnotation(final Class<? extends Annotation> annotation)
//// 通过类上的注解扫描,如.withClassAnnotation(Controller.class)只扫描有controller注解的类中的接口
// withClassAnnotation(final Class<? extends Annotation> annotation)
// basePackage(final String basePackage) // 根据包路径扫描接口
// paths(PathSelectors.ant("/guo/**")) //过滤什么路径:过滤/guo下的所有路径
.apis(RequestHandlerSelectors.basePackage("com.xxy.controller"))
// any() // 任何请求都扫描
// none() // 任何请求都不扫描
// regex(final String pathRegex) // 通过正则表达式控制
// ant(final String antPattern) // 通过ant()控制
.paths(PathSelectors.ant("/xxy/**"))
.build();
}
@Bean
public Docket docket1() {
return new Docket(DocumentationType.SWAGGER_2).groupName("1");
}
private ApiInfo apiInfo() {
Contact contact = new Contact("xxy1", "https://www.kuangstudy.com", "1@qq.com");
return new ApiInfo(
"xxyApi Documentation",
"xxyApi Documentation",
"v1.0",
"urn:tos",
contact,
"Apache 2.0",
"http://www.apache.org/licenses/LICENSE-2.0",
new ArrayList());

}
}

  • 3.0.0版本需要增加两个注解,不需要@EnableSwagger2
1
2
@EnableOpenApi // 可选
@EnableWebMvc // spring-boot-starter-web冲突会引发启动服务时null,必选
  1. 开发
  • User.java
1
2
3
4
5
6
7
8
9
10
11
12
@Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel(value="user对象",description="用户对象user")
public class User implements Serializable {
@ApiModelProperty(value="用户id",name="id",example="100")
private String id;
@ApiModelProperty(value="用户名",name="name",example="xingguo")
private String name;
@ApiModelProperty(value="用户邮箱",name="email",example="xingguo@163.com")
private String email;
}
  • UserController
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Api(value = "用户controller", tags = {"用户操作接口"})
@Controller
public class UserController {
@Autowired
private UserService userService;

@ApiOperation(value = "获取用户信息", tags = {"获取用户信息copy"}, notes = "注意问题点")
@GetMapping("/select")
@ResponseBody
public List<User> selectUserByAll() {
List<User> userList = userService.selectUserByAll();
for (User user : userList) {
System.out.println(user.toString());
}
return userList;
}

@GetMapping("/select/{id}")
@ResponseBody
public User selectUserById(@ApiParam(name="id",value="用户id",required=true) @PathVariable("id") String id) {
User user = userService.selectUserById(id);
System.out.println(user);
return user;
}
...

注意

正式环境或者开发测试结束后应该关闭Swagger,.enable(flag)

10. 任务

10.1 异步任务

  1. 定义

某些功能实现时可能要花费一定的时间,但是为了不影响客户端的体验,选择异步执行

  1. 开发

先启用@EnableAsync,在想要异步的方法加 @Async注解

1
2
3
4
5
6
7
8
@EnableAsync
@SpringBootApplication
public class SwaggerDemoApplication {

public static void main(String[] args) {
SpringApplication.run(SwaggerDemoApplication.class, args);
}
}
1
2
3
4
5
6
7
8
9
@Async
public void hello(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("数据正在传输...");
}

10.2 邮件任务

  1. 导包
1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
  1. 配置
1
2
3
4
5
spring.mail.username=你的邮箱
spring.mail.password=你的邮箱授权码
spring.mail.host=smtp.163.com
# qq需要配置ssl,其他邮箱不需要
spring.mail.properties.mail.smtp.ssl.enable=true
  1. 开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@SpringBootTest
class SwaggerDemoApplicationTests {

@Autowired
JavaMailSenderImpl mailSender;

@Test
public void contextLoads() {
//邮件设置1:一个简单的邮件
SimpleMailMessage message = new SimpleMailMessage();
message.setSubject("这是一个测试邮件发送标题");
message.setText("这是一个测试邮件发送内容");
message.setTo("xxy@163.com");
message.setFrom("xxy@163.com");
mailSender.send(message);
}
@Test
public void contextLoads2() throws MessagingException {
//邮件设置2:一个复杂的邮件
MimeMessage mimeMessage = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("通知-明天听课");
helper.setText("<b style='color:red'>今天 7:30来开会</b>",true);
//发送附件
helper.addAttachment("1.jpg",new File("绝对路径地址"));
helper.addAttachment("2.jpg",new File(""));
helper.setTo("xxy@163.com");
helper.setFrom("xxy@163.com");
mailSender.send(mimeMessage);
}
}

10.3 定时任务

  1. 简述接口和注解

TaskExecutor接口和TaskScheduler接口 注解:@EnableScheduling和@Scheduled

  1. cron
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    //秒   分   时     日   月   周几
(1)0/2 * * * * ? 表示每2秒 执行任务
(1)0 0/2 * * * ? 表示每2分钟 执行任务
(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED 表示每个星期三中午12点
(7)0 0 12 * * ? 每天中午12点触发
(8)0 15 10 ? * * 每天上午10:15触发
(9)0 15 10 * * ? 每天上午10:15触发
(10)0 15 10 * * ? 每天上午10:15触发
(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发
(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
(18)0 15 10 15 * ? 每月15日上午10:15触发
(19)0 15 10 L * ? 每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
  1. 特殊字符
序号 符号 意思 备注
1 * 每个
2 不确定值
3 - 指定范围
4 L 只用在DayofMonth和DayofWeek中,这个字符是“Last”的简写,重要的是不要指定列表或者值范围,否则会导致混乱。
5 W 指定给定日(星期一到星期五)最近的一天
6 # 表示本月中的第几个周几
7 表达一个列表值
8 / 如 x/y,x 是开始值,y 是步长
9 C 和Calendar计算过的值
  1. 开发
  • 在主程序加上@EnableScheduling注解
1
2
3
4
5
6
7
8
@Service
public class ScheduledService {
//秒 分 时 日 月 周几
@Scheduled(cron = "0 * * * * 0-7")
public void hello(){
System.out.println("hello.....");
}
}

11 SpringBoot和Redis

11.1 概述

  • SpringBoot操作数据: Spring-data jpa jdbc mongdb redis
  • springboot 2.x后 ,原来使用的 Jedis 被 lettuce 替换。
1
2
3
4
5
//LettuceConnectionFactory
public class LettuceConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory, ReactiveRedisConnectionFactory {
}
//JedisConnectionFactory
public class JedisConnectionFactory implements InitializingBean, DisposableBean, RedisConnectionFactory {

jedis:采用的直连,多个线程操作的话,是不安全的。如果要避免不安全,使用jedis pool连接池!更像BIO模式

lettuce:采用netty,实例可以在多个线程中共享,不存在线程不安全的情况!可以减少线程数据了,更像NIO模式

11.2 Redis在SpringBoot中的自动配置

  • 找到org.springframework.boot.autoconfigure包下的spring-boot-autoconfigure-2.5.0.jar!\META-INF\spring.factories,搜索redis
  • 存在一个xxxAutoConfiguration和RedisProperties
  • 若要修改配置查看RedisProperties
1
2
3
4
@ConfigurationProperties(
prefix = "spring.redis"
)
spring.redis.port=3967

11.3 测试

  • 导入依赖
1
2
3
4
5
<!--操作redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 配置
1
2
spring.redis.host=127.0.0.1
spring.redis.password=root
  • 自定义序列化模板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);

Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);

objectJackson2JsonRedisSerializer.setObjectMapper(objectMapper);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
template.setHashKeySerializer(stringRedisSerializer);
template.setHashValueSerializer(stringRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
  • 编写代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  @Autowired
private RedisTemplate redisTemplate;
@Test
void testJedis() {
Jedis jedis = new Jedis("127.0.0.1", 6379);
jedis.auth("root");
JSONObject jsonObject = new JSONObject();
jsonObject.put("hello", "xxy");
jsonObject.put("name", "k");
String s = jsonObject.toJSONString();
Transaction multi = jedis.multi();
try {
multi.set("u1", s);
multi.set("u2", s);
multi.exec();
} catch (Exception e) {
multi.discard();
e.printStackTrace();
} finally {
System.out.println(jedis.get("u1"));
System.out.println(jedis.get("u2"));
jedis.close();
}
}
@Test
void testSpringBootRedis() {
// RedisConnection connection = redisTemplate.getConnectionFactory().getConnection();
dog.setDogName("皇子");
dog.setAge(3);
JSONObject jsonObject = new JSONObject();
try {
String s = jsonObject.toJSONString(dog);
redisTemplate.opsForValue().set("js2",s);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(redisTemplate.opsForValue().get("js2"));
}

12. 分布式Dubbo和Zookeeper集成

12.1 分布式简述

  1. 定义

分布式系统是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统, 分布式系统(distributed system)是建立在网络之上的软件系统。其目的是利用更多的机器,处理更多的数据。

  1. 为什么使用

首先需要明确的是,只有当单个节点的处理能力无法满足日益增长的计算、存储任务的时候,且硬件的提升(加内存、加磁盘、使用更好的CPU)高昂到得不偿失的时候,应用程序也不能进一步优化的时候,我们才需要考虑分布式系统。

12.2 Dubbo

  1. 背景
  • 单一应用架构

性能扩展比较难、协同开发问题、不利于升级维护

  • 垂直应用架构(MVC)

公用模块无法重复利用,开发性的浪费

  • 分布式服务架构

提高业务复用及整合的**分布式服务框架(RPC)**是关键。

  • 流动计算架构

提高机器利用率的资源调度和治理中心(SOA)[ Service Oriented Architecture]是关键。

  1. 定义

Dubbo是一个分布式服务框架,致力于提供高性能和透明化的RPC远程服务调用方案,以及SOA服务治理方案

  1. 核心
  • 远程通讯: 提供对多种基于长连接的NIO框架抽象封装,包括多种线程模型,序列化,以及“请求-响应”模式的信息交换方式。
  • 集群容错: 提供基于接口方法的透明远程过程调用,包括多协议支持,以及软负载均衡,失败容错,地址路由,动态配置等集群支持。
  • 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。
  1. 调用关系
  • 服务提供者(Provider):暴露服务的服务提供方,服务提供者在启动时,向注册中心注册自己提供的服务。

  • 服务消费者(Consumer 发布订阅设计模式):调用远程服务的服务消费方,服务消费者在启动时,向注册中心订阅自己所需的服务,服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。

  • 注册中心(Registry):注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者

  • 监控中心(Monitor):服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心

  1. RPC

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。

  1. RPC核心

12.3 Zookeeper

  1. 定义

是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等。文件系统+监听通知机制。

12.4 开发

需要打开Zookeeper,如果需要监控dubbo-admin-0.0.1-SNAPSHOT.jar

  1. 导包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除这个slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>

  1. 配置或扩展
1
2
3
4
5
6
#当前应用名字
dubbo.application.name=provider-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
#扫描指定包下服务
dubbo.scan.base-packages=com.guo.provider.service
  1. 开发
1
2
3
4
5
6
7
8
9
10
11
12
import org.apache.dubbo.config.annotation.Service;
import org.springframework.stereotype.Component;

@Service //将服务发布出去,不是spring的注解,可以使用新注解@DubboService
@Component //放在容器中
public class TicketServiceImpl implements TicketService {
@Override
public String getTicket() {
return "111";
}
}

  1. 另外服务消费者导包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<!--dubbo-->
<!-- Dubbo Spring Boot Starter -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.3</version>
</dependency>
<!--zookeeper-->
<!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
<!-- 引入zookeeper -->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>2.12.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
<!--排除这个slf4j-log4j12-->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>

  1. 消费者配置
1
2
3
4
#当前应用名字
dubbo.application.name=consumer-server
#注册中心地址
dubbo.registry.address=zookeeper://127.0.0.1:2181
  1. 消费者开发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.guo.consumer.service;

import com.guo.provider.service.TicketService;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;

@Service //注入到容器中
public class UserService {

@Reference //远程引用指定的服务,他会按照全类名进行匹配,看谁给注册中心注册了这个全类名
TicketService ticketService;

public void buyTicket(){
String ticket = ticketService.getTicket();
System.out.println("在注册中心买到"+ticket);
}

}
  1. 测试
1
2
3
4
5
6
7
8
9
10
@RunWith(SpringRunner.class)
@SpringBootTest
public class ConsumerServerApplicationTests {
@Autowired
UserService userService;
@Test
public void contextLoads() {
userService.bugTicket();
}
}

13. 总结

本次系统学习了SpringBoot,下面开始学习SpringCloud,将往API网关【服务路由】、Http、RPC框架【异步调用】、服务注册与发现【高可用】、熔断机制【服务降级】方向发展