Java教程

SpringBoot学习笔记

本文主要是介绍SpringBoot学习笔记,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1、SpringBoot 原理

自动装配:

pom.xml

  • spring-boot-dependencies :核心依赖在父工程中!
  • 我们在写或者引入一些SpringBoot依赖的时候,不需要指定版本,就因为有这些版本仓库

启动器

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
  • 启动器:说白了就是SpringBoot的启动场景。
  • 比如spring-boot-starter-web,他就会帮我们自动导入web环境所有的依赖!
  • SpringBoot会将所有的功能场景,都变成一个个的启动器。
  • 我们要使用什么功能,就只需要找到对应的启动器就可以了starter

主程序

//@SpringBootApplication 来标注一个主程序类
//说明这是一个Spring Boot应用
@SpringBootApplication
public class SpringbootApplication {
    
   public static void main(String[] args) {
     //以为是启动了一个方法,没想到启动了一个服务
      SpringApplication.run(SpringbootApplication.class, args);
   } 
}
  • 注解
    • @SpringBootconfiguration:springboot的配置
      • @Configuration:spring配置类
      • @Component:说明这也是一个spring的组件
    • @EnableAutoConfiguration:自动配置
      • @AutoConfigurationPackage:自动配置包
        • @Import(AutoConfigurationPackages.Registrar.class):自动配置包注册
      • @Import(AutoConfigurationImportSelector.class):自动配置导入选择器
        • getCandidateConfigurations 方法 调用 loadFactoryNames 方法 搜寻配置文件 META-INF/spring.factories 的配置项
    • @ComponentScan:扫描组件

所以,自动配置真正实现是从 classpath 中搜寻所有的 META-INF/spring.factories 配置文件

并将其中对应的 org.springframework.boot.autoconfigure. 包下的配置项

通过反射实例化为对应标注了 @Configuration的JavaConfig形式的IOC容器配置类

然后将这些都汇总成为一个实例并加载到IOC容器中。

结论:

  1. SpringBoot 在启动的时候从类路径下的 META-INF/spring.factories 中获取 EnableAutoConfiguration 指定的值
  2. 将这些值作为自动配置类导入容器 ,自动配置类就生效 ,帮我们进行自动配置工作;
  3. 整个J2EE的整体解决方案和自动配置都在springboot-autoconfigure的jar包中;
  4. 它会给容器中导入非常多的自动配置类 (xxxAutoConfiguration),就是给容器中导入这个场景需要的所有组件,并配置好这些组件;
  5. 有了自动配置类,免去了我们手动编写配置注入功能组件等的工作;

run方法流程分析:

image

2、SpringBoot 配置

2.1 多环境切换

2.1.1 yml 多文档块

application.yml

# 选择要激活哪个环境块
spring:
  profiles:
    active: prod
    
# 用三条杠来分隔块
---
spring:
  profiles: dev # 配置环境的名称

server:
  port: 8081
  
---
spring:
  profiles: prod  # 配置环境的名称
  
server:
  port: 8082

2.1.2 配置文件加载位置

SpringBoot启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:

优先级1:项目路径下的config文件夹配置文件
优先级2:项目路径下配置文件
优先级3:资源路径下的config文件夹配置文件
优先级4:资源路径下配置文件

优先级由高到底,高优先级的配置会覆盖低优先级的配置;

SpringBoot会从这四个位置全部加载主配置文件;互补配置;

2.2 自动装配原理

HttpEncodingAutoConfiguration(Http编码自动配置)为例解释自动配置原理。

// 表示这是一个配置类,和以前编写的配置文件<Beans>一样,也可以给容器中添加组件;
@Configuration 

// 启动指定类的ConfigurationProperties功能;
// 进入这个HttpProperties查看,将配置文件中对应的值和HttpProperties绑定起来;
// 并把HttpProperties加入到ioc容器中
@EnableConfigurationProperties({HttpProperties.class}) 

