Spring Boot

星期四, 12月 26, 2024 | 60分钟阅读

MyraZ
Spring Boot

关于Spring Boot的学习!

SpringBoot

Spring框架发展史

Spring1.x 时代

​ 在Spring1.x时代,都是通过xml文件配置bean,随着项目的不断扩大,需要将xml配置分放到不同的配置文件中,需要频繁的在java类和xml配置文件中切换。

Spring2.x时代

​ 随着JDK 1.5带来的注解支持,Spring2.x可以使用注解对Bean进行申明和注入,大大的减少了xml配置文件,同时也大大简化了项目的开发。

​ 那么,问题来了,究竟是应该使用xml还是注解呢?

最佳实践:

  • 应用的基本配置用xml,比如:数据源、资源文件等;

  • 业务开发用注解,比如:Service中注入bean等;

Spring3.x到Spring4.x再到Spring5.x

​ 从Spring3.x开始提供了Java配置方式,使用Java配置方式可以更好的理解你配置的Bean,现在我们就处于这个时代,并且Spring4.x、Spring5.x和Spring Boot都推荐使用java配置的方式。

Spring 5.X 应用零配置开发

​ Spring 框架从5.x版本推荐使用注解形式来对java应用程序进行开发与配置,并且可以完全替代原始的XML+注解形式的开发,在使用注解形式进行项目开发与环境配置时,Spring 框架提供了针对环境配置与业务bean开发相关注解。

注解

声明Bean注解

@Component:组件 没有明确规定其角色作用在类级别上声明当前类为一个业务组件被Spring Ioc 容器维护;

@Service:在业务逻辑层Service 层类级别进行声明;

@Repository:在数据访问层(dao 层) 类级别声明;

@Controller:在展现层(MVC) 使用 标注当前类为一个控制器

注入Bean注解

@AutoWired:Spring 官方提供注解
    
@Inject:JSR-330 提供注解标准制定方
    
@Resource:JSR-250 提供注解

​ 以上三种注解在Set 方法或属性上声明,一般情况下通用一般开发中更习惯声明在属性上,代码简洁清晰。基于5.x 注解配置方式简化了xml 配置,应用程序开发与xml 环境配置均通过相应注解来实现。

Spring5.x中配置与获取Bean注解

@Configuration:作用与类上将当前类声明为一个配置类相当于一个xml 配置文件
    
@ComponentScan:自动扫描指定包下标注有@Repository,@Service,@Controller
    
@Component:注解的类并由Ioc 容器进行实例化和维护
    
@Bean:作用于方法上,相当于xml 文件中<bean> 声明当前方法返回值为一个bean可用于第三方组件的实例化主要作用因为第三方组件的源码无法改变没办法通过在类上加@Resource和@AutoWried的方式注入
    
@Value:获取properties 文件指定key value值

实例1-Ioc中Bean的实例化与获取

创建Spring普通工程

在pom.xml中添加坐标相关配置

  <dependencies>
    <!--spring的依赖坐标-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <!-- 指定Maven编译的JDK版本和编码-->
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <!--源代码使用的JDK版本-->
          <source>1.8</source>
          <target>1.8</target>
          <encoding>utf-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>

创建Bean对象

UserDao.java

@Repository
public class UserDao {

    public void test(){
        System.out.println("UserDao test...");
    }
}

UserService.java

@Service
public class UserService {

    @Resource
    private UserDao userDao;

    public void test(){
        System.out.println("UserService test...");

        userDao.test();
    }
}

创建IocConfig配置类

@Configuration //声明当前类是一个配置类,相当于xml配置文件
@ComponentScan("com.msb")//扫描器,定义扫描的包范围
public class IOCConfig {
}

创建启动类执行测试

public class Starter {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(IOCConfig.class);
        //得到需要被实例化的对象
        UserService service = context.getBean(UserService.class);
		//调用Bean对象的方法
        service.test();

    }
}

​ 此时启动Spring Ioc 容器,通过实例化AnnotationConfigApplicationContext类,接收配置参数类IocConfig,并获取UserService Bean 实现方法调用,此时应用环境不存在xml配置文件,简化了应用的xml 配置。

实例2-@Bean注解使用

​ 使用@Bean注解声明在方法(注意:方法名一般为bean对象名称)级别,用于返回实例化的Bean对象。

创建Bean对象

​ AccountDao.java

//不添加实例化注解
public class AccountDao {

    public void test(){
        System.out.println("AccountDao test...");
    }
}

修改IocConfig 配置类

​ 添加返回AccountDao Bean对象方法

@Configuration //声明当前类是一个配置类,相当于xml配置文件
@ComponentScan("com.msb")//扫描器,定义扫描的包范围
public class IOCConfig02 {

    /**
     * 实例化bean对象
     * @Bean 将方法的返回值(bean对象)交给IOC进行维护
     * 可用于第三方组件的实例化(主要作用,因为第三方组件的源码无法改变,没办法通过在类上加@Resource和@AutoWried的方式注入)
     */
    @Bean
    public AccountDao accountDao(){
        return new AccountDao();
    }
}

创建启动类执行测试

public class Starter02 {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(IOCConfig02.class);
        //得到需要被实例化的对象
        AccountService accountService = (AccountService) context.getBean("accountService");

        accountService.test();

    }
}

实例2-读取外部配置文件

​ 在开发Java web 应用进行时,配置文件是比较常见的,比如xml,properties,yml等文件,在Spring应用中对于配置文件的读取同样提供支持。对于配置文件读取,我们可以通过@PropertySource 注解声明到类级别来指定读取相关配置配置。

​ Spring El表达式语言,支持在Xml和注解中使用表达式,类似于JSP中EL表达式,Spring框架在借助该表达式实现资源注入,主要通过@Value 注解来使用表达式,通过@Value 注解,可以实现普通字符串,表达式运算结果,Bean 属性文件内容,属性文件等参数注入。具体使用如下:

准备properties配置文件

​ src/main/resources 目录下添加user.properties jdbc.roperties 文件

# user.properties
user.userName=admin
user.password=admin
# jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/hr?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root

@PropertySource加载配置文件

@Configuration //声明当前类是一个配置类,相当于xml配置文件
@ComponentScan("com.msb")//扫描器,定义扫描的包范围
//加载配置文件,声明在设置过@Configuration注解的配置类上
@PropertySource(value = {"classpath:db.properties","classpath:user.properties"})
public class IOCConfig03 {

    //通过@Value注解获取指定key的值, @Value("${key}"),声明在字段名上
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.username}")
    private String name;
    @Value("${jdbc.password}")
    private String pwd;

    @Value("${user.userName}")
    private String userName;
    @Value("${user.password}")
    private String password;

    public void print(){
        System.out.println("driver:"+driver+",url:"+url);
        System.out.println("name:"+name+",pwd:"+pwd);
        System.out.println("userName:"+userName+",password:"+password);
    }
}

其他Bean对象获取properties文件内容

@Service
public class UserService {

    @Resource
    private UserDao userDao;

    public void test(){
        System.out.println("UserService test...");

        userDao.test();
    }
}

启动Starter 查看控制台输出内容效果

组合注解与元注解

​ Spring 从2.x版本开始引入注解支持(目的是jdk1.5中推出注解功能),通过引入注解来消除大量xml 配置,Spring 引入注解主要用来注入bean以及aop 切面相关配置,但由于注解大量使用,就会造成大量重复注解代码出现,代码出现了重复,Spring 为了消除重复注解,在元注解上引入了组合注解,其实可以理解为对代码的重构,相当于注解的注解,拥有元注解的原始功能,比如在定义配置类时用到的@Configuration 注解就是组合注解,拥有Component 注解功能,即配置类本身也是一个被Ioc维护的单例Bean。

image-20250109104723096

自定义组合注解

  • 定义MyCompScan 注解,拥有@ComponentScan 扫描器注解功能

    /**
     * 自定义组合注解
     * 		拥有元注解@Configuration + @ComponentScan 两者功能
     * 		覆盖value 属性
     *      1.设置元注解
     *          参考Configuration的元注解
     *      2。添加想要组合注解
     *          配置类注解
     *          扫描器注解
     *
     */
    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    //配置类注解
    @Configuration
    //扫描器注解,设置扫描包范围
    @ComponentScan
    public @interface MyComScan {
    
        //覆盖@ComponentScan注解的属性
        String[] value() default {};
    }
    

应用组合注解

//使用自定义组合注解,结合了元注解+@Configuration+@ComponentScan
@MyComScan("com.msb")
//加载配置文件,声明在设置过@Configuration注解的配置类上
@PropertySource(value = {"classpath:db.properties","classpath:user.properties"})
public class IOCConfig04 {

    //通过@Value注解获取指定key的值, @Value("${key}"),声明在字段名上
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.username}")
    private String name;
    @Value("${jdbc.password}")
    private String pwd;

    @Value("${user.userName}")
    private String userName;
    @Value("${user.password}")
    private String password;

    public void print(){
        System.out.println("driver:"+driver+",url:"+url);
        System.out.println("name:"+name+",pwd:"+pwd);
        System.out.println("userName:"+userName+",password:"+password);
    }
}

测试组合注解

public class Starter04 {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(IOCConfig04.class);
        //得到需要被实例化的对象
        AccountService accountService = (AccountService) context.getBean("accountService");

        //得到IOCConfig03对象
        IOCConfig03 config03 = (IOCConfig03) context.getBean(IOCConfig03.class);
        config03.print();


    }
}

Spring MVC零配置创建与部署

​ 基于Spring Mvc 5.X 使用Maven搭建SpringMvc Web项目,通过Spring 提供的注解与相关配置来对项目进行创建与部署。

创建Spring Mvc Web工程

​ 创建Maven的web项目

pom.xml添加坐标相关配置

<dependencies>
    <!-- spring web -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <!-- spring mvc -->
    <dependency>
    <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>5.2.4.RELEASE</version>
    </dependency>
    <!-- web servlet -->
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.0.1</version>
    </dependency>
  </dependencies>
  <build>
    <finalName>springmvc5x</finalName>

    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.3.2</version>
        <configuration>
          <!-- 指定Maven编译的JDK版本和编码 -->
          <source>1.8</source>
          <target>1.8</target>
          <encoding>utf-8</encoding>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.eclipse.jetty</groupId>
        <artifactId>jetty-maven-plugin</artifactId>
        <version>9.4.27.v20200227</version>
      </plugin>
    </plugins>

  </build>

添加源代码

