Published on

java经典框架-springboot

Authors
  • avatar
    Name
    MissTree
    Twitter

安装与配置

官网下载安装(推荐)

spring 官网点击项目 Spring Boot页面下方 Quickstart Your Project 点击 Spring Initializr进入自定义项目页面,选择最新版的安装包,加载web依赖包最后点击下载 GENERATE CTRL+,解压导入IDEA后即可使用。 自定义项目页面

IDEA安装项目

IDEA下载版本选择 付费版本idea 流程和官网上基本一致

  1. 点击File -> New -> Project -> Maven Archetype (2023以后社区新版本不再支持 Spring Initializr 方式安装) 自定义项目页面 在生成的项目中,pom.xml 中添加以下依赖:
<!-- 下面的版本号按实际情况而定 -->
<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 http://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>3.x.x</version> 
 </parent>

 <groupId>org.example</groupId>
...

 <dependencies>
  <dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <version>3.x.x</version>
  </dependency>
  ....
 </dependencies>
</project>

maven 依赖搜索地址:Maven Repository

修改完后在 pom.xml 编辑区域右上角点击重新加载即可。

  1. 更新 src/main/java/<项目名称>/ 目录下的 <项目名称>Application.java 文件,添加 @SpringBootApplication 注解:
package com.misstree.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloDemoApplication {

	public static void main(String[] args) {
  // 默认两个参数,第一个是启动类,第二个是启动参数
		SpringApplication.run(HelloDemoApplication.class, args);
	}
}

当前文件为启动类,SpringBootApplication 本质是一个springboot的组件

  1. src/main/ 创建 resource 文件夹创建一个名为application.properties的配置文件

简单测试接口

src/main/java/<项目名称>/创建目录 controller 后新建一个简单的控制器类,用于处理 HTTP 请求:

package com.misstree.demo;

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

@RestController
public class helloworld {
  // 定义一个名为hello的公共类
  @RequestMapping("/hello")
  public String hello() {
    return "Hello World! 这是一个接口";
  }
}

测试接口

配置文件

Spring Boot 最新配置(Spring Boot 2.x配置) 支持多种配置文件格式,包括:

  • application.properties:使用键值对的方式配置应用程序的属性。
  • application.yml:使用 YAML 格式的配置文件。 spring 的配置大多遵循 约定大于配置 的原则,配置存放位置有四种,默认我们只需要在 src/main/resource 目录下创建一个名为application.properties的配置文件即可。
<!-- 配置文件存放位置优先级 -->
├── config:存放应用的配置文件
|   ├── application.yml:配置文件优先级最高
├── src:项目文件
|   ├── main:
|   |   ├── java:存放java源代码
|   |   └── resource::配置文件
|   |       ├── config:存放应用的配置文件
|   |       |   └──  application.yml:配置文件优先级最低
|   |       └── application.yml:配置文件优先级第三高(默认约定存放位置)
|   └── test  
└──  application.yml:配置文件优先级第二高

不仅支持在文件通过${random.uuid}方式变量设置,还可以设置配置的变量实现不同环境的切换,例如:application-dev.propertiesapplication-prod.properties,在启动时通过 --spring.profiles.active=dev 指定使用哪个配置文件。

# application.properties  设置环境base
spring.profiles.active=dev

# application-dev.properties
server.port=8081
# application-prod.properties
server.port=8082

# 若是用.yml文件则可以统一写到一个文件内
spring:
  profiles:
    active: dev
    group:application-dev,
application:
  name: demo
  port: ${server.port}
---
spring:
----------
spring:
  profiles:
    active: test
    group:application-test,application-server,application-client
application:
  name: demo
----------
spring:
  profiles:
    active: prod
    group:application-prod
application:
  name: demo    

还可以对不同环境的文件进行分组,例如:application-dev.ymlapplication-prod.yml,在启动时通过 --spring.profiles.active=dev 指定使用哪个配置文件。

# 配置端口号
server.port=8088
# 配置项目名称 
spring.application.name=demo
# 配置数据库连接信息
spring.datasource.url=jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root

# 自定义其他配置  在项目中可以直接引入
demo.name=demo
demo.age=18
demo.hobby=["打球","学习动作片"]

在其他类中可以通过 @Value 注解来获取配置文件中的值

获取配置文件值

Spring Boot 提供了 @Value@ConfigurationProperties 注解,可以将配置文件中的属性映射到 Java 对象中。Value注解支持单个值的绑定,ConfigurationProperties支持松散方式绑定,例如 demo.name 可以映射到 demoName,不需要单个绑定值。