// Spring底层@Conditional注解
// 根据不同的条件判断,如果满足指定的条件,整个配置类里面的配置就会生效;
// 这里的意思就是判断当前应用是否是web应用,如果是,当前配置类生效
@ConditionalOnWebApplication(
    type = Type.SERVLET
)

// 判断当前项目有没有这个类CharacterEncodingFilter;SpringMVC中进行乱码处理的过滤器;
@ConditionalOnClass({CharacterEncodingFilter.class})

// 判断配置文件中是否存在某个配置:spring.http.encoding.enabled;
// 如果不存在,判断也是成立的
// 即使我们配置文件中不配置spring.http.encoding.enabled=true,也是默认生效的;
@ConditionalOnProperty(
    prefix = "spring.http.encoding",
    value = {"enabled"},
    matchIfMissing = true
)

public class HttpEncodingAutoConfiguration {
    // 他已经和SpringBoot的配置文件映射了
    private final Encoding properties;
    // 只有一个有参构造器的情况下,参数的值就会从容器中拿
    public HttpEncodingAutoConfiguration(HttpProperties properties) {
        this.properties = properties.getEncoding();
    }

    // 给容器中添加一个组件,这个组件的某些值需要从properties中获取
    @Bean
    @ConditionalOnMissingBean //判断容器没有这个组件?
    public CharacterEncodingFilter characterEncodingFilter() {
        CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
        filter.setEncoding(this.properties.getCharset().name());
        // ......
        return filter;
    }
}

一句话总结 :根据当前不同的条件判断,决定这个配置类是否生效!

  • 一但这个配置类生效;这个配置类就会给容器中添加各种组件;
  • 这些组件的属性是从对应的properties类中获取的,这些类里面的每一个属性又是和配置文件绑定的;
  • 所有在配置文件中能配置的属性都是在xxxxProperties类中封装着;
  • 配置文件能配置什么就可以参照某个功能对应的这个属性类。
//从配置文件中获取指定的值和bean的属性进行绑定
@ConfigurationProperties(prefix = "spring.http") 
public class HttpProperties {
    // .....
}

精髓

  • SpringBoot启动会加载大量的自动配置类

  • 我们看我们需要的功能有没有在SpringBoot默认写好的自动配置类当中;

  • 我们再来看这个自动配置类中到底配置了哪些组件;(只要我们要用的组件存在在其中,我们就不需要再手动配置了)

  • 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们只需要在配置文件中指定这些属性的值即可;

    xxxxAutoConfigurartion:自动配置类;给容器中添加组件

    xxxxProperties:封装配置文件中相关属性;供自动配置类使用

2.3 @Conditional

了解完自动装配的原理后,我们来关注一个细节问题,自动配置类必须在一定的条件下才能生效;

@Conditional派生注解(Spring注解版原生的@Conditional作用)

作用:必须是@Conditional指定的条件成立,才给容器中添加组件,配置配里面的所有内容才生效;

@Conditional扩展注解 作用(判断是否满足当前指定条件)
@ConditionalOnJava 系统的Java版本是否符合要求
@ConditionalOnBean 容器中存在指定Bean
@ConditionalOnMissingBean 容器中不存在指定Bean
@ConditionalOnExpression 满足SpEL表达式指定
@ConditionalOnClass 系统中有指定的类
@ConditionalOnMissingClass 系统中没有指定的类
@ConditionalOnSingleCandidate 容器中只有一个指定的Bean,或者这个Bean是首选Bean
@ConditionalOnProperty 系统中指定的属性是否有指定的值
@ConditionalOnResource 类路径下是否存在指定资源文件
@ConditionalOnWebApplication 当前是Web环境
@ConditionalOnNotWebApplication 当前不是Web环境
@ConditionalOnJndi JNDI存在指定项

通过启用 debug=true属性;来让控制台打印自动配置报告,这样我们就可以很方便的知道哪些自动配置类生效;

#开启springboot的调试类
debug: true

3、yaml语法

# k=v
# 对空格的要求十分高!

# 普通的key-value
name: qinjiang