@Controller
public class UserController {

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

添加视图

​ 在WEB-INF/views 目录下创建index.jsp(这里以jsp为模板)

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
    <h2>Hello SpringMVC5</h2>
</body>
</html>

SpringMvc配置类添加

​ Spring Mvc 配置信息MvcConfig文件添加,作为Mvc 框架环境,原来是通过xml来进行配置(视图解析器,Json转换器,文件上传解析器等),这里基于注解通过继承WebMvcConfigurerAdapter类 并重写相关方法来进行配置(注意通过@EnableWebMvc注解来启动MVC环境)。

@Configuration//当前类是配置类
@EnableWebMvc//启用mvc环境
@ComponentScan("com.msb")//扫描包范围
public class MvcConfig extends WebMvcConfigurationSupport {

    /**
     * 配置jsp视图解析器
     */
    @Bean//方法的返回值会交给IOC容器进行实例化
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        //设置视图的前缀
        viewResolver.setPrefix("/WEB-INF/views/");
        //设置视图的后缀
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    /**
     * 如果需要使用拦截器或者其他相关的配置,则需要继承父类WebMvcConfigurerAdapter
     */
    /**
     * 设置拦截器
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        super.addInterceptors(registry);
    }
    /**
     * 设置静态资源路径
     */
    @Override
    public HandlerMapping defaultServletHandlerMapping() {
        return super.defaultServletHandlerMapping();
    }
}

​ MvcConfig 类定义好了,那么问题来了,怎么加载MvcConfig 类呢,原来在构建Mvc应用时是通过容器启动应用时加载web.xml 文件实现配置文件加载,现在的环境web.xml文件不存在,此时基于注解方式构建的Mvc 应用,定义WebInitializer 实现WebApplicationInitializer接口(该接口用来配置Servlet3.0+配置的接口,用于替代web.xml 配置),当servlet 容器启动Mvc应用时会通过SpringServletContainerInitializer接口进行加载 从而加载Mvc 应用信息配置。实现该接口onStartup方法 ,加载应用信息配置。

入口文件代码添加

/**
 * 定义类实现WebApplicationInitializer 接口
 *  当容器启动时,会自动执行onStartup 方法
 *
 *   加载springMVC的配置类
 */
public class WebInitializer implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        System.out.println("容器正在启动...");

        //基于Java配置类得到上下文环境
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        //注册mvc的配置类
        context.register(MvcConfig.class);
        //设置servletContext对象(注册servletContext上下文信息)
        context.setServletContext(servletContext);
        //设置中央控制器
        ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(context));
        //设置需要拦截的路径
        dispatcher.addMapping("/");//拦截所有的资源路径
        //设置容器启动时自动加载servlet(中央控制器)
        dispatcher.setLoadOnStartup(1);
    }
}

部署与测试

​ 通过tomcat 启动项目并访问

image-20250109110455320

此时地址访问成功。

​ 当项目访问成功后,那么问题来了,如果项目中存在静态资源文件,Handler放行处理该如何配置,定义的拦截器怎么应用,此时关注WebMvcConfigurationSupport 父类方法,重写相关方法即可。

	/**
     * 如果需要使用拦截器或者其他相关的配置,则需要继承父类WebMvcConfigurerAdapter
     */
    /**
     * 设置拦截器
     */
    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        super.addInterceptors(registry);
    }
    /**
     * 设置静态资源路径
     */
    @Override
    public HandlerMapping defaultServletHandlerMapping() {
        return super.defaultServletHandlerMapping();
    }

Spring Boot概念&特点

image-20250109110807527

框架概念

​ 随着动态语言流行(Ruby,Scala,NodeJs等),Java 开发变得相对笨重,配置繁琐,开发效率低下,部署流程复杂,以及第三方集成难度也相对较大,针对该环境,Spring Boot被开发出来,其使用“习惯大于配置目标”,借助Spring Boot 能够让项目快速运行起来,同时借助Spring Boot可以快速创建web 应用并独立进行部署(jar包 war 包方式,内嵌servlet 容器),同时借助Spring Boot 在开发应用时可以不用或很少去进行相关xml环境配置,简化了开发,大大提高项目开发效率。

​ Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,让Spring Boot在蓬勃发展的快速应用开发领域(rapid application development)成为领导者.

框架特点

​ 创建独立Spring应用程序、嵌入式Tomcat,Jetty容器、无需部署WAR包、简化Maven及Gradle配置、尽可能自动化配置Spring、直接植入产品环境下的实用功能,比如度量指标、健康检查及扩展配置、无需代码生成及XML配置等,同时Spring Boot不仅对web应用程序做了简化,还提供一系列的依赖包来把其它一些工作做成开箱即用。

Spring Boot快速入门

环境准备:Idea、Maven、Jdk 1.8+、Spring Boot 2.x

创建Maven普通项目

​ 通过Maven创建一个普通的java项目

添加依赖坐标

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.2.RELEASE</version>
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

Spring Boot的项目必须要将parent设置为Spring Boot的parent,该parent包含了大量默认的配置,简化程序的开发。Spring Boot的项目必须要将parent设置为Spring Boot的parent,该parent包含了大量默认的配置,简化程序的开发。

导入Spring Boot的web坐标与相关插件

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
</plugin>

添加源代码

@Controller
public class HelloController {
    @RequestMapping("hello")
    @ResponseBody
    public String hello(){
    	return "Hello Spring Boot";
    }
}

创建启动程序

@SpringBootApplication
public class Starter {

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

启动Spring Boot应用并测试

​ 这里运行main 方法即可 通过浏览器访问http://localhost:8080/hello 效果如下:

image-20250109111926983

Spring Boot核心配置

设置Banner图标

image-20250109112312747

​ 在搭建Spring Boot项目环境时,程序启动后会在控制台打印醒目的SpringBoot图标,图标描述了Spring Boot 版本信息,这是Spring Boot项目与Spring项目启动区别较大的地方,Spring Boot通过默认Banner在程序启动时显示应用启动图标,当然图标我们也可以进行自定义。

Banner图标自定义

​ Spring Boot项目启动时默认加载src/main/resources 目录下的banner.txt 图标文件,如果该目录文件未提供,则使用Spring Boot 默认图标打印在main 目录下新建resources 资源目录,并在该目录下新建banner.txt 文本文件。

​ 打开网址: http://patorjk.com/software/taag/#p=display&f=Graffiti&t=Type%20Something%20

​ 在线生成图标对应文本并将文本内容copy 到banner.txt 中

image-20250109112446816

​ 启动Spring Boot 应用打印如下:

image-20250109112551370

Banner图标关闭

​ 如果启动时不想要看到启动图标 ,这里也可以通过代码进行关闭操作,修改StarterApplication 设置BannerMode值为Banner.Mode.OFF,启动Spring Boot 应用关闭图标输出功能即可

@SpringBootApplication
public class Starter extends SpringBootServletInitializer {

    public static void main(String[] args) {
        SpringApplication springApplication=new SpringApplication(Starter.class);
        // 设置banner 图标关闭
        springApplication.setBannerMode(Banner.Mode.OFF);
        springApplication.run();
    }
}

Spring Boot配置文件

​ Spring Boot 默认会读取全局配置文件,配置文件名固定为:application.properties或application.yml,放置在src/main/resources资源目录下,使用配置文件来修改SpringBoot自动配置的默认值;

​ 在resources 资源目录下添加application.properties 文件,配置信息如下:

# SpringBoot的全局配置文件名必须为:application,可以是.properties 或 .yml文件,需要将配置文件设置到src/main/resources资源目录下
# 键值对,例如:键=值;配置文件中key是固定的,值根据需求自定义
## 项目启动端口号配置
server.port=8989
## 项目访问上下文路径
server.servlet-path=/mvc
## 数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/hr?useUnicode=true&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root

或者application.yml 文件

# SpringBoot的全局配置文件名必须为:application,可以是.properties 或 .yml文件,需要将配置文件设置到src/main/resources资源目录下

# 设置端口
server:
  port: 8989
  servlet:
    context-path: /sp

## 数据源配置
spring:
	datasource:
        type: com.mchange.v2.c3p0.ComboPooledDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/hr
        username: root
        password: root

Starter坐标&自动化配置

Starter坐标配置

​ Spring Boot 引入了全新的Starter坐标体系,简化企业项目开发大部分场景的Starter pom,应用程序引入指定场景的Start pom 相关配置就可以消除 ,通过Spring Boot就可以得到自动配置的Bean。

Web starter

​ 使用Spring MVC来构建RESTful Web应用,并使用Tomcat作为默认内嵌容器

    <!--web环境-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
Freemarker Starter & Thymeleaf starter

​ 集成视图技术,引入Freemarker Starter ,Thymeleaf Starter