@Value("${demo.name}")
private String name;

@Configuration
@ConfigurationProperties(prefix = "demo")
public class DemoProperties {
  private String name;
  private int age;
  private List<String> hobby;
}

PropertySource

如果需要从其他位置加载配置文件,可以使用 @PropertySource 注解。例如,从 classpath:config/application.properties 加载配置文件:

@Configuration
// classpath:指定配置文件位置 默认是resource目录下的配置文件 
@PropertySource("classpath:application.properties")
public class DemoProperties {
  @Value("${demo.name}")
  private String name;
}

# 加载多个文件
@Configuration
@PropertySource({"classpath:application.properties","classpath:config/application.properties"})
public class DemoProperties {
  @Value("${demo.name}")
  private String name;
}

#  动态加载(基于 Profile@Configuration
@PropertySource("classpath:config-${spring.profiles.active}.properties")
public class AppConfig {
  // ...
}

# 从外部文件加载(非 classpath)
@Configuration
@PropertySource("file:/path/to/external/config.properties")
public class AppConfig {
  // ...
}

# 与 @ConfigurationProperties 结合
@Configuration
@PropertySource("classpath:app.properties")
@ConfigurationProperties(prefix = "app")
public class AppProperties {
   private String name;
   private String version;
}

默认情况下,@PropertySource 不支持 YAML 文件,但可以通过自定义 PropertySourceFactory 实现对 YAML 文件的支持。

@Configuration
@PropertySource(value = "classpath:config.yml", factory = YamlPropertySourceFactory.class)
public class AppConfig {
    // ...
}

import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;

import java.io.IOException;

public class YamlPropertySourceFactory implements PropertySourceFactory {
  @Override
  public PropertySource<?> createPropertySource(String name, EncodedResource resource) throws IOException {
    return new YamlPropertySourceLoader().load(resource.getResource()
    .getFilename(), resource.getResource()).get(0);
  }
}

配置文件加载顺序

Spring Boot 会按照以下顺序加载配置文件:

  1. file:./config/:当前项目下的 config 目录。
  2. file:./:当前项目根目录。
  3. classpath:/config/:classpath 下的 config 目录。
  4. classpath:/:classpath 根目录。

常用注解

@SpringBootApplication

Spring Boot 的核心注解,用于标识一个 Spring Boot 应用程序的入口类。它是一个组合注解,包含了 @Configuration@EnableAutoConfiguration@ComponentScan 三个注解。

  • @Configuration:用于标识一个类为配置类,可以在这个类中定义 Bean。
  • @Component:用于标识一个类为 Spring 组件,可以被 Spring 容器管理。
  • @Controller:用于标识一个类为 Spring MVC 控制器,可以在这个类中定义请求处理方法。
  • @RestController:是 @Controller 和 @ResponseBody 的组合注解,返回的对象会自动转换为 JSON 或 XML 等格式的数据返回给客户端。
  • @RequestMapping: @RequestMapping(value = "/example", method = RequestMethod.GET)用于映射请求路径,可以指定请求方法、请求参数、请求头等。
    • @GetMapping:用于映射 GET 请求。
    • @PostMapping:用于映射 POST 请求。
    • @PutMapping:用于映射 PUT 请求。
    • @DeleteMapping:用于映射 DELETE 请求。
    • @PatchMapping:用于映射 PATCH 请求。
  • @ComponentScan:用于指定 Spring Boot 应该扫描哪些包以查找 Bean,Spring Boot默认是扫描@SpringBootApplication所在启动类的文件夹以及子文件夹,相邻或者父文件夹以及以上的兄弟文件夹夹注册的Bean是扫描不到的。
    • @ComponentScan(basePackages = "com.example.demo"):指定扫描的包路径。
  • @Service:用于标识一个类为服务层组件,通常用于处理业务逻辑。Spring 会自动扫描并将其注册为 Bean。
  • @Repository:用于标识一个类为数据访问层组件,通常用于与数据库交互。Spring 会自动扫描并将其注册为 Bean,同时还会对该类中抛出的数据访问异常进行转换。
  • @Value: 用于从配置文件(如 application.properties 或 application.yml)中读取属性值并注入到字段中。
  • @Autowired: 用于自动装配 Bean。Spring 会根据类型在容器中查找匹配的 Bean 并注入到使用该注解的字段、构造方法或 setter 方法中。
  • @Qualifier: 当存在多个相同类型的 Bean 时,@Autowired 无法确定要注入哪个 Bean,此时可以使用 @Qualifier 注解指定要注入的 Bean 的名称。
  • @EnableAutoConfiguration:用于自动配置 Spring Boot 应用程序,根据类路径中的 jar 依赖自动配置 Spring 应用程序。
  • @Validated:用于标识一个类需要进行验证,可以在这个类中定义验证规则。
  • @Aspect: 用于标识一个类为切面类,该类中可以定义切点和通知。