#对象
student:
	name: qinjiang
	age: 3
	
#行内写法
student: {name: qinjiang, age: 3}

#数组
pets:
    - cat
    - dog
    - pig
    
pets: [cat,dog,pig]

4、属性赋值

4.1 @Autowired

@SpringBootTest
class DemoApplicationTests {

    @Autowired
    Person person; //将person自动注入进来

    @Test
    public void contextLoads() {
        System.out.println(person); //打印person信息
    }

}

4.2 @Value

@Component //注册bean
@PropertySource(value = "classpath:user.properties")
public class User {
    //直接使用@value
    @Value("${user.name}") //从配置文件中取值
    private String name;
    @Value("#{9*2}")  // #{SPEL} Spring表达式
    private int age;
    @Value("男")  // 字面量
    private String sex;
}

4.3 @ConfigurationProperties

application.yaml

person:
	name: qinjiangage:3
	happy: false
	birth: 2019/11/e2
	maps: {k1:v1,k2:v2}
	lists:
		- code
		- music
		- girl
    dog:
    	name: 旺财
    	age: 3

Person.java

@Component
@ConfigurationProperties(prefix = "person")
public class Person {

    private String name;
    private Integer age;
    private Boolean happy;
    private Date birth;
    private Map<String,object> maps;
    private List<object> lists;
    private Dog dog;
}

@ConfigurationProperties作用:

将配置文件中配置的每一个属性的值,映射到这个组件中;
告诉springBoot将本类中的所有属性和配置文件中相关的配置进行绑定;
参数 prefix = "person”:将配置文件中的person下面的所有属性一一对应;
只有这个组件是容器中的组件,才能使用容器提供的@ConfigurationProperties功能。

配置文件占位符:

配置文件还可以编写占位符生成随机数

person:
    name: qinjiang${random.uuid} # 随机uuid
    age: ${random.int}  # 随机int
    happy: false
    birth: 2000/01/01
    maps: {k1: v1,k2: v2}
    lists:
      - code
      - girl
      - music
    dog:
      name: ${person.hello:other}_旺财
      age: 1

4.4 JSR-303 数据校验

如何使用

@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated  //数据校验
public class Person {

    @Email(message="邮箱格式错误") //name必须是邮箱格式
    private String name;
}

常见参数

@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;

空检查
@Null       验证对象是否为null
@NotNull    验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank   检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty   检查约束元素是否为NULL或者是EMPTY.
    
Boolean检查
@AssertTrue     验证 Boolean 对象是否为 true  
@AssertFalse    验证 Boolean 对象是否为 false  
    
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内  
@Length(min=, max=) string is between min and max included.

日期检查
@Past       验证 Date 和 Calendar 对象是否在当前时间之前  
@Future     验证 Date 和 Calendar 对象是否在当前时间之后  
@Pattern    验证 String 对象是否符合正则表达式的规则

.......等等
除此以外,我们还可以自定义一些数据校验规则

5、SpringBoot Web 开发

5.1 静态资源