    <!--freemarker视图-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
	<!--打war包时 忽略内置tomcat-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <scope>provided</scope>
    </dependency>
JavaMail邮件发送Starter
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-mail</artifactId>
</dependency>
引入AOP环境
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

​ 相关starter系列坐标参考

名称 描述
spring-boot-starter 核心Spring Boot starter,包括自动配置支持,日志和YAML
spring-boot-starter-actuator 生产准备的特性,用于帮我们监控和管理应用
spring-boot-starter-amqp 对”高级消息队列协议”的支持,通过spring-rabbit实现
spring-boot-starter-aop 对面向切面编程的支持,包括spring-aop和AspectJ
spring-boot-starter-batch 对Spring Batch的支持,包括HSQLDB数据库
spring-boot-starter-cloud-connectors 对Spring Cloud Connectors的支持,简化在云平台下(例如,Cloud Foundry 和Heroku)服务的连接
spring-boot-starter-data-elasticsearch 对Elasticsearch搜索和分析引擎的支持,包括spring-data-elasticsearch
spring-boot-starter-data-gemfire 对GemFire分布式数据存储的支持,包括spring-data-gemfire
spring-boot-starter-data-jpa 对”Java持久化API”的支持,包括spring-data-jpa,spring-orm和Hibernate
spring-boot-starter-data-mongodb 对MongoDB NOSQL数据库的支持,包括spring-data-mongodb
spring-boot-starter-data-rest 对通过REST暴露Spring Data仓库的支持,通过spring-data-rest-webmvc实现
spring-boot-starter-data-solr 对Apache Solr搜索平台的支持,包括spring-data-solr
spring-boot-starter-freemarker 对FreeMarker模板引擎的支持
spring-boot-starter-groovy-templates 对Groovy模板引擎的支持
spring-boot-starter-hateoas 对基于HATEOAS的RESTful服务的支持,通过spring-hateoas实现
spring-boot-starter-hornetq 对”Java消息服务API”的支持,通过HornetQ实现
spring-boot-starter-integration 对普通spring-integration模块的支持
spring-boot-starter-jdbc 对JDBC数据库的支持
spring-boot-starter-jersey 对Jersey RESTful Web服务框架的支持
spring-boot-starter-jta-atomikos 对JTA分布式事务的支持,通过Atomikos实现
spring-boot-starter-jta-bitronix 对JTA分布式事务的支持,通过Bitronix实现
spring-boot-starter-mail 对javax.mail的支持
spring-boot-starter-mobile 对spring-mobile的支持
spring-boot-starter-mustache 对Mustache模板引擎的支持
spring-boot-starter-redis 对REDIS键值数据存储的支持,包括spring-redis
spring-boot-starter-security 对spring-security的支持
spring-boot-starter-social-facebook 对spring-social-facebook的支持
spring-boot-starter-social-linkedin 对spring-social-linkedin的支持
spring-boot-starter-social-twitter 对spring-social-twitter的支持
spring-boot-starter-test 对常用测试依赖的支持,包括JUnit, Hamcrest和Mockito,还有spring-test模块
spring-boot-starter-thymeleaf 对Thymeleaf模板引擎的支持,包括和Spring的集成
spring-boot-starter-velocity 对Velocity模板引擎的支持
spring-boot-starter-web 对全栈web开发的支持, 包括Tomcat和spring-webmvc
spring-boot-starter-websocket 对WebSocket开发的支持
spring-boot-starter-ws 对Spring Web服务的支持

传统的maven坐标这里同样适用,如果引入传统maven坐标需要考虑相关配置类的编写

如果引入相关starter坐标这里不存在,可以到这里搜索。

自动化配置

SpringBoot Starter坐标版本查看

​ 前面介绍了SpringBoot Starter相关坐标,引入Starter坐标来简化应用环境的配置。这里以环境搭建spring-boot-starter-web坐标来简单分析SpringBoot 自动化配置过程。

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

​ 这里引入的web环境坐标不像传统的maven坐标那样包含坐标的版本号,项目中引入的starter 系列坐标对应的版本库统一由父工程坐标统一控制即项目中引入的parent标签。

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <!--
    父类项目统一对项目依赖版本统一控制!
    -->
    <version>2.2.2.RELEASE</version>
</parent>

​ 这里spring-boot-starter-parent 继承spring-boot-dependencies 项目,在spring-boot-dependencies项目中定义了spring-boot-starter-web坐标的版本!(spring-boot-dependencies 项目中定义了当前SpringBoot版本下各个starter 坐标版本以及依赖的其他坐标版本)

image-20250109130209916

image-20250109130310394

​ 这里依赖的坐标较多,不在截图展示,可关注spring-boot-dependencies-2.2.2.RELEASE.pom文件。

Spring Boot自动化配置

​ Spring Boot的项目一般都会有*Application的入口类,入口类中提供main方法,这是一个标准的Java应用程序的入口方法。@SpringBootApplication注解是Spring Boot的核心注解,它其实是一个组合注解:

  • @SpringBootApplication

image-20250109130625366

​ 可以看出该注解也是一个组合注解,组合了@Configuration注解,对于Spring Boot应用,@SpringBootConfiguration 注解属于Boot 项目的配置注解也是属于一个组合注解,Spring Boot 项目中推荐使用@SpringBootConfiguration注解,因为其组合了@Configuration注解。

  • @EnableAutoConfiguration

image-20250109130856762

@EnableAutoConfiguration注解组合了@AutoConfigurationPackage、 @Import(AutoConfigurationImportSelector.class) 注解。

@AutoConfigurationPackage底层也是一个@Import(AutoConfigurationPackages.Registrar.class),其会把启动类的包下组件都扫描到Spring容器中。

image-20250109131143536

​ @Import(AutoConfigurationImportSelector.class) 自动配置的核心类AutoConfigurationImportSelector.class,该类导入大量的自动配置类,debug可以发现,其读取的是classpath下的META-INF/spring.factories下配置文件。

image-20250109140500800

​ 以WebMvcAutoConfiguration为例,可以看出该类使用@Configuration 注解进行标注其为一个配置类。

image-20250109140929519

​ 当然spring.factories 文件中配置类默认不会都生效,具体哪些配置类生效由配置类上标注的@ConditionalOnClass 注解来决定,这里了解下@ConditionalOnClass 注解含义

@ConditionalOnBean // 当给定的在bean存在时,则实例化当前Bean
@ConditionalOnMissingBean // 当给定的在bean不存在时,则实例化当前Bean
@ConditionalOnClass // 当给定的类名在类路径上存在,则实例化当前Bean
@ConditionalOnMissingClass // 当给定的类名在类路径上不存在,则实例化当前Bean

​ 意味着WebMvcAutoConfiguration 配置类生效需要环境中存在Servlet.class,DispatcherServlet.class,WebMvcConfigurer.class实例,配置类才会生效。

从以上分析可以得出如下结论:

	Spring Boot通过maven中的starter导入了所需场景下的jar包,并通过主启动类上的@SpringBootApplication中的@EnableAutoConfiguration读取了类路径下的META-INF/spring.factories下EnableAutoConfiguration的配置类,这些配置类使用@ConditionalOnClass来标注,根据@ConditionalOnClass标注的约束条件来引入自动化的环境配置。

Profile配置

​ Profile 是Spring 用来针对不同环境对不同配置提供支持的全局Profile配置使用application-{profile}.yml,比如application-dev.yml ,application-test.yml。

​ 通过在application.yml中设置spring.profiles.active=test|dev|prod 来动态切换不同环境,具体配置如下:

  • application-dev.yml 开发环境配置文件

    server:
    	port: 8989
    
  • application-test.yml 测试环境配置文件

    server:
    	port: 9999
    
  • application-prod.yml 生产环境配置文件

    server:
    	port: 8686
    
  • application.yml 主配置文件

    ## 环境选择配置
    spring:
        profiles:
        	active: dev
    

启动Starter 查看控制台输入效果

image-20250109142511305

修改application.yml 设置active 值为prod

## 环境选择配置
spring:
	profiles:
		active: prod

启动Starter 再次查看控制台输入效果

image-20250109142534765

日志配置

​ 在开发企业项目时,日志的输出对于系统bug 定位无疑是一种比较有效的方式,也是项目后续进入生产环境后快速发现错误解决错误的一种有效手段,所以日志的使用对于项目也是比较重要的一块功能。

​ Spring Boot默认使用LogBack日志系统,如果不需要更改为其他日志系统如Log4j2等,则无需多余的配置,LogBack默认将日志打印到控制台上。如果要使用LogBack,原则上是需要添加dependency依赖的

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

image-20250109142903218

​ 因为新建的Spring Boot项目一般都会引用 spring-boot-starter 或者 spring-boot-starter-web ,而这两个起步依赖中都已经包含了对于 spring-boot-starter-logging 的依赖,所以,无需额外添加依赖。

项目中日志信息输出

​ Starter 启动类中添加Log 日志类,控制台打印日志信息。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Starter {
	private static Logger logger = LoggerFactory.getLogger(Starter.class);
	public static void main(String[] args) {
		logger.info("SpringBoot 应用开始启动...");
		SpringApplication.run(Starter.class);
	}
}

image-20250109143058122

日志输出格式配置

​ 修改application.yml文件添日志输出格式信息配置,可以修改application.yml文件来控制控制台日志输出格式,同时可以设置日志信息输出到外部文件。

# 设置日志输出格式
logging:
  pattern:
    console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger- %msg%n"
    level: debug
  file:
    path: "."
    name: "springboot.log"

image-20250109143351366

更多日志输出,参考官网

Freemarker & Thymeleaf视图技术集成

Freemarker 视图集成

​ SpringBoot内部支持Freemarker 视图技术的集成,并提供了自动化配置类FreeMarkerAutoConfiguration,借助自动化配置可以很方便的集成Freemarker基础到SpringBoot环境中。这里借助入门项目引入Freemarker环境配置。

  • starter坐标引入

    <!--freemarker视图-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-freemarker</artifactId>
    </dependency>
    
  • 添加Freemarker 配置信息

    Freemarker 默认默认视图路径文 resources/templates 目录(由自动化配置类

    FreemarkerProperties 类决定),该目录可以进行在application.yml 中进行修改。

    image-20250109145423522

    修改application.yml 添加freemarker 基本配置如下:

      # freemarker配置
      freemarker:
        # freemarker 默认的加载路径(resource目录下的views目录中)
        template-loader-path: classpath:/views/
        # 设置视图的后缀
        suffix: .ftl
        # 设置响应类型(默认text/html)
        content-type: text/html
        # 设置编码格式
        charset: UTF-8
    
  • 编写HelloController控制器转发视图

    @Controller
    public class HelloController {
    
        @RequestMapping("hello")
        @ResponseBody
        public String hello(){
            return "Hello Spring Boot";
        }
    
        @RequestMapping("index")
        public String index(HttpServletRequest request){
            //设置请求域
            request.setAttribute("msg", "Hello Spring Boot");
            //返回视图
            return "index";
        }
    }
    
  • views目录下添加index.ftl视图

    <h2>FreeMarker视图</h2>
    <h3>${msg}</h3>
    

  • 启动Starter访问

image-20250109150242338

Thymeleaf视图集成

​ SpringBoot 支持多种视图技术集成,并且SpringBoot 官网推荐使用Thymeleaf 作为前端视图页面,这里实现Thymeleaf 视图集成,借助入门项目引入Thymeleaf 环境配置。

  • starter坐标引入

        <!--thymeleaf视图-->
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
    
  • 添加Thymeleaf 配置信息

​ Thymeleaf 默认默认视图路径文 resources/templates 目录(由自动化配置类ThymeleafProperties 类决定),该目录可以进行在application.yml 中进行修改。

image-20250109150736418

  # thymeleaf 配置
  thymeleaf:
    # thymeleaf 默认的加载路径(resource目录下的html目录中)
    prefix: classpath:/html/
    ## 关闭thymeleaf 页面缓存
    cache: false
  • 编写HelloController控制器转发视图

    @Controller
    public class HelloController {
    
        @RequestMapping("hello")
        @ResponseBody
        public String hello(){
            return "Hello Spring Boot";
        }
    