Bean注册

Spring Boot 提供了多种方式来注册 Bean,包括:

  • @Bean:用于在配置类中定义一个 Bean 注册到容器中,可以指定 Bean 的名称、作用域、初始化方法、销毁方法等。

静态资源

默认配置

Spring Boot 提供了默认的静态资源路径,这些路径下的文件可以直接通过 URL 访问。默认的静态资源路径包括:

  • classpath:/static/
  • classpath:/public/
  • classpath:/resources/
  • classpath:/META-INF/resources/

自定义配置

可以通过在 application.propertiesapplication.yml 文件中添加以下配置来自定义静态资源路径:

# 设置静态资源路径
spring.resources.static-locations=classpath:/static/,classpath:/public/
spring:
  profiles:
    active: dev
  resources:
    static-locations: classpath:/static/,classpath:/public/

设置静态资源的加载

在 webapp 应用中可以通过 webjars 官网 查找需要的静态资源,通过maven的方式复制代码到 pom.xml 文件中加载依赖。例如 jQuery,可以通过下列方式加载。

# 在 pom.xml 文件中 dependencies 添加以下依赖:
<dependency>
    <groupId>org.webjars</groupId>
    <artifactId>jquery</artifactId>
    <version>3.5.0</version>
</dependency>

通过 localhost:8080/webjars/jquery/3.5.0/jquery.js 访问静态资源

加载的静态资源

还可以通过下面方式加载静态资源 前提是不另外在配置里另外设置静态资源文件夹,访问方式为 localhost:8080/jquery-3.5.0.js

  • resources:/src/main/resources/ 配置文件目录
    • resources:放置静态资源 权限最高
    • static:默认静态资源配置文件夹 权限第二
    • public:公共资源配置文件夹
    • META-INF/resources:默认静态资源配置文件夹

可以通过 WebMvcAutoConfiguration 类查看默认配置 addResourceHandlers 方法

设置首页src/main/resources/static 目录下创建一个名为 index.html 的文件,然后在浏览器中访问 localhost:8080 即可看到首页内容。 首页

模板引擎

Spring Boot 支持多种模板引擎,包括 Thymeleaf、Freemarker、Mustache 等。默认情况下,Spring Boot 会自动配置 Thymeleaf 模板引擎。

  • 在 pom.xml 文件中添加 Thymeleaf 依赖:
    <dependencies>
      <!-- Thymeleaf 核心依赖 -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>
    
      <!-- 如果需要 Thymeleaf 对 Spring Security 的支持(可选) -->
      <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity6</artifactId>
      </dependency>
    </dependencies>
    
  • src/main/resources/templates 目录下创建一个模板文件。
    <!DOCTYPE html>
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
      <meta charset="UTF-8">
      <title>Hello Thymeleaf</title>
    </head>
    <body>
      <h1 th:text="${message}"></h1>
    </body>
    </html>
    
  • 在控制器中返回模板文件名即可。
    @Controller
    public class HelloController {
      @GetMapping("/template")
      public String hello(Model model) {
        // 设置模板变量
        model.addAttribute("message", "Hello, Thymeleaf!");
        return "hello"; // 对应 templates/hello.html
      }
    }
    

相关代码参考 ThymeleafProperties 类,模板html的内部变量设置请查看 Thymeleaf 文档和相关版本。

在前后端分离的项目中,一般不会使用模板引擎,而是使用前后端分离的方式,通过 Ajax 请求获取数据,然后在前端页面中展示数据。

数据库连接

当前流行的方式是使用 mybatis + springboot + mysql
更新 Pom.xml 文件,添加数据库连接依赖:MySQL Driver 驱动和 Spring Data JDBC 依赖。

<dependencies>
    <!-- MySQL Driver -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.26</version>
    </dependency>

    <!-- Spring Data JDBC -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jdbc</artifactId>
    </dependency>