在SpringBoot,我们可以使用以下方式处理静态资源:

  • webjars localhost:8080/webjars/

  • public,static,/**, resource localhost:8080/

    优先级: resource > static (默认) > public

resources目录下有四个子文件夹:

  • public
  • resouce
  • static
  • templates

5.2 模板引擎 Thymeleaf

pom.xml

<!-- Thymeleaf -->
<dependency>
    <groupId>org.thymeleaf</groupId>
    <artifactId>thymeleaf-spring5</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-java8time</artifactId>
</dependency>

5.2.1 视图跳转

TestController.java

@Controller
public class TestController {   
    @RequestMapping("/t1")
    public String test1(){
        //classpath:/templates/test.html
        return "test";
    }
}
  • 将test.html 放在 resources/templates 目录下
  • 通过Thymeleaf配置的视图解析器即可跳转

5.2.2 语法

引入提示:

<html lang="en" xmlns:th="http://www.thymeleaf.org">
  • 变量表达式:${...}
  • 选择变量表达式:*{...}
  • 消息表达式:#{...}(来自于properties、yml文件的变量)
  • 链接 URL 表达式:@{...}
  • 片段表达式:~{...}

5.3 自定义扩展 SpringMVC 配置

以自定义用户视图解析器为例

// 因为类型要求为WebMvcConfigurer,所以我们实现其接口
// 可以使用自定义类扩展MVC的功能
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        // 浏览器发送/test , 就会跳转到test页面;
        registry.addViewController("/test").setViewName("test");
    }
}
  • 切记不能使用@EnableWebMvc,否则其他默认的mvc配置就失效了。
  • 相当于全面接管SpringMVC,自己实现所有配置,而不是扩展配置。

5.4 Servlet 注册

@Configuration
public class ServletConfig {
    @Bean
    public ServletRegistrationBean DIYServlet() {
        ServletRegistrationBean bean = new ServletRegistrationBean(new DIYServlet(), "/xxx");
        bean.setxxxx();
        ......
        return bean;
    }
}

5.5 Filter 注册

@Bean
public FilterRegistrationBean DIYFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new DIYFilter());

    //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
    Map<String, String> initParams = new HashMap<>();
    initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
    bean.setInitParameters(initParams);

    //"/*" 表示过滤所有请求
    bean.setUrlPatterns(Arrays.asList("/*"));
    return bean;
}

6、整合 JDBC

依赖导入(pom.xml)

<!-- JDBC -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>

配置参数(application.yml)

spring:
	datasource:
        username: root
        password: 123456
        url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
        driver-class-name: com.mysql.jdbc.Driver

使用

@RestController
public class JDBCController {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @GetMapping("/userList")
    public List<Map<String,object>> userList(){
        String sql = "select * from user";
        List<Map<String,object>> list_maps = jdbcTemplate.queryForList(sql);
        return list_maps;
    }
}
  • 默认数据源是Hikari

7、整合 Druid

依赖导入(pom.xml)

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.21</version>
</dependency>

配置参数(application.yml)

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

Log4j依赖

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

编写DruidConfig配置类

@Configuration
public class DruidConfig {
    /*
       将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
       绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource 从而让它们生效
       @ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
       前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
     */
    @ConfigurationProperties(prefix = "spring.datasource")
    @Bean
    public DataSource druidDataSource() {
        return new DruidDataSource();
    }

}

配置Druid数据源监控

//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
    // 设置进入后台页面的url
    ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");

    // 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet 
    // 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
    Map<String, String> initParams = new HashMap<>();
    initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
    initParams.put("loginPassword", "123456"); //后台管理界面的登录密码

    //后台允许谁可以访问
    //initParams.put("allow", "localhost"):表示只有本机可以访问
    //initParams.put("allow", ""):为空或者为null时,表示允许所有访问
    initParams.put("allow", "");
    //deny:Druid 后台拒绝谁访问
    //initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问

    //设置初始化参数
    bean.setInitParameters(initParams);
    return bean;
}

配置 Druid web 监控 filter 过滤器

//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean();
    bean.setFilter(new WebStatFilter());

    //exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
    Map<String, String> initParams = new HashMap<>();
    initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
    bean.setInitParameters(initParams);

    //"/*" 表示过滤所有请求
    bean.setUrlPatterns(Arrays.asList("/*"));
    return bean;
}

8、整合 Mybatis

导入依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

配置数据库连接信息

spring:
  datasource:
    username: root
    password: 123456
    #?serverTimezone=UTC解决时区的报错
    url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    driver-class-name: com.mysql.cj.jdbc.Driver
    type: com.alibaba.druid.pool.DruidDataSource

    #Spring Boot 默认是不注入这些属性值的,需要自己绑定
    #druid 数据源专有配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true

    #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
    #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
    #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
mybatis:
  mapper-locations: classpath:mappers/**/*.xml

创建Mapper接口

//@Mapper:表示本类是一个 MyBatis 的 Mapper
@Mapper
@Repository
public interface DepartmentMapper {