        @RequestMapping("index")
        public String index(HttpServletRequest request){
            //设置请求域
            request.setAttribute("msg", "Hello Spring Boot");
            //返回视图
            return "index";
        }
    }
    
  • html目录下添加index.html视图

​ 修改Thymeleaf模板默认存放路径(在resources 目录下创建html文件夹)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1 th:text="${msg}"></h1>
</body>
</html>
  • 启动Starter访问

    image-20250109151057548

SpringBoot静态资源访问

​ 从入门项目中可以看到:对于Spring Mvc 请求拦截规则为‘/’,Spring Boot 默认静态资源路径(由自动化配置类ResourceProperties类决定)如下:

image-20250109151527237

即:我们可以在resources 资源目录下存放web 应用静态资源文件。

默认静态资源路径

​ 在resources 目录下创建static 或者public 存放images、js、css等静态资源文件

image-20250109152006178

浏览器访问:

image-20250109152117033

image-20250109152222268

image-20250109152238060

自定义静态资源路径

​ 在spring.resources.static-location后面追加一个配置classpath:/os/

spring:
 #自定义静态资源的路径("classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/")
  resources:
    # 修改默认的静态资源目录,多个路径之间用逗号隔开
    static-locations: classpath:/static/,classpath:/public/,classpath:/os/

SpringBoot应用打包与部署

​ 当项目开发完毕进行部署上线时,需要对项目进行打包操,入门中构建的项目属于普通应用,由于SpringBoot内嵌Tomcat容器,所有打包后的jar包默认可以自行运行。

Jar包部署

配置打包命令

​ idea 下配置clean compile package -Dmaven.test.skip=true 执行打包命令,target目录得到待部署的项目文件。

image-20250109152732981

image-20250109152911000

部署并访问

​ 打开本地dos 窗口,执行java -jar 命令 部署已打好的jar包文件

​ 命令如下:java -jar jar包所在目录

image-20250109153118381

image-20250109153157332

浏览器访问

image-20250109153306387

war包部署

​ War 包形式部署Web 项目在生产环境中是比较常见的部署方式,也是目前大多数web 应用部署的方案,这里对于Spring Boot Web 项目进行打包部署步骤如下

pom.xml修改

  • 应用类型修改

​ 由于入门项目构建的项目默认为jar应用,所以这里打war需要作如下修改

image-20250109153616532

  • 内嵌tomcat忽略

​ 构建SPringBoot应用时,引入的spring-boot-starter-web默认引入tomcat容器,这里忽略掉内容

tomcat

    <!--打war包时 忽略内置tomcat-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-tomcat</artifactId>
      <scope>provided</scope>
    </dependency>

 <build>
    <!--打war包,war包的名字-->
    <finalName>springboot</finalName>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>

Starter修改

​ 添加容器启动加载文件(类似于读取web.xml),这里通过继承SpringBootServletInitializer 类并重写configure方法来实现,在部署项目时指定外部tomcat 读取项目入口方法。

@SpringBootApplication
public class Starter extends SpringBootServletInitializer {

    //使用日志
    private static org.slf4j.Logger Logger = LoggerFactory.getLogger(Starter.class);

    public static void main(String[] args) {
        SpringApplication.run(Starter.class);
        Logger.info("启用应用...");
    
    }

    /**
     * 添加容器启动加载文件(类似于读取web.xml)
     * 通过继承SpringBootServletInitializer 类并重写configure方法来实现
     * 在部署项目时指定外部tomcat 读取项目入口方法。
     **/
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(Starter.class);
    }
}

打包操作

部署并访问

  • 外部tomcat部署并访问

image-20250109154239547

image-20250109154416966

浏览器访问:

image-20250109154459878

Mybatis整合&数据访问

​ 使用SpringBoot开发企业项目时,持久层数据访问是前端页面数据展示的基础,SpringBoot支持市面上常见的关系库产品(Oracle,Mysql,SqlServer,DB2等)对应的相关持久层框架,当然除了对于关系库访问的支持,也支持当下众多的非关系库(Redis,Solr,MongoDB等)数据访问操作,这里主要介绍SpringBoot集成Mybatis并实现持久层数据基本增删改查操作。

SpringBoot整合Mybatis

环境整合配置

  • Idea 下创建Maven 普通工程 springboot_mybatis

    image-20250109160051845

  • pom.xml 添加核心依赖

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
      </properties>
    
      <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2.RELEASE</version>
      </parent>
      <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--
        mybatis 集成
        -->
        <dependency>
          <groupId>org.mybatis.spring.boot</groupId>
          <artifactId>mybatis-spring-boot-starter</artifactId>
          <version>2.1.1</version>
        </dependency>
        <!-- springboot分页插件 -->
        <dependency>
          <groupId>com.github.pagehelper</groupId>
          <artifactId>pagehelper-spring-boot-starter</artifactId>
          <version>1.2.13</version>
        </dependency>
        <!--mysql 驱动-->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <!--c3po数据源-->
        <dependency>
          <groupId>com.mchange</groupId>
          <artifactId>c3p0</artifactId>
          <version>0.9.5.5</version>
        </dependency>
    
      </dependencies>
    
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
          </plugin>
    
        </plugins>
      </build>
    
  • application.yml 整合配置

    ## 端口号
    server:
      # 设置端口
      port: 8080
      # 设置访问路径
      servlet:
        context-path: /springboot_mybatis
    
    ## 数据源配置
    spring:
      datasource:
        type: com.mchange.v2.c3p0.ComboPooledDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/springboot_mybatis?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8
        username: root
        password: 123456
    
    ## mybatis 配置
    mybatis:
      # 映射文件的路径(resource目录下的mappers目录)
      mapper-locations: classpath:/mappers/*.xml
      type-aliases-package: com.msb.springboot.po
      configuration:
        ## 下划线转驼峰配置
        map-underscore-to-camel-case: true
    
    ## pageHelper
    pagehelper:
      helper-dialect: mysql
    
    #显示dao 执行sql语句
    logging:
      level:
        com:
          msb:
            springboot:
              dao: debug
    

源代码添加

  • JavaBean对象定义

    package com.msb.springboot.po;
    
    public class User{
    
        private Integer id;//用户id 
        private String userName;//用户名
        private String userPwd;//用户密码
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getUserName() {
            return userName;
        }
    
        public void setUserName(String userName) {
            this.userName = userName;
        }
    
        public String getUserPwd() {
            return userPwd;
        }
    
        public void setUserPwd(String userPwd) {
            this.userPwd = userPwd;
        }
    
        public @Email String getEmail() {
            return email;
        }
    
        public void setEmail(@Email String email) {
            this.email = email;
        }
    }
    
  • Dao层接口方法定义

    com.msb.springboot.dao 包下创建UserDao.java 接口声明查询方法

    package com.msb.springboot.dao;
    
    import com.msb.springboot.po.User;
    
    import java.util.List;
    
    public interface UserMapper {
        //通过用户名查询用户对象
        User queryUserByName(String userName);
    
    }
    
  • SQL映射文件添加

    resources/mappers 目录下添加UserMapper.xml 配置查询statetment

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
    <mapper namespace="com.msb.springboot.dao.UserMapper">
        <!--通过用户名查询用户-->
        <select id="queryUserByName" parameterType="String" resultType="com.msb.springboot.po.User">
            select * from t_user where user_name=#{userName}
        </select>
    
    </mapper>
    
  • 添加service 、controller 对应代码

    UserService.java

    @Service
    public class UserService {
        @Autowired
        private UserMapper userMapper;
    
        /**
         * 通过用户名查询用户对象
         * @param userName
         * @return
         */
        public User queryUserByName(String userName){
            return userMapper.queryUserByName(userName);
        }
    
    
    }
    

    UserController.java

    @RestController
    public class UserController {
        @Resource
        private UserService userService;
        @GetMapping("user/{userName}")
        public User queryUserByUserName(@PathVariable String userName){
        	return userService.queryUserByUserName(userName);
        }
    }
    
  • 添加应用启动入口

    @SpringBootApplication
    @MapperScan("com.msb.springboot.dao")
    public class Starter {
        public static void main(String[] args) {
            SpringApplication.run(Starter.class);
        }
    }
    

启动测试

​ 运行Starter main方法,启动应用浏览器测试查询

image-20250109161538347

​ 后端日志打印效果:

image-20250109161811420

SpringBoot数据访问操作

​ 完成SpringBoot 与Mybatis 集成后,接下来以用户表为例实现一套用户模块基本数据维护。

查询操作

接口方法定义

​ UserMapper 接口添加数据访问基本方法

public interface UserMapper {
    
    //通过用户id查询用户对象
    User queryUserById(Integer id);

}
映射文件配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.msb.springboot.dao.UserMapper">

    <!--通过用户id查询用户-->
    <select id="queryUserById" parameterType="int" resultType="com.msb.springboot.po.User">
        select * from t_user where id=#{id}
    </select>

</mapper>
UserService
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    /**
     * 通过用户id查询用户对象
     * @param id
     * @return
     */
    public User queryUserById(Integer id){
        return userMapper.queryUserById(id);
    }

}
UserController
//@Controller
@RestController//如果类上设置了该注解,表示该类中所有的方法返回的都是(json)格式的字符串
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 通过用户id查询用户对象
     * @param id
     * @return
     */
    @GetMapping("/user/{id}")
    public User queryUserById(@PathVariable Integer id){
        return userService.queryUserById(id);
    }

}

添加操作

接口方法定义
public interface UserMapper {
   