</dependencies>

application.yml 文件中添加数据库连接配置。

server
  port: 8088
spring
  datasource:
    // 若是出现时区配置错误 可以添加 serverTimezone=UTC
    url: jdbc:mysql://localhost:3306/demo?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:

配置数据库连接

编辑一个连接数据库的类,例如 DataSourceConfig

package org.example.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.jdbc.core.JdbcTemplate;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class HelloWorld {
    @Autowired
    JdbcTemplate jdbcTemplate;
    @RequestMapping("/hello")
    public List hello() {
        String sql = "select * from comments";
        List<Map<String, Object>> list_map = jdbcTemplate.queryForList(sql);
        System.out.println(list_map);
        return list_map;
    }
}

连接远程数据库

操作数据库服务

package org.example.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController
public class HelloWorld {
  @Autowired
  JdbcTemplate jdbcTemplate;

  @RequestMapping("/search")
  public List search() {
    String sql = "select * from comments";
    List<Map<String, Object>> list_map = jdbcTemplate.queryForList(sql);
    System.out.println(list_map);
    Map<String, Object> response = new HashMap<>();
    response.put("resCode", 200);
    response.put("resMsg", "success");
    response.put("data", list_map);
    return response;
  }

  @RequestMapping("/add")
  public String add() {
    String sql = "insert into blog.comments (id, name, content) values (1, '张三', '这是一条评论')";
    jdbcTemplate.update(sql);
    return "success";
  }

  @RequestMapping("/delete/{id}")
  public String delete(@PathVariable("id") int id) {
      String sql = "delete from comments where  id ="+id;
      jdbcTemplate.update(sql);
      return "success";
  }

  @RequestMapping("/update/{id}")
  public String update(@PathVariable("id") int id ) {
    String sql = "update blog.comments set username = '李四-2366id' where id ="+id;
    jdbcTemplate.update(sql);
    return "success";
  }
}

整合 MyBatis

MyBatis 是一个优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。在 Spring Boot 中,我们可以使用 MyBatis 来操作数据库。

1. 添加 MyBatis 依赖

pom.xml 文件中添加 MyBatis 依赖:

		<!-- JDBC -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
			<version>3.4.4</version>
		</dependency>
		<!--  MYSQL  -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.26</version>
		</dependency>
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.4</version>
    </dependency>

2. 配置 MyBatis