    // 获取所有部门信息
    List<Department> getDepartments();

    // 通过id获得部门
    Department getDepartment(Integer id);

}

对应Mapper映射文件xml

DepartmentMapper.xml

<?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.kuang.mapper.DepartmentMapper">

    <select id="getDepartments" resultType="Department">
       select * from department;
    </select>

    <select id="getDepartment" resultType="Department" parameterType="int">
       select * from department where id = #{id};
    </select>

</mapper>

maven配置资源过滤问题

<resources>
    <resource>
        <directory>src/main/java</directory>
        <includes>
            <include>**/*.xml</include>
        </includes>
        <filtering>true</filtering>
    </resource>
</resources>

9、SpringBoot 多数据源配置

这里使用开源项目:Dynamic DataSource

依赖导入

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>3.3.6</version>
</dependency>

配置数据源

spring:
  datasource:
    dynamic:
      primary: master #设置默认的数据源或者数据源组,默认值即为master
      strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
      datasource:
        master:
          url: jdbc:mysql://xx.xx.xx.xx:3306/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver # 3.2.0开始支持SPI可省略此配置
        slave_1:
          url: jdbc:mysql://xx.xx.xx.xx:3307/dynamic
          username: root
          password: 123456
          driver-class-name: com.mysql.jdbc.Driver
        slave_2:
          url: ENC(xxxxx) # 内置加密,使用请查看详细文档
          username: ENC(xxxxx)
          password: ENC(xxxxx)
          driver-class-name: com.mysql.jdbc.Driver
       #......省略
       #以上会配置一个默认库master,一个组slave下有两个子库slave_1,slave_2

使用 @DS 切换数据源。

@DS 可以注解在方法上或类上,同时存在就近原则 方法上注解 优先于 类上注解。一般在Service层使用。

注解 结果
没有@DS 默认数据源
@DS("dsName") dsName可以为组名也可以为具体某个库的名称
@Service
@DS("slave")
public class UserServiceImpl implements UserService {

  @Autowired
  private JdbcTemplate jdbcTemplate;

  public List selectAll() {
    return  jdbcTemplate.queryForList("select * from user");
  }
  
  @Override
  @DS("slave_1")
  public List selectByCondition() {
    return  jdbcTemplate.queryForList("select * from user where age >10");
  }
}

10、异步任务

给方法添加@Async注解

//告诉Spring这是一个异步方法
@Async
public void hello(){
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("业务进行中....");
}

SpringBoot 就会自己开一个线程池,进行调用!

但是要让这个注解生效,我们还需要在主程序上添加一个注解@EnableAsync ,开启异步注解功能;

@EnableAsync //开启异步注解功能
@SpringBootApplication
public class SpringbootTaskApplication {

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

}

11、定时任务

1、创建一个ScheduledService

@Service
public class ScheduledService {

    //秒   分   时     日   月   周几
    //0 * * * * MON-FRI
    //注意cron表达式的用法;
    @Scheduled(cron = "0 * * * * 0-7")
    public void hello(){
        System.out.println("hello.....");
    }
}

2、这里写完定时任务之后,我们需要在主程序上增加@EnableScheduling 开启定时任务功能

@EnableAsync //开启异步注解功能
@EnableScheduling //开启基于注解的定时任务
@SpringBootApplication
public class SpringbootTaskApplication {

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

}

3、详细了解cron表达式

http://www.bejson.com/othertools/cron/

4、常用的cron表达式

(1)0/2 * * * * ?   表示每2秒 执行任务
(1)0 0/2 * * * ?   表示每2分钟 执行任务
(1)0 0 2 1 * ?   表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI   表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2002-2006   表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ?   每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ?   朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED   表示每个星期三中午12点
(7)0 0 12 * * ?   每天中午12点触发
(8)0 15 10 ? * *   每天上午10:15触发
(9)0 15 10 * * ?     每天上午10:15触发
(10)0 15 10 * * ?   每天上午10:15触发
(11)0 15 10 * * ? 2005   2005年的每天上午10:15触发
(12)0 * 14 * * ?     在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ?   在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ?     在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ?   在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED   每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI   周一至周五的上午10:15触发
(18)0 15 10 15 * ?   每月15日上午10:15触发
(19)0 15 10 L * ?   每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L   每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005   2002年至2005年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3   每月的第三个星期五上午10:15触发

12、邮件任务

引入pom依赖

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

看它引入的依赖,可以看到 jakarta.mail

<dependency>
   <groupId>com.sun.mail</groupId>
   <artifactId>jakarta.mail</artifactId>
   <version>1.6.4</version>
   <scope>compile</scope>
</dependency>

配置文件

spring.mail.username=24736743@qq.com
spring.mail.password=你的qq授权码
spring.mail.host=smtp.qq.com
# qq需要配置ssl
spring.mail.properties.mail.smtp.ssl.enable=true

获取授权码:在QQ邮箱中的设置->账户->开启pop3和smtp服务

Spring单元测试

@Autowired
JavaMailSenderImpl mailSender;

@Test
public void contextLoads() {
    //邮件设置1:一个简单的邮件
    SimpleMailMessage message = new SimpleMailMessage();
    message.setSubject("通知-明天来狂神这听课");
    message.setText("今晚7:30开会");

    message.setTo("24736743@qq.com");
    message.setFrom("24736743@qq.com");
    mailSender.send(message);
}

@Test
public void contextLoads2() throws MessagingException {
    //邮件设置2:一个复杂的邮件
    MimeMessage mimeMessage = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);

    helper.setSubject("通知-明天来狂神这听课");
    helper.setText("<b style='color:red'>今天 7:30来开会</b>",true);

    //发送附件
    helper.addAttachment("1.jpg",new File(""));
    helper.addAttachment("2.jpg",new File(""));

    helper.setTo("24736743@qq.com");
    helper.setFrom("24736743@qq.com");

    mailSender.send(mimeMessage);
}

13、全局统一API响应

https://links.jianshu.com/go?to=https%3A%2F%2Fmp.weixin.qq.com%2Fs%3F__biz%3DMzI4OTA3NDQ0Nw%3D%3D%26mid%3D2455549015%26idx%3D2%26sn%3D44fa3621a8df3bd88e8c7064f1832105%26chksm%3Dfb9ca837cceb2121b9c15064f0a5f6c87f610c4b19c030cf385e0b297abe91cd177895c9c0fa%26scene%3D126%26sessionid%3D1600250050%26key%3Dcf1b11cd59da7ce7d9f9d510eb01a11d336a0136e7a8daea4a88ce757d5d899300b20616201de2a8a81284c4de25ea9a694a218fd06da8b4a0f3be80f7e34e06c63adefb7ffb7124ff8cc0e4833f8bbfbde4da7120c547c133d899108caf4876dd6c5d6d379884c956fcf7d5a503af6a97513175a8d5fb4a3421f55d51d41a0e%26ascene%3D14%26uin%3DOTMyNDYxMzQw%26devicetype%3DWindows%2B10%2Bx64%26version%3D6300002f%26lang%3Dzh_CN%26exportkey%3DAUOK0L%252F5IlqEYWRwGtxG%252FwI%253D%26pass_ticket%3DfEs1HHbuT5JrkDds5Mn8cg1NzokPvax7ngMP5blOa%252FDonl2n0B0odFiegTD8NGcg%26wx_header%3D0

13.1 Result 实体类

@Data
@AllArgsConstructor
@JSONType(orders = {"code", "message", "data"})
public class Result {

    private int code;
    private String message;
    private Object data;

    public static Result error(int code, String message) {
        return new Result(code, message, null);
    }

    public static Result error(ResultStatus info) {
        return new Result(info.getCode(), info.getMessage(), null);
    }

    public static Result success(Object obj) {
        return new Result(200, "SUCCESS", obj);
    }
}

13.2 ResultStatus 枚举类

@Getter
public enum ResultStatus {

    PERMISSION_DENIED(1001, "Unable to authenticate"),
    TOKEN_EXPIRE(1002, "token expire"),
    INTERNAL_SERVER_ERROR(1003, "simple error");
    // ...

    private final int code;
    private final String message;

    ResultStatus(int code, String message) {
        this.code = code;
        this.message = message;
    }
}

13.3 http异常处理

@RestController
@Slf4j
public class BasicErrorController implements ErrorController {

    @RequestMapping(value = {"/error"})
    public Object error(HttpServletResponse response,Exception ex) {
        log.error(ex.getMessage(), ex);
        int code = response.getStatus();
        String message = HttpStatus.valueOf(response.getStatus()).getReasonPhrase();
        return Result.error(code, message);
    }
}

13.4 ResultException 异常类

@Data
@AllArgsConstructor
public class ResultException extends Exception{

    ResultStatus status;
}

13.5 @JsonResponseBody 注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
@Documented
@ResponseBody
public @interface JsonResponseBody {

}

13.6 全局统一返回(关键)

@RestControllerAdvice
@Slf4j
public class JsonResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    private static final Class<? extends Annotation> ANNOTATION_TYPE = JsonResponseBody.class;

    /**
     * 判断类或者方法是否使用了 @JsonResponseBody
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ANNOTATION_TYPE) || returnType.hasMethodAnnotation(ANNOTATION_TYPE);
    }

    /**
     * 当类或者方法使用了 @JsonResponseBody 就会调用这个方法
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body instanceof String) {
            response.getHeaders().add("Content-Type", "application/json");
            return JSON.toJSONString(Result.success(body));
        }
        if (body instanceof Result) {
            return body;
        }
        return Result.success(body);
    }

    /**
     * 提供对标准Spring MVC异常的处理
     *
     * @param ex      the target exception
     * @param request the current request
     */
    @ExceptionHandler(Exception.class)
    public final ResponseEntity<Result> exceptionHandler(Exception ex, WebRequest request) {
        log.error("ExceptionHandler: {}", ex.getMessage());
        if (ex instanceof ResultException) {
            return handleResultException((ResultException) ex);
        }
        // TODO: 2019/10/05 galaxy 这里可以自定义其他的异常拦截
        return handleException(ex, new HttpHeaders(), request);
    }

    /**
     * 对ResultException类返回返回结果的处理
     */
    protected ResponseEntity<Result> handleResultException(ResultException ex) {
        Result body = Result.error(ex.getStatus());
        return new ResponseEntity<>(body, HttpStatus.OK);
    }

    /**
     * 异常类的统一处理
     */
    protected ResponseEntity<Result> handleException(Exception ex, HttpHeaders headers, WebRequest request) {
        Result body = Result.error(500,"Internal Server Error");
        HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR;
        request.setAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE, ex, WebRequest.SCOPE_REQUEST);
        return new ResponseEntity<>(body, headers, status);
    }
}

13.5 Controller 返回 String 类型引发的异常问题

方式一

就是我们在我们自己写的beforeBodyWrite的方法中,将返回值用 Result 包装过后再将Result 转为 String 类型进行返回。

@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
    if (body instanceof String) {
        response.getHeaders().add("Content-Type", "application/json");
        return JSON.toJSONString(Result.success(body));
    }
    if (body instanceof Result) {
        return body;
    }
    return Result.success(body);
}

方式二

处理 spring 框架提供的转换器,如下代码中的方式,任选其一即可。

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // 第一种方式是将 json 处理的转换器放到第一位,使得先让 json 转换器处理返回值,这样 String转换器就处理不了了。
        converters.add(0, new MappingJackson2HttpMessageConverter());
        // 第二种就是把String类型的转换器去掉,不使用String类型的转换器
        converters.removeIf(httpMessageConverter -> httpMessageConverter.getClass() == StringHttpMessageConverter.class);
    }
}
这篇关于SpringBoot学习笔记的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!