    //添加用户
    int addUser(User user);

}
映射文件配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.msb.springboot.dao.UserMapper">

    <!--添加用户-->
    <insert id="addUser" parameterType="com.msb.springboot.po.User">
        insert into
            t_user(user_name,user_pwd)
        values
            (#{userName},#{userPwd})
    </insert>

</mapper>
添加commons-lang依赖
 <!--commons-lang3工具包-->
    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
    </dependency>
AssertUtil工具类
package com.msb.springboot.util;

import com.msb.springboot.exception.ParamsException;
import com.mysql.cj.exceptions.SSLParamsException;

public class AssertUtil {

    /**
     * 数据校验
     *      满足条件,则抛出异常
     * @param flag
     * @param msg
     */
    public static void isTrue(boolean flag, String msg) {
        //判断flag的值是否为空,如果为true,则抛出异常·
        if (flag) {
            throw new ParamsException(msg);
        }
    }
}
ParamsException自定义异常
package com.msb.springboot.exception;

public class ParamsException extends RuntimeException {
    private Integer code = 500;
    private String msg="参数异常";

    public ParamsException(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ParamsException(){}

    public ParamsException(String msg){
        this.msg = msg;
    }


    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}
UserService
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    /**
     * 添加用户
     *      1.用户名非空校验
     *      2.密码非空
     *      3.用户名唯一
     *      4.添加后返回受影响行数大于0
     * @param user
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void addUser(User user){
        //用户名 非空判断
        AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()),"用户名称不能为空");
        //用户密码 非空判断
        AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空");
        //通过用户名查询用户对象
        User temp = userMapper.queryUserByName(user.getUserName());
        //判断用户名唯一
        AssertUtil.isTrue(null!=temp,"用户已存在,请重新输入");
        //执行添加操作,判断受影响的行数
        AssertUtil.isTrue(userMapper.addUser(user)!=1,"用户添加失败");
    }

}
ResultInfo
package com.msb.springboot.model;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

/**
 * 用来封装返回的数据
 */
@ApiModel(value = "结果封装对象")
public class ResultInfo {
    @ApiModelProperty(value = "状态码",example = "200")
    private Integer code=200;//状态码 默认 200=成功 500=失败
    @ApiModelProperty(value = "提示信息")
    private String msg="操作成功";//提示信息,默认提示 操作成功
    @ApiModelProperty(value = "返回对象")
    private Object result;//返回结果

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }
}
UserController
//@Controller
@RestController//如果类上设置了该注解,表示该类中所有的方法返回的都是(json)格式的字符串
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 添加用户
     * @param user
     * @return
     */
    @PostMapping("user")
    public ResultInfo addUser(@RequestBody User user){
        ResultInfo resultInfo = new ResultInfo();

        //调用service层的添加方法
        userService.addUser(user);

        try{
            //调用service层的添加方法
            userService.addUser(user);
        }catch (ParamsException p){
            resultInfo.setCode(p.getCode());
            resultInfo.setMsg(p.getMsg());
        } catch (Exception e){
            resultInfo.setCode(500);
            resultInfo.setMsg("用户添加失败");
        }

        return resultInfo;
    }

}

修改操作

接口方法定义
public interface UserMapper {
    
    //修改用户
    int updateUser(User user);

}
映射文件配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.msb.springboot.dao.UserMapper">

    <!--修改用户-->
    <update id="updateUser" parameterType="com.msb.springboot.po.User">
        update
            t_user
        set
            user_name=#{userName},user_pwd=#{userPwd}
        where
            id = #{id}
    </update>

</mapper>
UserService
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    /**
     * 修改用户
     *      1.用户名非空校验
     *      2.密码非空
     *      3.用户名唯一
     *          通过用户名查询用户对象,用户对象不存在 或 用户对象是当前修改记录本身
     *      4.修改后返回受影响行数大于0
     * @param user
     */
    public User updateUser(User user){
        //用户名 非空判断
        AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()),"用户名称不能为空");
        //用户密码 非空判断
        AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空");
        //通过用户名查询用户对象
        User temp = userMapper.queryUserByName(user.getUserName());
        //如果用户对象不为空且用户id不一致
        AssertUtil.isTrue(null!=temp && !(user.getId().equals(temp.getId())),"用户名已存在,请重新输入");
        //执行修改操作,判断受影响的行数
        AssertUtil.isTrue(userMapper.updateUser(user)!=1,"用户添加失败");

        return user;
    }

}
UserController
//@Controller
@RestController//如果类上设置了该注解,表示该类中所有的方法返回的都是(json)格式的字符串
public class UserController {

    @Autowired
    private UserService userService;

/**
     * 修改用户
     * @param user
     * @return
     */
    @PutMapping("user")
    public ResultInfo updateUser(@RequestBody User user){
        ResultInfo resultInfo = new ResultInfo();

        //调用service层的添加方法
        userService.updateUser(user);
        try{
            //调用service层的添加方法
            userService.updateUser(user);
        }catch (ParamsException p){
            resultInfo.setCode(p.getCode());
            resultInfo.setMsg(p.getMsg());
        } catch (Exception e){
            resultInfo.setCode(500);
            resultInfo.setMsg("用户修改失败");
            e.printStackTrace();
        }

        return resultInfo;
    }

}

删除操作

接口方法定义
public interface UserMapper {
    
    //删除用户
    int deleteUser(Integer id);

}
映射文件配置
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.msb.springboot.dao.UserMapper">

    <!--删除用户-->
    <delete id="deleteUser" parameterType="int">
        delete  from t_user where id = #{id}
    </delete>


</mapper>
UserService
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

     /**
     * 删除用户
     *      1. id是否为空且用户对象存在
     * @param id
     */
    @Transactional(propagation = Propagation.REQUIRED)
    @CacheEvict(value = "users",key = "#id")
    public void deleteUser(Integer id){
        //判断id是否为空且用户对象存在
        AssertUtil.isTrue(null==id &&userMapper.queryUserById(id)==null,"待删除记录不存在");
        //执行删除操作,判断受影响的行数
        AssertUtil.isTrue(userMapper.deleteUser(id)!=1,"用户删除失败");
    }

}
UserController
//@Controller
@RestController//如果类上设置了该注解,表示该类中所有的方法返回的都是(json)格式的字符串
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 删除用户
     * @param userId
     * @return
     */
    @DeleteMapping("user/{userId}")
    public ResultInfo deleteUser(@PathVariable Integer userId){
        ResultInfo resultInfo = new ResultInfo();

        //调用service层的添加方法
        userService.deleteUser(userId);

        try{
            //调用service层的添加方法
            userService.deleteUser(userId);
        }catch (ParamsException p){
            resultInfo.setCode(p.getCode());
            resultInfo.setMsg(p.getMsg());
        } catch (Exception e){
            resultInfo.setCode(500);
            resultInfo.setMsg("用户删除失败");
        }

        return resultInfo;
    }

}

分页条件查询操作

UserQuery
package com.msb.springboot.query;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;

@ApiModel(value = "用户查询对象")
public class UserQuery {
    /* 分页参数 */
    @ApiModelProperty(value = "当前页",example = "1")
    private Integer pageNum=1;//当前页
    @ApiModelProperty(value = "每页显示的记录数",example = "10")
    private Integer pageSize=10;//每页显示的记录数

    /* 条件参数 */
    @ApiModelProperty(value = "用户名")
    private String userName;//查询条件:用户名

    public Integer getPageNum() {
        return pageNum;
    }

    public void setPageNum(Integer pageNum) {
        this.pageNum = pageNum;
    }

    public Integer getPageSize() {
        return pageSize;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }
}
接口方法定义
public interface UserMapper {
   
    //分页查询
    public List<User> selectByParams(UserQuery userQuery);

}
映射文件配置
    <!--条件查询用户列表-->
    <select id="selectByParams" parameterType="com.msb.springboot.query.UserQuery" resultType="com.msb.springboot.po.User">
        select
            *
        from
            t_user
        <where>
            <if test="null !=userName and userName !=''">
                and user_name like concat('%',#{userName},'%')
            </if>
        </where>
    </select>
UserService
@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    /**
     * 分页条件查询用户列表
     * @param userQuery
     * @return
     */
    @Cacheable(value = "users",key = "#userQuery.userName+'-'+#userQuery.pageNum+'-'+#userQuery.pageSize")
    public PageInfo<User> selectByParams(UserQuery userQuery){
        //开启分页
        PageHelper.startPage(userQuery.getPageNum(),userQuery.getPageSize());
        //返回分页结果
        return new PageInfo<User>(userMapper.selectByParams(userQuery));
    }

}
UserController
//@Controller
@RestController//如果类上设置了该注解,表示该类中所有的方法返回的都是(json)格式的字符串
public class UserController {

    @Autowired
    private UserService userService;

	/**
     * 分页条件查询用户列表
     * @param userQuery
     * @return
     */
    @GetMapping("user/list")
    public PageInfo<User> selectByParams(UserQuery userQuery){
        return userService.selectByParams(userQuery);
    }

}

API文档构建工具-Swagger2

​ 由于Spring Boot能够快速开发、便捷部署等特性,通常在使用Spring Boot构建Restful 接口应用时考虑到多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web前端。对于不同的终端公用一套接口API时对于联调测试时就需要知道后端提供的接口Api 列表文档,对于服务端开发人员来说就需要编写接口文档,描述接口调用地址参数结果等,这里借助第三方构建工具Swagger2来实现Api文档生成功能。

环境整合配置

  • pom.xml 依赖添加

        <!--swagger2 文档生成工具-->
        <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger2</artifactId>
          <version>2.9.2</version>
        </dependency>
        <dependency>
          <groupId>io.springfox</groupId>
          <artifactId>springfox-swagger-ui</artifactId>
          <version>2.9.2</version>
        </dependency>
    
  • 配置类添加

    package com.msb.springboot.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    @Configuration
    @EnableSwagger2
    public class Swagger2 {
        @Bean
        public Docket createRestApi() {
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    .apis(RequestHandlerSelectors.basePackage("com.msb.springboot.controller"))
                    .paths(PathSelectors.any())
                    .build();
        }
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("用户管理接口API文档参考")
                    .version("1.0")
                    .build();
        }
    }
    

Swagger2常用注解说明

@Api

@Api用在请求的类上说明该类的作用
	tags="说明该类的作用"
@Api(tags="APP用户注册Controller")

@ApiOperation

@ApiOperation"用在请求的方法上,说明方法的作用"
    value="说明方法的作用"
    notes="方法的备注说明"
@ApiOperation(value="用户注册",notes="手机号、密码都是必输项,年龄随边填,但必须是数字")

@ApiImplicitParams

@ApiImplicitParams用在请求的方法上包含一组参数说明
	@ApiImplicitParam用在 @ApiImplicitParams 注解中指定一个请求参数的配置信息
        name参数名
        value参数的汉字说明解释
        required参数是否必须传
        paramType参数放在哪个地方
            · header --> 请求参数的获取@RequestHeader
            · query --> 请求参数的获取@RequestParam
            · path用于restful接口-->请求参数的获取:@PathVariable
            · body不常用
            · form不常用
        dataType参数类型默认String其它值dataType="Integer"
        defaultValue参数的默认值
@ApiImplicitParams({
    @ApiImplicitParam(name="mobile",value="手机号",required=true,paramType="form"),
    @ApiImplicitParam(name="password",value="密码",required=true,paramType="form"),
    @ApiImplicitParam(name="age",value="年龄",required=true,paramType="form",dataType="Integer")
})