mybatis:
  # 静态资源 映射文件
  mapper-locations: classpath:mapper/*.xml
  # 实体类 包扫描位置不能错,
  type-aliases-package: com.example.demo.entity

3. 创建实体类

创建一个实体类,例如 User

package com.example.demo.entity;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Comments {
    private int id;
    private String name;
    private String content;
    private String time;
    ...
}

4. 创建 Mapper 接口

创建一个 Mapper 接口,例如 UserMapper

package com.example.demo.mapper;
@Mapper
@Repository
public interface CommentsMaper {
    List<Comments> getComments();
    int addComments(Comments comments);
    int delComments(int id);
    int updateComments(Comments comments);
}

5. 创建 Mapper XML 文件

创建一个 Mapper XML 文件,例如 UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.misstree.demo.mapper.CommentsMaper">
    <select id="getComments" resultType="Comments">
        select * from comments
    </select>

    <insert id="addComments" parameterType="Comments">
        insert into comments(id,name,content) values(#{id},#{name},#{content})
    </insert>

    <delete id="delComments" parameterType="Comments">
        delete from comments where id = #{id}
    </delete>

    <update id="updateComments" parameterType="Comments">
        update comments set name = #{name},content = #{content} where id = #{id}
    </update>
</mapper>

6. 在 Service 中使用 Mapper

在 Service 中注入 Mapper,并使用它来操作数据库:

@Data
@AllArgsConstructor
@NoArgsConstructor
@RestController
public class CommentsController {
    @Autowired
    private CommentsMaper commentsMaper;

    @GetMapping("/getComments")
    public List<Comments> getComments(){
        return commentsMaper.getComments();
    }
    @RequestMapping("/addComments")
    public int addComments(Comments comments){
        return commentsMaper.addComments(comments);
    }
    @RequestMapping("/delComments/{id}")
    public int delComments(@PathVariable int id){
        return commentsMaper.delComments(id);
    }
    @RequestMapping("/updateComments")
    public int updateComments(Comments comments){
        return commentsMaper.updateComments(comments);
    }
}

注意:在 Mapper 接口中,每个方法都需要对应一个 SQL 语句,SQL 语句可以在 Mapper XML 文件中定义。application.yml 中配置了 mapper-locations 和 type-aliases-package,这样 MyBatis 就可以自动扫描到这些配置,并自动映射实体类和 SQL 语句。

数据库连接池

HikariCP

HikariCP Spring Boot 2.x默认连接池,性能极高

特点:轻量级,代码精简,启动速度快,并发性能好

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10
      idle-timeout: 30000
      max-lifetime: 1800000
      connection-timeout: 30000

Druid(阿里巴巴)

Druid (阿里巴巴) 国内最流行的连接池,功能全面

github文档阿里云官网 特点:强大的监控功能,防SQL注入,支持分库分表,国内开发者文档丰富 因为Spring Boot 3.x版本开始,迁移到了Jakarta EE 9+,所以主要分两个大版本依赖,支持 springboot 2.x 的 druid 和支持 springboot 3.x 的 druid-spring-boot-3-starter

spring boot 2.x

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.23</version>
    </dependency>
spring:
  datasource:
    #  若是出现时区配置错误 可以添加 serverTimezone=UTC
    url: jdbc:mysql://[2409:xxxx:4826:xxxx:80cc:1212:xxxx:xxxx]:3306/blog?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    username: xxxx
    password: xxxxxxx
    driver-class-name: com.mysql.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      # 初始化连接池即创建连接,建议保持不变
      init: true
      # 初始化连接池时建立连接的个数,可以根据实际情况调整
      initialSize: 10
      # 连接池中允许的最大连接数量,可以根据实际情况调整,建议和业务线程池大小相同
      maxActive: 200
      # 连接池中维护的空闲连接的数量,可以根据实际情况调整,建议和maxActive的值相同
      minIdle: 200
      # 获取连接最大等待时间,单位毫秒(ms),建议保持不变
      maxWait: 30000

      # 连接保活配置项,建议保持不变,否则可能出现连接断开
      druid.keepAlive: true
      # 连接空闲多久以后,会检查连接的有效性
      druid.keepAliveBetweenTimeMillis: 120000
      # 多久执行一次淘汰连接和保活操作
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 1800000

      # 连接验证配置项,建议保持不变
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # 测试配置连接是否有效
      validation-query: select 'x'
      # 是否在连接处于空闲状态时进行有效性检查。
      test-while-idle: true
      # 从连接池中获取连接时,是否要对连接进行有效性检查。
      test-on-borrow: false
      # 归还连接时,是否要检查连接是否有效。
      test-on-return: false
      # 对预编译语句进行缓存
      pool-prepared-statements: true
      # 连接池中连接的最大连接数
      max-open-prepared-statements: 20

设置配置

package org.example.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

    @Bean
    public ServletRegistrationBean<StatViewServlet> statViewServlet() {
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin");
        initParams.put("loginPassword", "admin");
        initParams.put("allow", "");
        bean.setInitParameters(initParams);
        return bean;
    }

    @Bean
    public FilterRegistrationBean<WebStatFilter> webStatFilter() {
        FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new WebStatFilter());
        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/*");
        bean.setInitParameters(initParams);
        bean.addUrlPatterns("/*");
        return bean;
    }
}

spring boot 3.x

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid-spring-boot-3-starter</artifactId>
    </dependency>
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      url: jdbc:mysql://[2409:8a5c:4826:80:xxxx:1212:xxxx:xxxx]:3306/blog?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
      #url=jdbc:mysql://ld-uf6k8yqb741t3****-proxy-sql-lindorm-public.lindorm.rds.aliyuncs.com:33060/database?sslMode=disabled&allowPublicKeyRetrieval=true&useServerPrepStmts=true&useLocalSessionState=true&rewriteBatchedStatements=true&cachePrepStmts=true&prepStmtCacheSize=100&prepStmtCacheSqlLimit=50000000&socketTimeout=120000
      username: xxx
      password: xxxxx
      driver-class-name: com.mysql.cj.jdbc.Driver

      # 初始化连接池即创建连接,建议保持不变
      init: true
      # 初始化连接池时建立连接的个数,可以根据实际情况调整
      initialSize: 10
      # 连接池中允许的最大连接数量,可以根据实际情况调整,建议和业务线程池大小相同
      maxActive: 200
      # 连接池中维护的空闲连接的数量,可以根据实际情况调整,建议和maxActive的值相同
      minIdle: 200
      # 获取连接最大等待时间,单位毫秒(ms),建议保持不变
      maxWait: 30000

      # 连接保活配置项,建议保持不变,否则可能出现连接断开
      druid.keepAlive: true
      # 连接空闲多久以后,会检查连接的有效性
      druid.keepAliveBetweenTimeMillis: 120000
      # 多久执行一次淘汰连接和保活操作
      timeBetweenEvictionRunsMillis: 60000
      minEvictableIdleTimeMillis: 1800000

      # 连接验证配置项,建议保持不变
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # 测试配置连接是否有效
      validation-query: select 'x'
      # 是否在连接处于空闲状态时进行有效性检查。
      test-while-idle: true
      # 从连接池中获取连接时,是否要对连接进行有效性检查。
      test-on-borrow: false
      # 归还连接时,是否要检查连接是否有效。
      test-on-return: false
      # 对预编译语句进行缓存
      pool-prepared-statements: true
      # 连接池中连接的最大连接数
      max-open-prepared-statements: 20
      # 监控配置
      stat-view-servlet:
        enabled: true
        url-pattern: /druid/*

设置配置

package org.example.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.jakarta.StatViewServlet;  // 替换旧的http包
import com.alibaba.druid.support.jakarta.WebStatFilter;    // 替换旧的http包
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class DruidConfig {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.druid")
    public DataSource druidDataSource() {
        System.out.println(new DruidDataSource());
        return new DruidDataSource();
    }

    @Bean
    public ServletRegistrationBean<StatViewServlet> statViewServlet() {
        // 使用 jakarta.servlet.Servlet 兼容的 StatViewServlet
        ServletRegistrationBean<StatViewServlet> bean = new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
        Map<String, String> initParams = new HashMap<>();
        initParams.put("loginUsername", "admin");
        initParams.put("loginPassword", "admin");
        initParams.put("allow", "");
        bean.setInitParameters(initParams);
        return bean;
    }

    @Bean
    public FilterRegistrationBean<WebStatFilter> webStatFilter() {
        FilterRegistrationBean<WebStatFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new WebStatFilter());
        Map<String, String> initParams = new HashMap<>();
        initParams.put("exclusions", "*.js,*.css,/druid/*");
        bean.setInitParameters(initParams);
        bean.addUrlPatterns("/*");
        return bean;
    }
}

数据库监控

Tomcat JDBC Pool

Tomcat JDBC Pool Spring Boot 1.x默认连接池

特点:稳定性高,适合传统应用

spring:
  datasource:
    tomcat:
      max-active: 50
      max-idle: 10
      min-idle: 5

c3p0

c3p0 老牌连接池 特点:兼容性好,稳定性高

<dependency>
    <groupId>com.mchange</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.5.5</version>
</dependency>

一般来说性能排序:HikariCP > Druid > Tomcat JDBC > c3p0

Springsecurity(安全)

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入spring-boot-starter-security模块,进行少量的配置,即可实现强大的安全管理!

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策路
  • AuthenticationManagerBuilder:自定义认证策路
  • @EnableWebsecurity:开启Websecurity模式

Spring Security的两个主要目标是“认证”和“授权”(访问控制)。 “认证”(Authentication) "授权”(Authorization)

这个概念是通用的,而不是只在Spring Security 中存在

参考官网:https://spring.io/projects/spring-security, 查看我们自己项目中的版本,找到对应的帮助文档:https://docs.spring.io/spring-security/site/docs/5.2.0.RELEASE/reference/htmlsingle/

用户认证和授权

主要是服务端渲染模板方式判断,前后端分离方式由后端数据判断前端处理

//AOP : 拦截器!
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    //链式编释
    @Override
    protected void configure(Httpsecurity http)throws Exception {
      //首页所有人可以访问,功能页只有对应有权限的人才能访问//诸求授权的规网~
      http.authorizeRequests()
        .antMatchers("/").permitAll()
        .antMatchers("/level1/*").hasRole("vip1")
        .antMatchers("/leve12/*").hasRole("vip2")
        .antMatchers("/level3/**").hasRole("vip3")

      //没有权限默认会到发录页面
      http.formLogin();

      //注销
      // 防止CSRF攻击
      http.csrf().disable();
      http.logout().logoutSuccessUrl("/");

      // 开启记住我功能
      http.rememberMe().characterEncoding("utf-8").tokenValiditySeconds(60 * 60 * 24);
    }
    }

    //认证,springboot 2.1.x 可以直按使用//余码编码:PasswordEncoder
    //4:spring secutiry 5.e+ 新增了很多的加密方法,
    @Override
    protected void configure(AuthenticationManagerBuilder auth)throws Exception {
      //这些数据正常应该从数据库中谈
      auth.inMemoryAuthentication().passwordEncoder(new BcryptPasswordEncoder())
        .withUser( username: "mn").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
        .withUser( username: "root").password(new BcryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
        .and()
        .withuser( username: "guest").password(new BcryptPasswordEncoder().encode("123466")).roles("vip1");
    }  
}

//  EnableWebSecurity 开启WebSecurity模式
@EnableWebSecurity
public class DirectlyConfiguredJwkSetUri extends WebSecurityConfigurerAdapter {
    protected void configure(HttpSecurity http) {
        http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .oauth2ResourceServer().jwt()
            .jwtAuthenticationConverter(grantedAuthoritiesExtractor());
    }
}

在 Security 5.7 后 WebSecurityConfigurerAdapter 被标记为过时,建议直接实现SecurityFilterChain接口。

package com.misstree.demo.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((requests) -> requests
                .anyRequest().authenticated()
            )
            .formLogin((form) -> form
                .loginPage("/login")
                .permitAll()
            )
            .logout((logout) -> logout.permitAll());
        return http.build();
    }
}

JWT

JWT(JSON Web Token)是一种用于在各方之间安全传输信息的紧凑、URL安全的方式。它由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。JWT通常用于身份验证和授权。 在 Java Spring Boot 3 中,除了 ​JJWT​(io.jsonwebtoken)之外,另一个常用的 JWT 库是 ​java-jwt​(com.auth0:java-jwt)。它也是一个功能强大且广泛使用的 JWT 实现库。

JWT 优点

  • 无状态:JWT是无状态的,服务器不需要存储会话信息,因此可以轻松扩展。
  • 简单:JWT的格式简单,易于解析和验证。
  • 安全:JWT使用数字签名来确保数据的完整性和真实性。
  • 跨平台:JWT可以在不同的平台和语言之间传递,无需担心兼容性问题。

JWT 缺点

  • 安全性:JWT的安全性取决于密钥的保密性,如果密钥泄露,攻击者可以伪造JWT。
  • 性能:由于JWT是无状态的,每次请求都需要验证JWT,可能会对服务器性能产生影响。
  • 可扩展性:JWT的扩展性不如传统的会话管理方式,如果需要存储额外的信息,可能需要修改JWT的结构。

JWT 使用场景

  • 身份验证:JWT可以用于验证用户的身份,并在用户登录后生成一个JWT,用户在后续的请求中携带JWT进行身份验证。

JWT 依赖

<!-- Spring Security -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- JWT 库 -->
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
</dependency>

JWT 代码

配置application.properties

# JWT配置
app.jwtSecret=yourVerySecureSecretKeyThatShouldBeLongAndRandom
app.jwtExpirationInMs=86400000 # 24小时

工具类

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

@Component
public class JwtTokenProvider {

    @Value("${app.jwtSecret}")
    private String jwtSecret;

    @Value("${app.jwtExpirationInMs}")
    private int jwtExpirationInMs;

    // 生成密钥
    private Key getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(jwtSecret);
        return Keys.hmacShaKeyFor(keyBytes);
    }

    // 从认证对象生成token
    public String generateToken(Authentication authentication) {
        UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();

        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);

        return Jwts.builder()
                .setSubject(Long.toString(userPrincipal.getId()))
                .setIssuedAt(new Date())
                .setExpiration(expiryDate)
                .signWith(getSigningKey(), SignatureAlgorithm.HS512)
                .compact();
    }

    // 从token中获取用户ID
    public Long getUserIdFromJWT(String token) {
        Claims claims = Jwts.parserBuilder()
                .setSigningKey(getSigningKey())
                .build()
                .parseClaimsJws(token)
                .getBody();

        return Long.parseLong(claims.getSubject());
    }

    // 验证token
    public boolean validateToken(String authToken) {
        try {
            Jwts.parserBuilder().setSigningKey(getSigningKey()).build().parse(authToken);
            return true;
        } catch (MalformedJwtException ex) {
            // 无效的JWT令牌
        } catch (ExpiredJwtException ex) {
            // 过期的JWT令牌
        } catch (UnsupportedJwtException ex) {
            // 不支持的JWT令牌
        } catch (IllegalArgumentException ex) {
            // JWT声明为空
        }
        return false;
    }
}

创建认证过滤器

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider tokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        
        try {
            String jwt = getJwtFromRequest(request);

            if (jwt != null && tokenProvider.validateToken(jwt)) {
                Long userId = tokenProvider.getUserIdFromJWT(jwt);
                
                // 这里可以根据需要加载用户信息
                // Authentication authentication = ... 
                // SecurityContextHolder.getContext().setAuthentication(authentication);
                
                // 简单示例 - 实际应用中应该创建完整的Authentication对象
                SecurityContextHolder.getContext().setAuthentication(
                    new UsernamePasswordAuthenticationToken(userId, null, Collections.emptyList())
                );
            }
        } catch (Exception ex) {
            logger.error("Could not set user authentication in security context", ex);
        }

        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

配置 Spring Security

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtAuthenticationFilter jwtAuthenticationFilter;
    private final UserDetailsService userDetailsService; // 你的UserDetailsService实现

    public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter, 
                         UserDetailsService userDetailsService) {
        this.jwtAuthenticationFilter = jwtAuthenticationFilter;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .cors().and().csrf().disable()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .authorizeRequests()
                .antMatchers("/api/auth/**").permitAll() // 登录注册等公开接口
                // 放行特定的 POST 请求(如登录/注册)
                .antMatchers(HttpMethod.POST, "/api/auth/**").permitAll()
                // 放行静态资源
                .antMatchers("/static/**", "/swagger-ui/**").permitAll()
                .antMatchers(HttpMethod.GET, "/**").permitAll()// 放行所有 GET 请求
                .anyRequest().authenticated(); // 其他接口需要认证

        // 添加JWT过滤器
        http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

创建认证控制器

import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

    private final AuthenticationManager authenticationManager;
    private final JwtTokenProvider tokenProvider;
    private final UserDetailsService userDetailsService; // 你的UserDetailsService实现

    public AuthController(AuthenticationManager authenticationManager,
                          JwtTokenProvider tokenProvider,
                          UserDetailsService userDetailsService) {
        this.authenticationManager = authenticationManager;
        this.tokenProvider = tokenProvider;
        this.userDetailsService = userDetailsService;
    }

    @PostMapping("/login")
    public ResponseEntity<?> authenticateUser(@RequestBody LoginRequest loginRequest) {
        Authentication authentication = authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                loginRequest.getUsernameOrEmail(),
                loginRequest.getPassword()
            )
        );

        SecurityContextHolder.getContext().setAuthentication(authentication);
        String jwt = tokenProvider.generateToken(authentication);
        
        // 返回token和其他用户信息
        return ResponseEntity.ok(new JwtAuthenticationResponse(jwt));
    }
}

OAuth

特性OAuth1.0OAuth2.0
签名机制必须签名(HMAC-SHA1)依赖HTTPS,移除签名
​令牌格式无明确规范支持Bearer Token(如JWT)
​流程灵活性单一流程多种授权流程(授权码、隐式等)
适用场景主要Web应用Web、移动端、服务间通信等
安全性增强较高(但实现复杂)更高(HTTPS + 多种防护措施)
​扩展性有限支持OIDC、设备授权等扩展

在单纯的是前后端分离web应用使用OAuth1.0也可以(但是现在OAuth1.0在逐渐淘汰),在需要第三方应用授权的情况需要使用到 OAuth2.0,如微信登录、QQ登录等。 以下功能必须要用到OAuth2.0:

  • 第三方登录:如微信、QQ、微博等第三方平台提供的登录功能。
  • ​授权码模式(Authorization Code)​
  • 客户端凭证模式(Client Credentials)​
  • ​令牌交换(Token Exchange)​
  • OpenID Connect(OIDC)​

OAuth2.0 依赖

<!-- Spring Security OAuth2 核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>

<!-- 如果需要资源服务器功能(验证JWT令牌) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>

<!-- 如果需要授权服务器(颁发JWT) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(auth -> auth
                .antMatchers("/api/public/**").permitAll()
                .anyRequest().authenticated()
            )
            // JWT认证(自定义过滤器)
            .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            // OAuth2登录
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/login") // 自定义登录页
                .defaultSuccessUrl("/home", true)
            );
        return http.build();
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter() {
        return new JwtAuthenticationFilter(jwtTokenProvider);
    }
}