@ApiResponses

@ApiResponses用于请求的方法上表示一组响应
	@ApiResponse用在@ApiResponses中一般用于表达一个错误的响应信息
        code数字例如400
        message信息例如"请求参数没填好"
        response抛出异常的类
@ApiOperation(value = "select请求",notes = "多个参数,多种的查询参数类型")
    @ApiResponses({
        @ApiResponse(code=400,message="请求参数没填好"),
        @ApiResponse(code=404,message="请求路径没有或页面跳转路径不对")
})

@ApiModel

@ApiModel用于响应类上表示一个返回响应数据的信息这种一般用在post创建的时候使用@RequestBody这样的场景请求参数无法使用@ApiImplicitParam注解进行描述的时候
	@ApiModelProperty用在属性上描述响应类的属性
@ApiModel(description= "返回响应数据")
public class RestMessage implements Serializable{
    @ApiModelProperty(value = "是否成功")
    private boolean success=true;
    @ApiModelProperty(value = "返回对象")
    private Object data;
    @ApiModelProperty(value = "错误编号")
    private Integer errCode;
    @ApiModelProperty(value = "错误信息")
    private String message;
    /* getter/setter */
}

用户模块注解配置

UserController使用注解

	 /**
     * 通过用户名查询用户对象
     * @param userName
     * @return
     */
    @GetMapping("/user/name/{userName}")
    @ApiOperation(value = "通过用户名称查询用户对象")
    @ApiImplicitParam(name = "userName",value = "用户名称",required = true,paramType = "path")
    public User getUserService(@PathVariable String userName) {
        System.out.println("用户查询...test121123");
        return userService.queryUserByName(userName);
    }

    /**
     * 通过用户id查询用户对象
     * @param id
     * @return
     */
    @GetMapping("/user/{id}")
    @ApiOperation(value = "通过用户id查询用户对象")
    @ApiImplicitParam(name = "id",value = "用户id",required = true,paramType = "path")
    public User queryUserById(@PathVariable Integer id){
        return userService.queryUserById(id);
    }

    /**
     * 添加用户
     * @param user
     * @return
     */
    @PostMapping("user")
    @ApiOperation(value = "添加用户")
    @ApiImplicitParam(name = "user", value = "用户实体类")
    public ResultInfo addUser(@RequestBody User user){
        ResultInfo resultInfo = new ResultInfo();

        //调用service层的添加方法
        userService.addUser(user);

        /*try{
            //调用service层的添加方法
            userService.addUser(user);
        }catch (ParamsException p){
            resultInfo.setCode(p.getCode());
            resultInfo.setMsg(p.getMsg());
        } catch (Exception e){
            resultInfo.setCode(500);
            resultInfo.setMsg("用户添加失败");
        }*/

        return resultInfo;
    }


    /**
     * 添加用户
     * @param user
     * @return
     */
    @PostMapping("user02")
    @ApiOperation(value = "添加用户")
    @ApiImplicitParam(name = "user", value = "用户实体类")
    public ResultInfo addUser02(@Valid User user){
        ResultInfo resultInfo = new ResultInfo();

        //调用service层的添加方法
        userService.addUser(user);

        return resultInfo;
    }

    /**
     * 修改用户
     * @param user
     * @return
     */
    @PutMapping("user")
    @ApiOperation(value = "修改用户")
    @ApiImplicitParam(name = "user",value = "用户实体类")
    public ResultInfo updateUser(@RequestBody User user){
        ResultInfo resultInfo = new ResultInfo();

        //调用service层的添加方法
        userService.updateUser(user);
        /*try{
            //调用service层的添加方法
            userService.updateUser(user);
        }catch (ParamsException p){
            resultInfo.setCode(p.getCode());
            resultInfo.setMsg(p.getMsg());
        } catch (Exception e){
            resultInfo.setCode(500);
            resultInfo.setMsg("用户修改失败");
            e.printStackTrace();
        }*/

        return resultInfo;
    }

    /**
     * 删除用户
     * @param userId
     * @return
     */
    @DeleteMapping("user/{userId}")
    @ApiOperation(value = "删除用户")
    @ApiImplicitParam(name = "userId",value = "用户ID",required = true,paramType = "path")
    public ResultInfo deleteUser(@PathVariable Integer userId){
        ResultInfo resultInfo = new ResultInfo();

        //调用service层的添加方法
        userService.deleteUser(userId);

        /*try{
            //调用service层的添加方法
            userService.deleteUser(userId);
        }catch (ParamsException p){
            resultInfo.setCode(p.getCode());
            resultInfo.setMsg(p.getMsg());
        } catch (Exception e){
            resultInfo.setCode(500);
            resultInfo.setMsg("用户删除失败");
        }*/

        return resultInfo;
    }

    /**
     * 分页条件查询用户列表
     * @param userQuery
     * @return
     */
    @GetMapping("user/list")
    @ApiOperation(value = "分页条件查询用户列表")
    @ApiImplicitParam(name = "userQuery",value = "用户查询类")
    public PageInfo<User> selectByParams(UserQuery userQuery){
        return userService.selectByParams(userQuery);
    }

JavaBean的注解使用

  • User.java
@ApiModel(value = "用户实体类")
public class User implements Serializable {
    @ApiModelProperty(name="id",value = "用户id",example = "0")
    private Integer id;//用户id
    @ApiModelProperty(value = "用户名称")
    private String userName;//用户名
    @ApiModelProperty(value = "用户密码")
    private String userPwd;//用户密码

	/*
		省略get|set
	*/

}
  • UserQuery.java

    @ApiModel(value = "用户查询对象")
    public class UserQuery {
        /* 分页参数 */
        @ApiModelProperty(value = "当前页",example = "1")
        private Integer pageNum=1;//当前页
        @ApiModelProperty(value = "每页显示的记录数",example = "10")
        private Integer pageSize=10;//每页显示的记录数
    
        /* 条件参数 */
        @ApiModelProperty(value = "用户名")
        private String userName;//查询条件:用户名
    
    	/*
    		省略get|set
    	*/
    }
    
  • ResultInfo.java

    /**
     * 用来封装返回的数据
     */
    @ApiModel(value = "结果封装对象")
    public class ResultInfo {
        @ApiModelProperty(value = "状态码",example = "200")
        private Integer code=200;//状态码 默认 200=成功 500=失败
        @ApiModelProperty(value = "提示信息")
        private String msg="操作成功";//提示信息,默认提示 操作成功
        @ApiModelProperty(value = "返回对象")
        private Object result;//返回结果
    
        /*
    		省略get|set
    	*/
    }
    

Swagger2接口文档访问

启动工程,浏览器访问:http://localhost:9999/swagger-ui.html

image-20250109172211705

SpringBoot应用热部署

什么是热部署?

​ 热部署,就是在应用正在运行的时候升级软件(增加业务/修改bug),却不需要重新启动应用。

​ 大家都知道在项目开发过程中,常常会改动页面数据或者修改数据结构,为了显示改动效果,往往需要重启应用查看改变效果,其实就是重新编译生成了新的 Class 文件,这个文件里记录着和代码等对应的各种信息,然后 Class 文件将被虚拟机的 ClassLoader 加载。

​ 而热部署正是利用了这个特点,它监听到如果有 Class 文件改动了,就会创建一个新的ClaassLoader 进行加载该文件,经过一系列的过程,最终将结果呈现在我们眼前,Spring Boot通过配置DevTools 工具来达到热部署效果。

在原理上是使用了两个ClassLoader,一个Classloader加载那些不会改变的类(第三方Jar包),另一个ClassLoader加载会更改的类,称为restart ClassLoader,这样在有代码更改的时候,原来的restartClassLoader 被丢弃,重新创建一个restart ClassLoader,由于需要加载的类相比较少,所以实现了较快的重启时间。

热部署环境配置与测试

配置DevTools环境

  • 修改 Pom 文件,添加 DevTools 依赖

    	<!-- DevTools 的坐标 -->
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-devtools</artifactId>
          <!--当前这个项目被继承之后,这个不向下传递-->
          <optional>true</optional>
        </dependency>
    
  • 同时在plugin中添加devtools生效标志

          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
              <fork>true</fork><!-- 如果没有该配置,热部署的devtools不生效 -->
            </configuration>
          </plugin>
    

​ devtools可以实现页面热部署(即页面修改后会立即生效,这个可以直接在application.properties文件中配置spring.thymeleaf.cache=false来实现),实现类文件热部署(类文件修改后不会立即生效),实现对属性文件的热部署。即devtools会监听classpath下的文件变动,并且会立即重启应用(发生在保存时机),注意:因为其采用的虚拟机机制,该项重启是很快的。 配置了后在修改java文件后也就支持了热启动,不过这种方式是属于项目重启(速度比较快的项目重启),会清空session中的值,也就是如果有用户登陆的话,项目重启后需要重新登陆。

​ 默认情况下,/META-INF/maven,/META-INF/resources,/resources,/static,/templates,/public这些文件夹下的文件修改不会使应用重启,但是会重新加载(devtools内嵌了一个LiveReload server,当资源发生改变时,浏览器刷新)

全局配置文件配置

在application.yml中配置spring.devtools.restart.enabled=false,此时restart类加载器还会初始化,但不会监视文件更新。

spring:
  ## 热部署配置
  devtools:
    restart:
      enabled: true
      # 设置重启的目录,添加目录的文件需要restart
      additional-paths: src/main/java
      # 解决项目自动重新编译后接口报404的问题
      poll-interval: 3000
      quiet-period: 1000

Idea配置

​ 当我们修改了Java类后,IDEA默认是不自动编译的,而spring-boot-devtools又是监测classpath下的文件发生变化才会重启应用,所以需要设置IDEA的自动编译

  • 自动编译配置

​ File-Settings-Compiler-Build Project automatically

image-20250109173431817

  • Registry 属性修改

    ctrl + shift + alt + /,选择Registry,勾上 Compiler autoMake allow when app running

    image-20250109173540377

    image-20250109173617725

热部署效果测试

  • 第一次访问 user/uname/{uname} 接口

    @GetMapping("user/uname/{userName}")
    @ApiOperation(value = "根据用户名查询用户记录")
    @ApiImplicitParam(name = "userName",value = "查询参数",required = true,paramType= "path")
    public User queryUserByUserName(@PathVariable String userName){
    	return userService.queryUserByUserName(userName);
    }
    

    image-20250109173747188

    image-20250109173815738

  • 修改接口代码 控制台打印接收的uname参数 ctrl+f9 键重新编译 浏览器访问

    @GetMapping("user/uname/{userName}")
    @ApiOperation(value = "根据用户名查询用户记录")
    @ApiImplicitParam(name = "userName",value = "查询参数",required = true,paramType
    = "path")
    public User queryUserByUserName(@PathVariable String userName){
        System.out.println("查询参数-->userName:"+userName);
        return userService.queryUserByUserName(userName);
    }
    

    image-20250109174541302

    image-20250109174607054

SpringBoot单元测试

​ 做过web项目开发的对于单元测试都并不陌生了,通过它能够快速检测业务代码功能的正确与否,SpringBoot框架对单元测试也提供了良好的支持,来看SpringBoot应用中单元测试的使用。

pom.xml测试依赖添加

    <!--单元测试-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
    </dependency>

Service业务方法测试

​ 这里以UserService为例,src/tets/java 目录下添加测试包 com.msb.sprinboot.service 定义测试类代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest(classes={Starter.class})
public class TestUserService {

    //使用日志
    private Logger logger= LoggerFactory.getLogger(TestUserService.class);

    //注入service
    @Autowired
    private UserService userService;


    @Before
    public void before(){
        System.out.println("单元测试方法执行前执行...");
    }


    @Test
    public void test01(){
        logger.info("当前查询用户对象的用户名:{}",userService.queryUserById(10).getUserName());
    }

    @After
    public void after(){
        System.out.println("单元测试方法执行后执行...");
    }
}

image-20250109182810324

控制层接口方法测试

​ 视图层代码使用MockMvc 进行测试,这里以UserCntroller 为例,src/tets/java 目录下添加测试包com.msb.sprinboot.controller 定义测试类代码如下:

@RunWith(SpringRunner.class)
@SpringBootTest(classes={Starter.class})
@AutoConfigureMockMvc
public class TestController {

    private Logger log = LoggerFactory.getLogger(TestController.class);
    @Autowired
    private MockMvc mockMvc;

    @Test
    public void test01() throws Exception {
        //构建请求
        MockHttpServletRequestBuilder request = MockMvcRequestBuilders.get("/user/list")
                .contentType("text/html;charset=UTF-8")//设置请求头
                .accept(MediaType.APPLICATION_JSON);//设置接收类型

        //发送请求,获取请求的结果
        ResultActions perform = mockMvc.perform(request);

        //校验请求结果
        perform.andExpect(MockMvcResultMatchers.status().isOk());

        //得到请求返回的结果
        MvcResult mvcResult = perform.andReturn();

        //获取请求返回的数据
        String data = mvcResult.getResponse().getContentAsString();

        System.out.println(data);

    }
}

image-20250109183215444

分布式缓存Ehcache整合

​ EhCache是一个比较成熟的Java缓存框架(分布式本地缓存),最早从hibernate发展而来, 是进程中的缓存系统,它提供了用内存,磁盘文件存储,以及分布式存储方式等多种灵活的cache管理方案,快速简单。

​ Spring Boot对Ehcache的使用提供支持,所以在Spring Boot中只需简单配置即可使用Ehcache实现数据缓存处理。

Spring Cache相关注解说明

​ SpringBoot 内部使用SpringCache 来实现缓存控制,这里集成Ehcache实际上是对SpringCache抽象的其中一种实现,这里在使用Ehcache实现缓存控制时相关注解说明如下

@CacheConfig

​ 用于标注在类上,可以存放该类中所有缓存的公有属性,比如设置缓存的名字。

@CacheConfig(cacheNames = "users")
public class UserService {。。。}

​ 这里也可以不使用该注解,直接使用@Cacheable配置缓存集的名字。

@Cacheable

​ 应用到读取数据的方法上,即可缓存的方法,如查找方法,先从缓存中读取,如果没有再调用相应方法获取数据,然后把数据添加到缓存中。

该注解主要有下面几个参数:

  • **value、cacheNames:**两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了

  • key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档

  • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有当第一个参数的长度小于3的时候才会被缓存。

  • unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。

  • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的

  • cacheManager:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用

  • cacheResolver:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver接口来实现自己的缓存解析器,并用该参数指定。

@Cacheable(value = "user", key = "#id")
User selectUserById(final Integer id);

@CachePut

​ 应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存,@CachePut的参数与@Cacheable类似,示例如下:

@CachePut(value = "user", key = "#user.id")
public User save(User user) {
    users.add(user);
    return user;
}

@CacheEvict

​ 应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据,示例如下:

@CacheEvict(value = "user", key = "#id")
void delete(final Integer id);

​ 除了同@Cacheable一样的参数之外,@CacheEvict还有下面两个参数:

  • allEntries:非必需,默认为false。当为true时,会移除所有数据

  • beforeInvocation:非必需,默认为false,会在调用方法之后移除数据。当为true时,会在调用方法之前移除数据。

@Caching

​ 组合多个Cache注解使用。示例:

@Caching(
    put = {
    @CachePut(value = "user", key = "#user.id"),
    @CachePut(value = "user", key = "#user.username"),
    @CachePut(value = "user", key = "#user.age")
    }
}

​ 将id–>user;username—>user;age—>user进行缓存。

环境配置

pom.xml 依赖添加

 <!-- Ehcache -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    <!-- Ehcache 坐标 -->
    <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache</artifactId>
    </dependency>

ehcahe.xml 文件添加

​ src/main/resources 目录下添加ehcache.xml 文件,内容如下:

<ehcache name="mycache">
    <diskStore path="E:\Informations\MSB\project\springboot\cache"/>
    <!--
    name:缓存名称。
    maxElementsInMemory:缓存最大数目
    maxElementsOnDisk:硬盘最大缓存个数。
    eternal:对象是否永久有效,一但设置了,timeout将不起作用。
    overflowToDisk:是否保存到磁盘,当系统宕机时
    timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。
        仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
    timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。
        最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0,也就是对象存活时间无穷大。
    diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists
    between restarts of the Virtual Machine. The default value is false.
    diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
    diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
    memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。
    默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
    clearOnFlush:内存数量最大时是否清除。
    memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
    FIFO,first in first out,这个是大家最熟的,先进先出。
    LFU, Less Frequently Used,最近最少被访问的。
    LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,
    当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
    -->
    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            maxElementsOnDisk="10000000"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU">
    </defaultCache>
    <cache
            name="users"
            eternal="false"
            maxElementsInMemory="100"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="0"
            timeToLiveSeconds="300"
            memoryStoreEvictionPolicy="LRU"/>
</ehcache>

application.yml 添加缓存配置

spring:
  # Ehcache缓存配置
  cache:
    ehcache:
      config: classpath:ehcache.xml

Starter 启动入口类启动缓存

​ 在Starter启动入口类中,添加@EnableCaching注解,启动缓存

@SpringBootApplication
@EnableCaching//启动缓存
@MapperScan("com.msb.springboot.dao")
public class Starter {
    public static void main(String[] args) {
        SpringApplication.run(Starter.class);
    }
}

JavaBean对象实现序列化

@ApiModel(value = "用户实体类")
public class User implements Serializable {
    @ApiModelProperty(name="id",value = "用户id",example = "0")
    private Integer id;//用户id
    @ApiModelProperty(value = "用户名称")
    @NotNull(message = "用户名称不能为空")
    private String userName;//用户名
    @ApiModelProperty(value = "用户密码")
    @NotNull(message = "用户密码不能为空")
    private String userPwd;//用户密码

    /*
    	省略 get|set方法
    */

}

缓存实现

以UserService 方法为例

用户详情查询缓存

    @Cacheable(value = "users",key = "#id")
    public User queryUserById(Integer id){
        return userMapper.queryUserById(id);
    }

用户列表查询缓存

    @Cacheable(value = "users",key = "#userQuery.userName+'-'+#userQuery.pageNum+'-'+#userQuery.pageSize")
    public PageInfo<User> selectByParams(UserQuery userQuery){
        //开启分页
        PageHelper.startPage(userQuery.getPageNum(),userQuery.getPageSize());
        //返回分页结果
        return new PageInfo<User>(userMapper.selectByParams(userQuery));
    }

用户更新&删除缓存

 	@CachePut(value = "users",key = "#user.id")
    public User updateUser(User user){
        //用户名 非空判断
        AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()),"用户名称不能为空");
        //用户密码 非空判断
        AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空");
        //通过用户名查询用户对象
        User temp = userMapper.queryUserByName(user.getUserName());
        //如果用户对象不为空且用户id不一致
        AssertUtil.isTrue(null!=temp && !(user.getId().equals(temp.getId())),"用户名已存在,请重新输入");
        //执行修改操作,判断受影响的行数
        AssertUtil.isTrue(userMapper.updateUser(user)!=1,"用户添加失败");

        return user;
    }

 	@Transactional(propagation = Propagation.REQUIRED)
    @CacheEvict(value = "users",key = "#id")
    public void deleteUser(Integer id){
        //判断id是否为空且用户对象存在
        AssertUtil.isTrue(null==id &&userMapper.queryUserById(id)==null,"待删除记录不存在");
        //执行删除操作,判断受影响的行数
        AssertUtil.isTrue(userMapper.deleteUser(id)!=1,"用户删除失败");
    }

定时调度集成-Quartz

​ 在日常项目运行中,我们总会有需求在某一时间段周期性的执行某个动作。比如每天在某个时间段导出报表,或者每隔多久统计一次现在在线的用户量等。

​ 在Spring Boot中有Java自带的java.util.Timer类,SpringBoot自带的Scheduled来实现,也有强大的调度器Quartz。Scheduled 在Spring3.X 引入,默认SpringBoot自带该功能,使用起来也很简单,在启动类级别添加@EnableScheduling注解即可引入定时任务环境。但遗憾的是Scheduled 默认不支持分布式环境,这里主要讲解Quartz 时钟调度框架与Spring Boot 集成。

环境整合配置

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

代码实现

定义job

​ com.msb.springboot下添加jobs包,定义待执行job任务

public class MyFirstJob implements Job {
    private Logger log = LoggerFactory.getLogger(MyFirstJob.class);
    @Override
    public void execute(JobExecutionContext context) throws JobExecutionException {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        TriggerKey triggerKey = context.getTrigger().getKey();
        log.info("触发器:"+triggerKey.getName()+"-->所属组:"+triggerKey.getGroup()+"-->"+sdf.format(new Date())+"-->"+"hello Spring Boot Quartz...");
    }
}

构建调度配置类

@Configuration
public class QuartzConfig {
    @Bean
    public JobDetail jobDetail1(){
    	return JobBuilder.newJob(MyFirstJob.class).storeDurably().build();
    }
    
    @Bean
    public Trigger trigger1(){
    	SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule()
        //每一秒执行一次
        .withIntervalInSeconds(1)
        //永久重复,一直执行下去
        .repeatForever();
        return TriggerBuilder.newTrigger()
        .withIdentity("trigger1","group1")
        .withSchedule(scheduleBuilder)
		.forJob(jobDetail1())
		.build();
}
        // 每两秒触发一次任务
        @Bean
        public Trigger trigger2(){
        return TriggerBuilder.newTrigger()
        .withIdentity("trigger2", "group1")
        .withSchedule(CronScheduleBuilder.cronSchedule("0/5 * * * * ? *"))
        .forJob(jobDetail1())
        .build();
        }
}    

启动Starter

image-20250109190220819

全局异常与事物控制

Spring Boot事物支持

​ 在使用Jdbc 作为数据库访问技术时,Spring Boot框架定义了基于jdbc 的PlatformTransactionManager 接口的实现DataSourceTransactionManager,并在Spring Boot 应用启动时自动进行配置。如果使用jpa 的话 Spring Boot 同样提供了对应实现。

image-20250109190429249

​ 这里Spring Boot 集成了Mybatis框架,Mybatis底层数据访问层实现基于jdbc 来实现,所以在Spring Boot 环境下对事物进行控制,事物实现由Spring Boot实现并自动配置,在使用时通过注解方式标注相关方法加入事物控制即可

  • 声明式事物配置

    	@Transactional(propagation = Propagation.REQUIRED)
        public void addUser(User user){
            //用户名 非空判断
            AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()),"用户名称不能为空");
            //用户密码 非空判断
            AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空");
            //通过用户名查询用户对象
            User temp = userMapper.queryUserByName(user.getUserName());
            //判断用户名唯一
            AssertUtil.isTrue(null!=temp,"用户已存在,请重新输入");
            //执行添加操作,判断受影响的行数
            AssertUtil.isTrue(userMapper.addUser(user)!=1,"用户添加失败");
        }
    
    	@Transactional(propagation = Propagation.REQUIRED)
        @CachePut(value = "users",key = "#user.id")
        public User updateUser(User user){
            //用户名 非空判断
            AssertUtil.isTrue(StringUtils.isBlank(user.getUserName()),"用户名称不能为空");
            //用户密码 非空判断
            AssertUtil.isTrue(StringUtils.isBlank(user.getUserPwd()),"用户密码不能为空");
            //通过用户名查询用户对象
            User temp = userMapper.queryUserByName(user.getUserName());
            //如果用户对象不为空且用户id不一致
            AssertUtil.isTrue(null!=temp && !(user.getId().equals(temp.getId())),"用户名已存在,请重新输入");
            //执行修改操作,判断受影响的行数
            AssertUtil.isTrue(userMapper.updateUser(user)!=1,"用户添加失败");
    
            return user;
        }
    
    	@Transactional(propagation = Propagation.REQUIRED)
        @CacheEvict(value = "users",key = "#id")
        public void deleteUser(Integer id){
            //判断id是否为空且用户对象存在
            AssertUtil.isTrue(null==id &&userMapper.queryUserById(id)==null,"待删除记录不存在");
            //执行删除操作,判断受影响的行数
            AssertUtil.isTrue(userMapper.deleteUser(id)!=1,"用户删除失败");
        }
    

Spring Boot 全局异常处理

​ SpringMvc 中对异常统一处理提供了相应处理方式,推荐大家使用的是实现接口HandlerExceptionResolver的方式,对代码侵入性较小。

​ 在Spring Boot 应用中同样提供了对异常的全局性处理,相关注解如下:

@ControllerAdvice

​ 该注解组合了@Component注解功能,最常用的就是作为全局异常处理的切面类,同时通过该注解可以指定包扫描的范围。@ControllerAdvice约定了几种可行的返回值,如果是直接返回model类的话,需要使用@ResponseBody进行json转换

@ExceptionHandler

​ 该注解在Spring 3.X 版本引入,在处理异常时标注在方法级别,代表当前方法处理的异常类型有哪些 具体应用以Restful 接口为例,测试保存用户接口

全局异常应用

异常抛出与全局捕捉
  • UserController 查询接口

        /**
         * 通过用户名查询用户对象
         * @param userName
         * @return
         */
        @GetMapping("/user/name/{userName}")
        @ApiOperation(value = "通过用户名称查询用户对象")
        @ApiImplicitParam(name = "userName",value = "用户名称",required = true,paramType = "path")
        public User getUserService(@PathVariable String userName) {
            System.out.println("用户查询...test121123");
            return userService.queryUserByName(userName);
        }
    
  • UserService 查询业务方法,抛出ParamExceptions 异常

        public User queryUserByName(String userName){
    
            AssertUtil.isTrue(true,"异常...");
    
            return userMapper.queryUserByName(userName);
        }
    
  • 全局异常处理类GlobalExceptionHandler定义

    @ControllerAdvice//声明该类作为全局异常处理的切面类
    public class GlobalExceptionHandler {
    
        /**
         * 全局异常处理 返回json
         * @param e
         * @return
         */
        @ExceptionHandler(value = Exception.class)
        @ResponseBody
        public ResultInfo exceptionHandler(Exception e){
            ResultInfo resultInfo=new ResultInfo();
            resultInfo.setCode(500);
            resultInfo.setMsg("访问异常!");
            return resultInfo;
        }
    
    }
    
  • Postman 执行测试效果

image-20250109192634375

特定异常处理

​ 通过@ExceptionHandler 标注方法可以处理特定异常,这里以参数异常为例,通过全局异常进行统一处理

@ControllerAdvice//声明该类作为全局异常处理的切面类
public class GlobalExceptionHandler {

    /**
     * 全局异常处理 返回json
     * @param e
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResultInfo exceptionHandler(Exception e){
        ResultInfo resultInfo=new ResultInfo();
        resultInfo.setCode(500);
        resultInfo.setMsg("访问异常!");
        return resultInfo;
    }

    /**
     * 处理特定异常
     * @param p
     * @return
     */
    @ExceptionHandler(value = ParamsException.class)
    @ResponseBody
    public ResultInfo ParamsExceptionHandler(ParamsException p){
        ResultInfo resultInfo=new ResultInfo();
        resultInfo.setCode(p.getCode());
        resultInfo.setMsg(p.getMsg());
        return resultInfo;
    }

}

SpringBoot数据校验-Validation

​ 日常项目开发中,对于前端提交的表单,后台接口接收到表单数据后,为了程序的严谨性,通常后端会加入业务参数的合法校验操作来避免程序的非技术性bug,这里对于客户端提交的数据校验,SpringBoot通过spring-boot-starter-validation 模块包含了数据校验的工作。

​ 这里主要介绍Spring Boot中对请求数据进行校验,相关概念如下

  • JSR303/JSR-349: JSR303是一项标准,只提供规范不提供实现,规定一些校验规范即校验注解,如@Null,@NotNull,@Pattern,位于javax.validation.constraints包下。JSR-349是其升级版本,添加了一些新特性。

  • Hibernate Validation:Hibernate Validation是对这个规范的实现,并增加了一些其他校验注解,如@Email,@Length,@Range等等

  • Spring Validation:Spring Validation对Hibernate Validation进行了二次封装,在Spring Mvc模块中添加了自动校验,并将校验信息封装进了特定的类中

环境配置

​ 实现参数校验,程序必须引入spring-boot-starter-validation 依赖,只是在引入spring-boot-starter-web依赖时,该模块会自动依赖spring-boot-starter-validation,所以程序中引入spring-boot-starter-web 会一并依赖spring-boot-starter-validation到项目中。

image-20250109194010575

校验相关注解

注解 功能
@AssertFalse 可以为null,如果不为null的话必须为false
@AssertTrue 可以为null,如果不为null的话必须为true
@DecimalMax 设置不能超过最大值
@DecimalMin 设置不能超过最小值
@Digits 设置必须是数字且数字整数的位数和小数的位数必须在指定范围内
@Future 日期必须在当前日期的未来
@Past 日期必须在当前日期的过去
@Max 最大不得超过此最大值
@Min 最大不得小于此最小值
@NotNull 不能为null,可以是空
@Pattern 必须满足指定的正则表达式
@Size 集合、数组、map等的size()值必须在指定范围内
@Email 必须是email格式
@Length 长度必须在指定范围内
@NotBlank 字符串不能为null,字符串trin()后也不能等于“”
@NotEmpty 不能为null,集合、数组、map等size()不能为0;字符串trin()后可以等于“”
@Range 值必须在指定范围内
@URL 必须是一个URL

校验注解使用

  • User实体类参数校验注解

    public class User implements Serializable {
        private Integer id;
        @NotBlank(message = "用户名不能为空!")
        private String userName;
        @NotBlank(message = "用户密码不能为空!")
        @Length(min = 6, max = 10,message = "密码长度至少6位但不超过10位!")
        private String userPwd;
        @Email
        private String email;
        /*
        省略get set 方法
        */
    }
    
  • 接口方法形参@Valid注解添加

    @PostMapping("user02")
    @ApiOperation(value = "用户添加")
    @ApiImplicitParam(name = "user02",value = "用户实体类",dataType = "User")
    public ResultInfo saveUser02(@Valid User user){
        ResultInfo resultInfo=new ResultInfo();
        //userService.saveUser(user);
        return resultInfo;
    }
    
  • 全局异常错误信息捕捉

    @ControllerAdvice//声明该类作为全局异常处理的切面类
    public class GlobalExceptionHandler {
        /**
         * 数据校验 处理特定异常
         * @param b
         * @return
         */
        @ExceptionHandler(value = BindException.class)
        @ResponseBody
        public ResultInfo BindExceptionHandler(BindException b){
            ResultInfo resultInfo=new ResultInfo();
            resultInfo.setCode(500);
          resultInfo.setMsg(b.getBindingResult().getFieldError().getDefaultMessage());
            return resultInfo;
        }
    }
    
  • PostMan 接口测试

image-20250109195142953

image-20250109195224033

© 2024 - 2025 雪中烛

 

我是谁

我是MyraZ.

👉百度

Me

Cut out summary from your post content here.

The remaining content of your post.

Desc2

Cut out summary from your post content here.

The remaining content of your post.