入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)

注:本篇笔记较长,所有涉及到知识仅作为入门的简单了解,帮助扩宽知识面,以便在能用上的场景可以有大体解决方向
本篇项目的giee:https://gitee.com/ywq869819435/springboot-hodgepodge-primer

快速创建Spring Boot

1.在官网下载

不好用还是需要导入idea

2.idea创建

创建一个新项目

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第1张图片
入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第2张图片

Group: 表示什么样项目类型(com【表示公司】.公司名)

Artifact: 项目名称

Type: 选择工程类型

Packaging: 选择导出包的形式 这里是jar包

Name:项目名称 Description:描述

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第3张图片

选择一些常用的组件

这里选择Web的Spring Web、SQL的Spring Data JDBC、MySQL Driver
入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第4张图片

项目名称以及存储路径,之后完成创建

设置导入依赖以及配置

file->setting

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第5张图片

勾掉图中这个,才可以导入依赖成功

之后配置application文件

spring:
  datasource:
    url: jdbc:mysql://127.0.0.1:3306/ecp-test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true&useSSL=false&allowMultiQueries=true
    username: root
    password: 123456
    driver-class-name: com.mysql.jdbc.Driver

之后运行即可

Maven简单了解

不使用maven的jar导入

首先从网上下载需要的jar包,下载放入项目的lib下面,然后加到项目的classpath下面的

依赖冲突

a->b表示a包要依赖b包才能导入

A->B->C->D1 ,E->F->D2 这时候有存在依赖冲突D1,D2只是版本上的不同,这时候我们就需要手动删除某些jar非常麻烦,当使用maven去处理的时候会选择路径最短的依赖,但是如果想保留D1也是可以的,在pom.xml文件中对应的依赖加入如下方式(以某个依赖包为列)来去除指定依赖

<dependency>
	<grounpld>org.apache.hadoopgrounpld>
    <artifactld>zookeeperartifactld>
    <version>3.3.1version>
    <exclusions>
        <exclusion>
            <groupld>jlinegroupld>
            <artifactld>jlineartifactld>
        exclusion>
    exclusions>
dependency>

依赖管理文件

  • pom文件为基本的依赖管理文件
  • resouces 资源文件
    • statics 静态资源
    • templates 模板资源
    • application.yml 配置文件
  • myspringbootApplication程序的入口。

spring boot的基本依赖


<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0modelVersion>
    
    <parent>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-parentartifactId>
        <version>2.3.1.RELEASEversion>
        <relativePath/> 
    parent>

    
    <groupId>com.mycompanygroupId>
    <artifactId>myspringbootartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <name>myspringbootname>
    <description>Demo project for Spring Bootdescription>

    
    <properties>
        <java.version>1.8java.version>
    properties>

    
    <dependencies>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jdbcartifactId>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>
        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
            
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintagegroupId>
                    <artifactId>junit-vintage-engineartifactId>
                exclusion>
            exclusions>
        dependency>
    dependencies>

    
    <build>
        
        <plugins>
            
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
            plugin>
        plugins>
    build>

project>

其中spring-boot-starter-web-parent不仅包含spring-boot-starter,还自动开启了web功能。

功能演示

举个栗子,比如你引入了Thymeleaf的依赖,spring boot 就会自动帮你引入Spring Template Engine(模板依赖),当你引入了自己的Spring Template Engine,spring boot就不会帮你引入。它让你专注于你的自己的业务开发,而不是各种配置。

@RestController
public class HelloController {

    @RequestMapping("/")
    public String index() {
        return "Greetings from Spring Boot!";
    }

}

启动SpringbootFirstApplication的main方法,打开浏览器localhost:8080,浏览器显示:

Greetings from Spring Boot!

神奇之处(相比SSM)

  • 你没有做任何的web.xml配置。
  • 你没有做任何的sping mvc的配置; springboot为你做了。
  • 你没有配置tomcat ;springboot内嵌tomcat.
  • 因为Spring Boot的自动装配为你节省了很多繁琐的配资文件的编写

Spring Boot特点

约定大于配置

约定大于配置是一种开发原则,就是减少人为的配置,直接用默认的配置就能获得我们想要的结果,eg:默认连接池,默认端口,默认配置文件名称,默认日志文件名称

约定好工程目录结构、配置文件位置【可以有多个配置文件对应不同的环境】、启动类的固定位置

  • application-test.yml:测试环境
  • application-dev.yml:开发环境
  • application-rc.yml:预生产环境
  • application-prod.yml:生产环境

application.yml配置内容

spring:
  profiles:
    active: dev #当有多个配置文件的时候,表明选用那个配置文件(查找里面有dev的yml文件,首先选择dev的配置属性,若dev里没有则在application.yml配置文件取)

application-dev.yml

spring:
  datasource: #配置数据源
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/ecp-test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
    username: root
    password: 123456

server:
  port: 8080 #设置端口号 

总结约定:

  1. maven机构目录
    1. 默认的resources文件夹下是放资源文件
    2. resources 下的static放静态资源文件
    3. templates放动态的资源文件
    4. 默认的生成的class 文件存在target目录下
  2. 我们的主启动类必须置于业务代码的最外层,否则我们写的一些类不能被扫描注册到bean
  3. 我们的配置文件命名必须是application
  4. 日子文件必须是logback.xml或者logback-spring.xml(推荐后者,因为后者加载晚于启动类,可以取到全部类的变量)
  5. spring boot 内嵌tomcat且默认端口号为8080

工程目录介绍

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第6张图片

项目包
	idea的配置
	maven的相关配置
	项目所有文件内容
		项目内容
			java代码内容
				包名
					项目名
						业务分类包
						启动类(一定置项目名下)
			resources
				static[存放一些静态资源,如图片]
				templates[动态资源,比如访问的资页面]
				application.yml[配置文件]
	targer是编译后的文件
    ......
    pom.xml [maven配置文件]
    工程用的jar包

由于Spring Boot的Bean的扫描原则是根据启动类自上而下的扫描,所以启动类必须在业务包的最外层

自动配置

内置tomcat在导入依赖包中有体现

启动类代码:[自动配置的入口]

@SpringBootApplication
public class MyspringbootApplication {

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

注解跟着原码进入

@SpringBootApplication

@Target({ElementType.TYPE}) //注解的使用范围
@Retention(RetentionPolicy.RUNTIME) //注解的生成周期
@Documented //用它来生成工具文档
@Inherited //表示标注类型可以被继承
//这个四个是java的原注解
@SpringBootConfiguration //配置类不需要了解
@EnableAutoConfiguration //进去这里查看
@Import({AutoConfigurationImportSelector.class})
//导入那些组件的选择器
@Import({Registrar.class}) //自动配置包

跟随着注解的减少最后只剩下一个非原注解的之后就会发现[ctrl + F8查看方法返回值]一个扫描注解的方法。这些自动配置就是常说的约定

去除掉某些默认配置类的方法

@SpringBootApplication(exclude - {...}) //去除自动配置某个配置

YML基础与配置扩展

三种配置格式比较

properties的优先级比yml要高一些

等价格式演示:

<server>
	<port>8090port>
    <servelet>
    	<context-path>/myspringbootcontext-path>
    servelet>
server>
server.port=8090
server.servelet.context-path= /myspringboot

使用properties的时候可能会出现中文乱码,这是idea的问题,需要改一下设置如下

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第7张图片

yml:

server:
  port: 8090 #设置端口号
  serverlet:
    context-path: /myspringboot #配置上下文路径

相比来yml层级关系明显,以数据为中心

语法

  1. 冒号后一定有一个空格,下一级就按回车
  2. 对大小写敏感
  3. 使用缩进代表层级关系[不使用table,一般是2-4个空格]

扩展属性配置

server:
  port: 8080 #设置端口号
  serverlet:
    context-path: /myproject #配置上下文路径[命名可以改变],后面没有对应的配置默认找index.html

logging: #设置日志
  level:  #打印等级
    root: info

person: #自定义
  name: ywq
  age: 21
  readBook: 《java》,《C++》

获取yml中的值的方式

可以接受list的方式,用逗号隔开

方法一:

用value获取单个值

@Value("${server.port}")
private Integer port;

@Test
void getValue(){
    System.out.println(port);
}

方法二:

导入依赖

<dependency>
    <groupId> org.springframework.boot groupId>
    <artifactId> spring-boot-configuration-processor artifactId>
    <optional> true optional>
dependency>

写注解

@ConfigurationProperties(prefix = "person")
@Component

输出的方法

private Person person;

@Test
void getValue(){
	System.out.println(person);
}

一般会把自定义的这种值单独放一个配置文件里方便管理,获取的时候多加一个参数

myperson.yml

person: #自己设置的一个
  name: ywq
  age: 21
  readBook: 《java》,《C++》
//指定改实体类获取值的路径
@PropertySource(value = {"classpath:myperson.yml"})
@ConfigurationProperties(prefix = "person")
@Component

两种方式各有优劣,自行选择

Spring Boot集成工具

集成Druid连接池

在springboot里面默认支持的三种连接池:dbcp、tomcat[boot1.5之前默认用这个]、hikari

【数据访问】

Druid连接池简介

是阿里巴巴开源的一个数据源,主要用于java数据库连接池,相比于Spring推荐的默认数据库连接池,在市场上占绝对优势;Druid数据源由于有强大的监控特性、可拓展性等特点被广泛使用。即使HikariCP的性能比Druid优秀,但是因为Druid包括许多维度的统计和分析功能,所以大家都选择使用Druid的更多

查看默认连接池

在不更改任何连接池配置文件的前提下查看默认连接池,使用Test单元测试查看

@SpringBootTest
class MyspringbootApplicationTests {

    @Autowired
    private DataSource dataSource;

    @Test
    void contextLoads() throws SQLException {
        Connection connection = dataSource.getConnection();
        System.out.println(connection);
    }
}

在这里插入图片描述

默认连接池为HikarProxy

连接Druid连接池

导入依赖

有两种依赖一个druid和spring-boot-start-druid【spring-boot-start-xxx都是springboot提供的包,而xxx-spring-boot-start则是xxx专门为spring boot开发的包,如果是springboot的包应该选用带springboot的】两种不同的依赖包


<dependency>
    <groupId>com.alibabagroupId>
    <artifactId>druid-spring-boot-starterartifactId>
    <version>1.1.10version>
dependency>

引进带start的包可以免去一些配置文件的配置,不带的就需要手动配置许多文件

配置Druid

配置yml
spring:
	#配置druid连接池
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      initial-size: 10
      #初始化连接池大小
      max-active: 100
      #最大连接数
      min-idle: 10
      #最小连接数
      max-wait: 60001
      #连接超时等待时间
      pool-prepared-statements: false
      #是否开启PSCache[某个缓存处理]
      time-between-eviction-runs-millis: 60000
      #配置间隔多久进行一次检查是否有需要关闭的链接
      min-evictable-idle-time-millis: 300000
      #配置一个连接在池中存在最小存在时间
      filter: stat,wall,log4j2
      #配置一些扩展插件[监控统计、防SQL注入、日志]

配置后重启查看是否配置成功

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第8张图片

可以看到已经生效。

com.mysql.cj.jdbc.Driver版本5之前的路径不一样,之后要用这个

配置config文件

在业务包下建一个config包然后创建对应的配置java文件

/**
 * @Description 连接池配置类
 * @Author ywq
 */
@Configuration
public class DruidConfiguration {

    private final Logger logger = LoggerFactory.getLogger(DruidConfiguration.class);

    @Bean
    public ServletRegistrationBean druidServlet() {
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
        //IP白名单
        servletRegistrationBean.addInitParameter("allow","*");
        //IP黑名单(共同存在时,deny优于allow)
        servletRegistrationBean.addInitParameter("deny","192.168.1.100");
        //控制台管理用户名和密码
        servletRegistrationBean.addInitParameter("loginUsername","admin");
        servletRegistrationBean.addInitParameter("loginPassword","admin");
        //是否能够重置数据,禁用HTML页面上的“Reset All”功能
        servletRegistrationBean.addInitParameter("resetEnable","false");
        return servletRegistrationBean;
    }

    @Bean
    public FilterRegistrationBean filterRegistrationBean(){
        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
        //监控一些Url请求
        filterRegistrationBean.addUrlPatterns("/*");
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}

配置完成后查看druid监控界面

http://localhost:8080/druid/index.html

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第9张图片

集成MyBatis/Hibernate

MyBatis与Hibernate

MyBatis优势:

1. 可以进行更为细致的sql优化,可以减少查询字段

2. 容易掌握,而Hibernate门槛较高
3. 占有绝大部分的中国软件市场

Hibernate优势:

  1. DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射
  2. 对对象的维护和缓存要比Mybatis好,对增删查改的对象的维护要方便
  3. 数据库移植性很好,Mybatis的数据库移植性不好,不同的数据库需要写不同sql

【数据访问】

声明式事物[Transactional]

两者都带有对应sql 的事物机制,即当程序执行异常的时候进行数据的回滚,就是在有这个声明的开始,整个运行本次程序过程中只要中间任意一步出现异常就会让其运行到改变全部改回原来的状态,即回到没有运行的状态。

使用也很简单,只要加上注解@Transactional就可以了,可以设置其接受的异常范围

eg:

 @Transactional(rollbackFor = Exception.class)

配置MyBatis

导入依赖

这里仍选择Spring boot starter的包(含有自动配置)


<dependency>
    <groupId>org.mybatis.spring.bootgroupId>
    <artifactId>mybatis-spring-boot-starterartifactId>
    <version>2.0.1version>
dependency>

配置yml

mybatis:
  #mapper.xml所在位置
  mapperLocations: classpath:mybatis/mapper/**/*Mapper.xml
  configuration:
    mapUnderscoreToCamelCase: true  #大小写驼峰转换
    call-setters-on-nulls: true #设置查询结果为null的时候是否返回
    jdbcTypeForNull: VARCHAR #当没有指定传入参数的时候默认为varchar

配置好后就可以进行mybatis编写sql


DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mycompany.myspringboot.user.dao.UserMapper">
    <select id="queryUserNameList" resultType="String">
        SELECT
            user_name
        FROM
            t_sys_user
    select>
mapper>

注意当使用一些特写字符的时候用这个装起来,这个会按文本形式处理

还有注意关于各个层级之间的注解写好。

Mybatis写sql的标签

主要标签如下

  1. select对应注解@Select
  2. 对应注解@Update
  3. 对应注解@Insert
  4. 对应注解@Delete
  5. :在某些条件根据入参有无决定时可使用,以避免1=1这种写法,也会根据是否为where条件后第一个条件参数自动去除and
  6. :类似于java中的条件判断if,没有标签
  7. 标签



  8. :可以对数组、Map或实现了Iterable接口(如List、Set)的对象遍历。可实现in、批量更新、批量插入等。
  9. :映射结果集
  10. :映射结果类型,可是java实体类或Map、List等类型。

配置Hibernate(了解)

导入依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-jpaartifactId>
dependency>


<dependency>
    <groupId>mysqlgroupId>
    <artifactId>mysql-connector-javaartifactId>
    <scope>runtimescope>
dependency> 

配置yml

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
    username: root
    password: 123456

  jpa:
    hibernate:
      ddl-auto: update  # 第一次简表create  后面用update
    show-sql: true

注意,如果通过jpa在数据库中建表,将jpa.hibernate,ddl-auto改为create,建完表之后,要改为update,要不然每次重启工程会删除表并新建。

完成以上步骤后,实验一个例子

创建实体类

通过@Entity 表明是一个映射的实体类, @Id表明id, @GeneratedValue 字段自动生成

@Entity
public class Account {
    @Id
    @GeneratedValue
    private int id ;
    private String name ;
    private double money;

//...  省略getter setter
}
Dao层

数据访问层,通过编写一个继承自 JpaRepository 的接口就能完成数据访问,其中包含了几本的单表查询的方法,非常的方便。值得注意的是,这个Account 对象名,而不是具体的表名,另外Interger是主键的类型,一般为Integer或者Long

public interface AccountDao  extends JpaRepository<Account,Integer> {
}
Web层

在这个栗子中我简略了service层的书写,在实际开发中,不可省略。新写一个controller,写几个restful api来测试数据的访问。

@RestController
@RequestMapping("/account")
public class AccountController {

    @Autowired
    AccountDao accountDao;

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public List<Account> getAccounts() {
        return accountDao.findAll();
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.GET)
    public Account getAccountById(@PathVariable("id") int id) {
        return accountDao.findOne(id);
    }

    @RequestMapping(value = "/{id}", method = RequestMethod.PUT)
    public String updateAccount(@PathVariable("id") int id, @RequestParam(value = "name", required = true) String name,
                                @RequestParam(value = "money", required = true) double money) {
        Account account = new Account();
        account.setMoney(money);
        account.setName(name);
        account.setId(id);
        Account account1 = accountDao.saveAndFlush(account);

        return account1.toString();

    }

    @RequestMapping(value = "", method = RequestMethod.POST)
    public String postAccount(@RequestParam(value = "name") String name,
                              @RequestParam(value = "money") double money) {
        Account account = new Account();
        account.setMoney(money);
        account.setName(name);
        Account account1 = accountDao.save(account);
        return account1.toString();
    }

}

通用Mapper集成

是什么

通用mapper是为了方便开发的时候去避免写一些简单单表增删改查,里面有集成的增删改查方法。属于mybatis的扩展

导入依赖


<dependency>
    <groupId>tk.mybatisgroupId>
    <artifactId>mapper-spring-boot-starterartifactId>
    <version>2.1.5version>
dependency>

修改dao和entity代码

对应的Dao层Mapper继承Mapper[tk包的],Mapper<表明对应的实体类>

@Mapper
public interface UserMapper extends tk.mybatis.mapper.common.Mapper<User> {
    List<String> queryUserNameList();
}

对应的entity代码需要注解表明和主键

@Table(name = "t_sys_user")
public class User {
    @Id
    private String userId;

    private String userName;
}

都修改完成后,运行结果如下:

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第10张图片

当使用的通用mapper之后需要更改MapperScan的包为

import tk.mybatis.spring.annotation.MapperScan;

PageHelper集成

是什么

PageHelper是分页工具,在开发过程中经常使用

导入依赖


<dependency>
    <groupId>com.github.pagehelpergroupId>
    <artifactId>pagehelper-spring-boot-starterartifactId>
    <version>1.2.10version>
dependency>

配置yml

pagehelper:
  helper-dialect: mysql #数据库类型[数据库分页原理不同,mysql是limit,sql server是top]
  reasonable: true #分页合理化
  support-methods-arguments: true #是否支持接口参数来传递分页参数,默认false
  page-size-zero: true #当设置为true的时候,如果pageSize设置为0(或RowBounds的limit=0,就不执行分页)

修改Controller和Service代码

Controller:

@GetMapping("/queryUserNameList")
public PageInfo<String> queryUserNameList(@RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "0") Integer pageSize){
    return userService.queryUserNameList(pageNum,pageSize);
}

@RequestParam设置默认参数值

Service:

public PageInfo<String> queryUserNameList(Integer pageNum, Integer pageSize){
    //设置分页的页数和页码,一定要放在查询前,且仅对一条sql进行分页
    PageHelper.startPage(pageNum,pageSize);
    List<String> list = userMapper.queryUserNameList();
    //返回一些分页的信息
    PageInfo<String> pageInfo = new PageInfo<>(list);
    return pageInfo;
}

Lombok集成

简介

在项目开发的阶段往往存在实体类的属性的变化以及字段的增删等,这时候存在多次实体类变动,我们就要频繁的对其对应的getter和setter进行修改,这时候就有了这个Lombok插件,通过注解为我们省去手写的getter和setter,是一个编译级别的插件,在编译源码的时候自动帮我们生成对应方法

引入Lombok

导入依赖报错后,需要通过idea->file->settting->pulsing下载对应插件


<dependency>
    <groupId>org.projectlombokgroupId>
    <artifactId>lombokartifactId>
    <version>1.18.10version>
    <scope>providedscope>
dependency>

当导入依赖报错后需要安装如下插件:

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第11张图片

修改实体类注解

@Table(name = "t_sys_user")
@Setter
@Getter
public class User {
    @Id
    private String userId;

    private String userName;
}

也可以直接写@Data,这里会自动提供get、set、toString、equals的重写

相关注解

@Data

需要导入Lombok依赖或者idea下载次插件,主要作用是提高代码的简洁,使用这个注解可以省去代码中大量的get()、 set()、 toString()、equals、hashCode、canEqua方法;

@Setter

注解在属性或者实体类上,提供set方法

@Getter

注解在属性或者实体类上,提供get方法

@EqualsAndHashCode

注解在类上,提供对应的equals和hashCode方法

@AllArgsConstructor

使用后添加一个构造函数,该构造函数含有所有已声明字段属性参数

@NoArgsConstructor

使用后创建一个无参构造函数

@NonNull

如果给参数加上这个注解,参数为null会抛出空指针异常

@Value

与@Data类似,区别在于它会把所有的成员变量默认定义为private final修饰,并且不会生成set方法

测试注解

@Slf4j
@RestController
@RequestMapping("/lombokTest")
public class LombokTest {

    @GetMapping("/query")
    //参数前可以有多个注解
    public String query(@NonNull @RequestParam("name") String name){
        log.info("用户姓名是" + name);
        return log.toString();
    }
}

当有传入name参数的时候

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第12张图片

当不传入name参数的时候

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第13张图片

原理

不能改变java代码运行机制,只是编译源码的时候去对应的语法树中找到代码里写的注解然后匹配语法规则生成相应的源码,这样编译完成后自动生成一些代码。

注意问题

  1. idea2017以前的版本不支持lombok
  2. 使用Lombok时,编译器可能会报错,报错就需要安装Lombok插件,选择星级最高即可
  3. 参数的处理往往都根据项目需求来进行,该用Value就用,该手写的还是要手写

当下载插件还是不可以编译的时候,开启idea启用注解编程,勾选上表示开启

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第14张图片

使用Spring Cache集成redis

缓存作用

缓和较慢存储的高频请求,缓解数据库压力,提升响应速率。

Spring Cache + redis优势

spring cache是Spring对缓存的封装,适用于EHCache、Redis、Guava等缓存技术。主要是可以使用注解的方式来处理缓存,eg:单使用Redis进行缓存,查询数据,如果查到需要写一些判断,而如果使用Spring Cache注解来处理,则可以省去这些判断。【spring cache之间数据不互通,而redis是分布的缓存,可以互通数据】

引入Spring Cache + Redis

导入Spring Cache依赖

依赖本号最后带有RELEASE表示当前稳定的版本


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-cacheartifactId>
    <version>2.2.2.RELEASEversion>
dependency>

测试Spring Cache

这里的value表示对应的缓冲区的名称,key是存储对应内容【这里是缓存当前的方法名对应的返回结果】

@Cacheable(value = "mycache",key = "#root.methodName")
@GetMapping("/queryUserNameList")
public PageInfo<String> queryUserNameList(@RequestParam(defaultValue = "0") Integer pageNum, @RequestParam(defaultValue = "0") Integer pageSize){
    System.out.println("进入查询+++++++++++++++++++++++++++++++++++++++++++++++++");
    return userService.queryUserNameList(pageNum,pageSize);
}

当连续执行两次后,后台只打印了一次“进入查询”就表示生效

注意:需要在启动类上加开启缓存的注解@EnableCaching否则不会生效

导入Redis依赖

当不指定版本的时候会在这里找可用的版本信息

2.1.5之前版本的redis的连接池是jedis,之后就用了lettuce


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-data-redisartifactId>
    <version>2.2.5.RELEASEversion>
dependency>

<dependency>
    <groupId>org.apache.commonsgroupId>
    <artifactId>commons-pool2artifactId>
dependency>

配置yml

spring:
  datasource: #配置数据源
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/walk_bookstore?useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true&useSSL=false&allowMultiQueries=true&serverTimezone=UTC
    username: root
    password: 123456
    #配置druid连接池
    type: com.alibaba.druid.pool.DruidDataSource
    druid:
      #初始化连接池大小
      initial-size: 10
      #最大连接数
      max-active: 100
      #最小连接数
      min-idle: 10
      #连接超时等待时间
      max-wait: 60001
      #是否开启PSCache[某个缓存处理]
      pool-prepared-statements: false
      #配置间隔多久进行一次检查是否有需要关闭的链接
      time-between-eviction-runs-millis: 60000
      #配置一个连接在池中存在最小存在时间
      min-evictable-idle-time-millis: 300000
      #配置一些扩展插件[监控统计、防SQL注入、日志]
      filter: stat,wall,log4j2
  cache:
    type: REDIS  #选择处理缓存的工具
  redis:
    host: 127.0.0.1
    port: 6379 #默认是这个端口号
    #password: 没有设置密码不需要配置这个
    database: 8 #使用几号redis库
    timeout: 600 #连接池最大的连接数,若使用负值表示没有限制
    lettuce:
      pool:
        max-active: 50 #连接池最大数
        max-wait: -1 #连接池最大阻塞等待时间,若使用负值表示没有限制
        max-idle: 8 #连接池中的最大空闲连接
        min-idle: 0 #连接池中的最小空闲连接

查看结果

配置完重启后发现缓存已经存入Redis中

在这里插入图片描述

运行删除缓存

@CacheEvict(value = "mycache", allEntries = true, beforeInvocation = true)
@GetMapping("/deleteCache")
public String deleteCache(){
    System.out.println("进入删除++++++++++++++++");
    return "删除成功";
}

之后再看redis刷新那个缓存就没有了

Spring Cache的其他注解

@CacheEvict
  1. value = 要删除的缓冲区名,也可以写上对应key去删除具体的缓冲区的那个模块
  2. allEntries = true 表示清楚这个缓存模块下的所有缓存
  3. beforeInvocation = true 表示在这个方法执行之前先执行删除缓存,防止方法报错导致删除失败

Redis中get值显示16进制问题

原因:

spring-data-redis的RedisTemplate模板类在操作redis时默认使用JdkSerializationRedisSerializer来进行序列化

解决:

使用其他类序列化redis的key和value,因为RedisTemplate默认是用字节方式序列化,可以用泛型解决RedisTemplate,也可以直接使用StringRedisTemplate进行Redis的相关操作。

自己写一个redis的configuration然后重写关于序列化的地方

在这里插入图片描述

集成Swagger2在线调试

为什么?

在传统的开发模式中,每个系统准备一份接口文档,方便各个团队之间的交流。但是由于软件的发展,需求功能越来越多,导致接口数量也越来越多,开发的人也越来越杂,维护接口文档就变成了一个很大的负担。

简介

Swagger是一款通过注解的方式生成Restful APIs交互界面的工具

优点:

  1. 减少了我们文档的编写
  2. 同时能够实现接口测试,接口修改的同时能够维护文档,可以很好的避免信息不一致

缺点:

  1. 代码侵入严重,在源代码的基础上要写更多的注解

导入依赖

swagger2核心jar包和UI界面包


<dependency>
    <groupId>io.springfoxgroupId>
    <artifactId>springfox-swagger2artifactId>
    <version>2.9.2version>
dependency>

<dependency>
    <groupId>io.springfoxgroupId>
    <artifactId>springfox-swagger-uiartifactId>
    <version>2.9.2version>
dependency>

配置Swagger2Config

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
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.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.HashSet;

@Configuration
@EnableSwagger2
@ConditionalOnBean(Swagger2Config.class)
public class Swagger2Config {

    @Autowired
    private AppConfig appConfig;

    /**
     * 全局设置Content Type,默认是application/json
     * 如果想只针对某个方法,则注释掉改语句,在特定的方法加上下面信息
     * @ApiOperation(consumes="application/x-www-form-urlencoded")
     */
    public static final HashSet<String> consumes = new HashSet<String>() {{
        add("application/x-www-form-urlencoded");
    }};

    @Bean(value = "commonApi")
    public Docket commonApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .groupName("通用公共类接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.mycompany.myspringboot.controller.common"))
                .paths(PathSelectors.any())
                .build()
                //.securityContexts(Lists.newArrayList(securityContext())).securitySchemes(Lists. newArrayList(apiKey()))
                .consumes(consumes);
    }

    @Bean(value = "systemApi")
    public Docket systemApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .groupName("系统权限管理接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.mycompany.myspringboot.controller.system"))
                .paths(PathSelectors.any())
                .build()
                //.securityContexts(Lists.newArrayList(securityContext())).securitySchemes(Lists. newArrayList(apiKey()))
                .consumes(consumes);
    }

    @Bean(value = "ecpBaseDataApi")
    public Docket ecpBaseDataApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .groupName("基础数据管理")
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.com.bgyfw.ecp.controller.config"))//扫描包
                .paths(PathSelectors.any())
                .build()
                //.securityContexts(Lists.newArrayList(securityContext())).securitySchemes(Lists. newArrayList(apiKey()))
                .consumes(consumes);
    }

    @Bean(value = "businessBaseDataApi")
    public Docket businessBaseDataApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .groupName("用户信息模块")
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.mycompany.myspringboot.user.controller"))//扫描包
                .paths(PathSelectors.any())
                .build()
                //.securityContexts(Lists.newArrayList(securityContext())).securitySchemes(Lists. newArrayList(apiKey()))
                .consumes(consumes);
    }
    
    @Bean(value = "scheduleApi")
    public Docket scheduleApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .groupName("外部调用定时同步测试接口")
                .select()
                .apis(RequestHandlerSelectors.basePackage("cn.com.bgyfw.ecp.controller.synch"))
                .paths(PathSelectors.any())
                .build()
                //.securityContexts(Lists.newArrayList(securityContext())).securitySchemes(Lists. newArrayList(apiKey()))
                .consumes(consumes);
    }

    /**
     * 添加摘要信息
     */
    private ApiInfo apiInfo() {
        // 用ApiInfoBuilder进行定制
        return new ApiInfoBuilder()
                //标题
                .title("swagger2Demo文档")
                //简介
                .description("")
                //服务条款
                .termsOfServiceUrl("")
                //作者个人信息
                .contact(new Contact(appConfig.name, null, null))
                .version("版本号:" + appConfig.version)
                .build();
    }
}

这里对应的值要在application.yml里配置

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

/**
 * @author ywq
 * @version v1.0.0
 * @date 2020-06-23 17:37
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "app")
public class AppConfig {

    public String name;
    public String version;
    public boolean checkAccessToken;
    public boolean autoToken;

    //下载路径
    public String downloadPath;
    //上传路径
    public String uploadPath;
}

配置完成后就可以开始使用注解

常用注解

@Api

用在请求的类上,表示对类的说明,描述Controller的作用

tags=“说明该类的作用,可以在UI界面上看到的注解”

value=“该参数没什么意义,在UI界面上也看到,所以不需要配置”

@Api(tags = "用户信息模块")

@ApiOperation

用在请求的方法或者接口上,说明方法的用途、作用

value=“说明方法的用途、作用”

notes=“方法的备注说明”

@ApiOperation(value = "用户名查询", notes = "如果不传任何分页参数就不分页,分页必须传pageNum,pageSize",httpMethod = "GET")

@ApiImplicitParams

用在请求的方法上,表示一组参数说明

@ApiImplicitParam:用在@ApiImplicitParams注解中,指定一个请求参数的各个方面
name:参数名
value:参数的汉字说明、解释
required:参数是否必须传
paramType:参数放在哪个地方
· header --> 请求参数的获取:@RequestHeader
· query --> 请求参数的获取:@RequestParam
· path(用于restful接口)–> 请求参数的获取:@PathVariable
· body(不常用)
· form(不常用)
dataType:参数类型,默认String,其它值dataType=“Integer”
defaultValue:参数的默认值

@ApiImplicitParams({
        @ApiImplicitParam(name = "pageNum",value = "第几页", dataType = "int",defaultValue = "0"),
        @ApiImplicitParam(name = "pageSize",value = "一页几条", dataType = "int",defaultValue = "0")
})

还有一些其他的注解可以去了解,查看当前结果

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第15张图片

其他部分注解简介

  • @ApiParam:单个参数描述
  • @ApiModel:用对象来接收参数
  • @ApiProperty:用对象接收参数时,描述对象的一个字段
  • @ApiResponse:HTTP响应其中1个描述
  • @ApiResponses:HTTP响应整体描述
  • @ApiIgnore:使用该注解忽略这个API
  • @ApiError :发生错误返回的信息

logback集成

简介

区分日志门面和具体的日志框架

slf4j就是简单的日志门面框架,只提供接口,没有具体的实现。具体的日志功能有具体的日志框架去实现[logback,log4j],使用Slf4j【门面】有个很大的好处,当你想切换其他日志框架的时候,原来的代码几乎不用更改。

logback与log4j比较

更快的执行速度,更少的内存

导入依赖

spring boot本身都有导入很多的日志框架依赖,当你需要更新版本的时候才去导入新的日志依赖

配置yml

my:
  log:
    path: t:/myspringboot

logback-spring-dev.xml文件






<configuration  scan="true" scanPeriod="60 seconds">
    <contextName>logbackcontextName>

    
    <property name="log.path" value="G:/logs/pmp" />

    
    
    <conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
    <conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
    <conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
    
    <property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>

    
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>debuglevel>
        filter>
        <encoder>
            <Pattern>${CONSOLE_LOG_PATTERN}Pattern>
            
            <charset>UTF-8charset>
        encoder>
    appender>

    
    
    <appender name="DEBUG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${log.path}/web_debug.logfile>
        
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
            <charset>UTF-8charset> 
        encoder>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            
            <fileNamePattern>${log.path}/web-debug-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MBmaxFileSize>
            timeBasedFileNamingAndTriggeringPolicy>
            
            <maxHistory>15maxHistory>
        rollingPolicy>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>debuglevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

    
    <appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${log.path}/web_info.logfile>
        
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
            <charset>UTF-8charset>
        encoder>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            
            <fileNamePattern>${log.path}/web-info-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MBmaxFileSize>
            timeBasedFileNamingAndTriggeringPolicy>
            
            <maxHistory>15maxHistory>
        rollingPolicy>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>infolevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

    
    <appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${log.path}/web_warn.logfile>
        
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
            <charset>UTF-8charset> 
        encoder>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/web-warn-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MBmaxFileSize>
            timeBasedFileNamingAndTriggeringPolicy>
            
            <maxHistory>15maxHistory>
        rollingPolicy>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>warnlevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

    
    <appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        <file>${log.path}/web_error.logfile>
        
        <encoder>
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
            <charset>UTF-8charset> 
        encoder>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${log.path}/web-error-%d{yyyy-MM-dd}.%i.logfileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MBmaxFileSize>
            timeBasedFileNamingAndTriggeringPolicy>
            
            <maxHistory>15maxHistory>
        rollingPolicy>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERRORlevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

    

    

    

    
    
    <springProfile name="dev">
        <logger name="com.sdcm.pmp" level="debug"/>
    springProfile>

    <root level="info">
        <appender-ref ref="CONSOLE" />
        <appender-ref ref="DEBUG_FILE" />
        <appender-ref ref="INFO_FILE" />
        <appender-ref ref="WARN_FILE" />
        <appender-ref ref="ERROR_FILE" />
    root>

    

configuration>

查看结果

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第16张图片

Restful风格的接口

为了方便与前端各种各样的设备层进行通信,就有了这个统一的机制。

例举几个类型

  1. GET(SELECT): 从服务器取出资源(一项或多项)
  2. POST(CREATE): 在服务器创建一个资源.
  3. PUT(UPDATE): 在服务器更新资源(客户端提供改变后的完整资源)
  4. PATCH(UPDATE): 在服务器更新资源(客户端提供改变的属性)
  5. DELETE(DELETE): 从服务器删除资源
  6. OPTIONS: 获取信息,关于资源的那些属性是客户端可以改变的

附上代码示例

都是同样的路径,但是请求方式不一样

Controller:

@GetMapping(value = "/user")
public List<User> findUser(){
    return userService.getUserNameList();
}

@PostMapping(value = "/user")
public int addUser(@RequestParam("name") String name){
    User user = new User();
    user.setUserId("202006241002");
    user.setPhone("123123123");
    user.setUserName(name);
    return userService.addUser(user);
}

@PatchMapping(value = "/user/{userCode}")
public int updateUser(@PathVariable("userId") String userId,@RequestParam("name") String name){
    User user = new User();
    user.setPhone("1231234564");
    user.setUserId(userId);
    user.setUserName(name);
    return userService.updateUser(user);
}

@DeleteMapping(value = "/user/{userCode}")
public int deleteUser(@PathVariable("userId") String userId){
    return userService.deleteUser(userId);
}

Service:

public List<User> getUserNameList(){
    List<User> list = userMapper.selectAll();
    return list;
}

public int addUser(User user){
    return userMapper.insert(user);
}

public int updateUser(User user){
    return userMapper.updateByPrimaryKey(user);
}

public int deleteUser(String userId){
    return userMapper.deleteByPrimaryKey(userId);
}

查询成功

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第17张图片

新增成功

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第18张图片

修改成功(这个只修改一条用PATCH,注意url的不同),查看数据库也有改变

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第19张图片

删除成功

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第20张图片

异常处理

spring boot自身带有对应的异常处理,但是这个异常处理的返回信息提示非常不友好,直接反回Http状态码500(让页面崩溃的那种),而在实际过程中我们往往只需要返回一个错误提示就好,就是http的状态码为200,返回错误的信息就可以。这时候我们需要自己定义异常处理。

自定义异常处理

@ControllerAdvice定义统一的异常处理类,这样就不必在每个Controller中逐个定义AOP去拦截处理异常。

@ExceptionHandler用定义函数针对的异常类型,最后将Exception对象处理成自己想要的结果

理想结果,返回错误信息http请求状态码是200

编写异常处理

@ControllerAdvice
public class ResfulApiExceptionHandler {
    /**
     * 设置请求参数错误的时候返回的异常信息,这样处理http的状态码是200,不过返回信息是500
     * @param request
     * @param exception
     * @return
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    @ResponseBody
    public Map<String, Object> requestExceptionHandler(HttpServletRequest request,MissingServletRequestParameterException exception){
        Map<String, Object> error = Maps.newHashMap();
        error.put("status",500);
        error.put("messager","参数" + exception.getParameterName() + "错误");
        return error;
    }

    /**
     * 找不到具体错误的,就返回这个异常信息
     * @param request
     * @param exception
     * @return
     */
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public Map<String, Object> exceptionHandler(HttpServletRequest request,Exception exception){
        Map<String, Object> error = Maps.newHashMap();
        error.put("status",500);
        error.put("messager","系统错误,请联系管理员");
        return error;
    }
}

这时候故意写错一个请求参数返回结果为

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第21张图片

故意写一个非请求参数的异常

@PatchMapping(value = "/user/{userId}")
public int updateUser(@PathVariable("userId") String userId,@RequestParam("name") String name){
    User user = new User();
    user.setPhone("1231234564");
    user.setUserId(userId);
    user.setUserName(name);
    if (name.length() > 5){
        int a = 1/0;   //!!!!!!!
    }
    return userService.updateUser(user);
}

返回结果是:

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第22张图片

编写统一返回结果

AppResult

@Data
public class AppResult<T> {
    private int code;
    private String message;
    private T data;

}

AppResultBuilder[构建体]

public class AppResultBuilder {

    public static <T> AppResult<T> successNoData(ResultCode code){
        AppResult<T> result = new AppResult<T>();
        result.setCode(code.getCode());
        result.setMessage(code.getMessage());
        return result;
    }

    public static <T> AppResult<T> success(T t,ResultCode code){
        AppResult<T> result = new AppResult<T>();
        result.setCode(code.getCode());
        result.setMessage(code.getMessage());
        result.setData(t);
        return result;
    }
    
     public static <T> AppResult<T> fail(ResultCode code,String error){
        AppResult<T> result = new AppResult<T>();
        result.setCode(code.getCode());
        result.setMessage(code.getMessage() + ": " + error);
        return result;
    }
}

ResultCode[枚举体]

package com.mycompany.myspringboot.config;

public enum ResultCode {

    SUCCESS(200,"成功"),//成功
    FAIL(400,"失败"),//失败
    CHECK_FAIL(405,"数据检查异常"),//数据检查异常
    UNAUTHORIZED(401,"未认证(签名错误)"),//未认证(签名错误)
    NOT_FOUND(404,"接口不存在"),//接口不存在
    INTERNAL_SERVER_ERROR(500,"服务器内部错误"),//服务器内部错误
    OK(0,"成功"), //成功
    NOK(500,"失败"); //失败

    public int code;

    public String message;

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

    public int getCode() {
        return code;
    }

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

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

嵌入业务代码

@PatchMapping(value = "/user/{userId}")
public AppResult updateUser(@PathVariable("userId") String userId, @RequestParam("name") String name){
    User user = new User();
    user.setPhone("1231234564");
    user.setUserId(userId);
    user.setUserName(name);
    if (name.length() > 5){
        //            int a = 1/0;
        return AppResultBuilder.fail(ResultCode.FAIL,"用户名长度大于5");
    }
    int cnt = userService.updateUser(user);
    return AppResultBuilder.success(cnt, ResultCode.SUCCESS);
}

结果如下:

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第23张图片

自定义注解

使用自定义注解 + AOP进行开发

下面示范一个列子

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第24张图片

步骤一:定义注解

自定义注解一定要加上java原注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RequestTypeHandler {
    String value();
}

步骤二:所在类加注解

注解对应包含要实现的那些类

@Component
@RequestTypeHandler("BIP2B341")
@Slf4j
public class RequireSaveServiceImpl extends AbstractRequestTypeHandler {
    @Override
    @ResponseBody
    public void handler(HttpServletRequest request, HttpServletResponse response) throws Exception{
        log.info("现在进入了报文下发接口");
    }
}
@Component
@RequestTypeHandler("BIP2B342")
@Slf4j
public class SeeLookServiceImpl extends AbstractRequestTypeHandler {
    @Override
    @ResponseBody
    public void handler(HttpServletRequest request, HttpServletResponse response) throws Exception{
        log.info("现在进入了报文审阅接口");
    }
}

步骤三:AOP拦截

[AOP拦截注解类解析进行bean注册实例化]

写了注解的请求类型处理器(选择要做那件事)

/**
 * 注解的请求类型处理器
 * 注解的类型和对应实现类放入map[注册到Bean工厂]来处理选择用哪个实现类
 */
@Slf4j
@Component
public class RequestTypeHandlerContext implements ApplicationContextAware {
    @Autowired
    ApplicationContext applicationContext;

    private static final Map<String, Class> handlerMap = new HashMap<>(10);

    /**
     * 获取Bean是那个实现类
     * @param type
     * @return
     */
    public AbstractRequestTypeHandler getHandlerInstance(String type){
        //获取Bean的对象
        Class clazz = handlerMap.get(type);
        if (clazz == null){
            log.error("本次业务编码对应接口未找到:{}",type);
        }
        return (AbstractRequestTypeHandler) applicationContext.getBean(clazz);
    }

    /**
     * 设置Bean,注册Bean,就是对应的Type给出对应的实现类
     * @param applicationContext
     * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException{
        Map<String,Object> beans = applicationContext.getBeansWithAnnotation(RequestTypeHandler.class);
        if (beans != null && beans.size() > 0){
            for (Object serviceBean : beans.values()){
                String payType = serviceBean.getClass().getAnnotation(RequestTypeHandler.class).value();
                handlerMap.put(payType,serviceBean.getClass());
            }
        }
    }
}

为了让所有的具体实现类都统一,写了如下的抽象方法

public abstract class AbstractRequestTypeHandler {

    abstract public void handler(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

让具体实现类去继承他

对应的Controller

/**
 *  采用策略模式,避免过多的if-else
 *  提高扩展性,添加新的功能直接添加新的类,根据type选择对应的实现
 */
@RestController
@AllArgsConstructor
@Slf4j
@RequestMapping("/HttpService")
public class HttpServiceController {

    private final RequestTypeHandlerContext requestTypeHandlerContext;

    @RequestMapping(value = "/httpserver")
    @Transactional(rollbackFor = Exception.class)
    public void requestProcessor(HttpServletRequest request, HttpServletResponse response) throws Exception{
        String type = request.getParameter("type");
        this.requestTypeHandlerContext.getHandlerInstance(type).handler(request,response);
    }
}

结果如下:

在这里插入图片描述

任务[异步、定时、邮件]

异步任务

创建异步任务

  1. 书写异步任务的配置类

@EnableAsync:表示的是开启异步任务(多线程)功能; 注意名称前缀

  1. 在需要开启线程的方法或者类上,加注解:@Async

eg:

@Slf4j
@Component
@EnableAsync //这个注解可以写在启动类,但是启动类东西有些多,放这里表明这个方法启动就好
public class AsyncTask {
    @Async
    public void sendMessage() {
        try {
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        log.info("选择进行短信群发");
    }
}
@Slf4j
@RestController
@RequestMapping("/userController")
public class ResfulUserController {
    @GetMapping(value = "/user")
    public List<User> findUser(){
        asyncTask.sendMessage();
        System.out.println("执行查询开始");
        return userService.getUserNameList();
    }
}

注意:

  1. 当异步任务在同一个Controller层下的时候是不可以生效的,因为同一层下是不会被Spring AOP拦截的

eg:

@Slf4j
@RestController
@RequestMapping("/userController")
public class ResfulUserController {

    @Autowired
    private UserService userService;

    @GetMapping(value = "/user")
    public List<User> findUser(){
        this.sendMessage();
        System.out.println("执行查询开始");
        return userService.getUserNameList();
    }

    @Async
    public void sendMessage() {
        try {
            Thread.sleep(2000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        log.info("选择进行短信群发");
    }
}

配置线程池

不配置会存在的问题

当访问量很多的时候,每个请求都要调用异步的方法,那就要拿到相应的数量的处理异步的线程,大量的线程会给服务器造成很大压力。

解决方法:

在配置类中,ThreadPoolTaskExecutor类是一个线程池。配置一些线程池的参数,确保高效的运转。

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

    @Override
    public Executor getAsyncExecutor(){
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心线程池数量,方法
        executor.setCorePoolSize(7);
        // 最大线程数量
        executor.setMaxPoolSize(42);
        // 线程池的队列容量
        executor.setQueueCapacity(11);
        // 线程名称的前缀
        executor.setThreadNamePrefix("fyk-executor-");
        // 线程池对拒绝任务的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler(){
        return new SimpleAsyncUncaughtExceptionHandler();
    }
}

定时任务

创建定时任务有两种写法,一种是基于注解,一种是基于接口【当cron的值需要从数据库中读取的时候就必须要用这种方式】,在以后遇到更频繁改变的定时任务的时候需要用相关的第三方框架:Quart2

基于注解的定时任务

  1. 启动类加注解@EnableScheduling【可以加在对应要用的地方】
  2. 需要定时任务的类上面加@Scheduled(cron = “0/5 * * * * ?”)
@EnableScheduling
@Component
@Slf4j
public class MyScheduled {
    @Scheduled(cron = "0/5 * * * * ?")
    public void task(){
        log.info("定时任务");
    }

}

结果如下:

在这里插入图片描述

基于接口的定时任务

@Component
@Slf4j
@EnableScheduling
public class interfaceScheduled implements SchedulingConfigurer {

    @Mapper //为了方便随手写了dao层
    public interface CronMapper{
        @Select("SELECT cron FROM cron LIMIT 1")
        public String getCron();
    }

    @Autowired
    CronMapper cronMapper;

    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        scheduledTaskRegistrar.addTriggerTask(
                // 1. 添加定时任务内容(Runnable)
                () -> System.out.println("执行动态定时任务:" + LocalDateTime.now().toLocalDate()),
                // 2. 设置执行周期(Trigger)
                triggerContext -> {
                    // 2.1 从数据库获取执行周期
                    String cron = cronMapper.getCron();
                    // 2.2 合法性校验
                    if (StringUtils.isEmpty(cron)){
                        // 默认的定时方式
                    }
                    // 2.3 返回执行周期
                    return new CronTrigger(cron).nextExecutionTime(triggerContext);
                }
        );
    }
}

结果如下:

在这里插入图片描述

邮件任务

导入依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-mailartifactId>
dependency>

配置文件

(password: eeffuilmbtkujehh #QQ邮箱开通第三方登录的授权码)

spring:
  mail:
  username: [email protected]
  password: kathgcjopvzdbdfe  # 授权码
  host: smtp.qq.com
  properties:
    mail:
      smtp:
        ssl:
          enable: true

邮箱确认开启smtp

QQ邮箱设置-账号设置开启

我的邮箱号授权码:kathgcjopvzdbdfe

测试结果

@Test
public void sendMaill(){
    SimpleMailMessage mailMessage = new SimpleMailMessage();
    mailMessage.setSubject("注意");
    mailMessage.setText("????");
    mailMessage.setFrom("[email protected]");
    mailMessage.setTo("[email protected]");
    //发送邮箱
    mailSender.send(mailMessage);
}

发送图片

@Test
public void sendMaill2() throws MessagingException {
    MimeMessage message = mailSender.createMimeMessage();
    MimeMessageHelper helper = new MimeMessageHelper(message,true);
    helper.setSubject("注意");
    helper.setText("???",true);
    helper.setFrom("[email protected]");
    helper.setTo("[email protected]");
    helper.addAttachment("",new File("E:\\poo\\personal files\\实习资料\\学习\\LearnImage\\1368768-20190613220434628-1803630402.png"));
    mailSender.send(message);
}

防止重复提交本地锁

产生原因

存在服务器反应时间内仍接受到多次重复的请求,这时候会形成N个请求,每个请求都需要花时间,服务器压力只会越来越大。前端要控制,后端也要,数据库也要。以防万一。

实现方式

自定义注解+Spring AOP + Cache【AOP拦截】

导入依赖


<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
dependency>

创建Lock注解

在这里插入图片描述

String key() default

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface LocalLock {
    /**
     * 默认的key
     */
    String key() default "";
}

创建Lock拦截器(AOP)

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第25张图片

CacheBuilder.new Builder()构建出缓存对象在具体的interceptor()方法上采用的是Around(环绕增强)

还有其他的注解表明在方法执行的什么时候运行拦截内容如图所示

入门SpringBoot集成常用框架以及常见处理方式(括宽知识面)_第26张图片

import com.alibaba.druid.util.StringUtils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.mycompany.myspringboot.user.annotation.LocalLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * 配置拦截后动作内容
 * @author ywq
 */
@Aspect
@Configuration
public class LocalMethodInterceptor {
    /**
     * 创建缓存
     * maximumSize(180) //最大缓存个数
     * expireAfterWrite(5, TimeUnit.SECONDS) //缓存5秒过期
     */
    private static final Cache<String, Object> CACHE = CacheBuilder
            .newBuilder()
            .maximumSize(180)
            .expireAfterWrite(50, TimeUnit.SECONDS)
            .build();

    /**
     * Around表明是环绕增强,触发条件为任意的公共方法以及有对应[路径]的注解
     * @param proceedingJoinPoint
     * @return
     */
    @Around("execution(public * *(..)) && @annotation(com.mycompany.myspringboot.user.annotation.LocalLock)")
    public  Object interceptor(ProceedingJoinPoint proceedingJoinPoint){
        //获取切点
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        //获取拦截到的方法
        Method method = signature.getMethod();
        LocalLock localLock = method.getAnnotation(LocalLock.class);
        String key = getKey(localLock.key(),proceedingJoinPoint.getArgs());
        if (!StringUtils.isEmpty(key)){
            if (CACHE.getIfPresent(key) != null){
                //如果有这个key,则抛出不要重复提交
                throw new RuntimeException("请勿重复提交");
            }
            //放入缓存
            CACHE.put(key,key);
        }
        try{
            return proceedingJoinPoint.proceed();
        }catch (Throwable throwable){
            throwable.printStackTrace();
            throw new RuntimeException("服务器异常");
        }
    }

    /**
     * key的生成策略
     */
    private String getKey(String keyExpress,Object[] args){
        for (int i = 0; i < args.length ; i++ ){
            keyExpress = keyExpress.replace("arg[" + i + "]",args[i].toString());
        }
        return keyExpress;
    }
}

注意抛出异常的时候,自己如果有重写异常抛出情况,就需要加上一个异常抛出类型Runtime的

import com.google.common.collect.Maps;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@ControllerAdvice
public class ResfulApiExceptionHandler {
    //...

    /**
     * 运行时异常
     * @param request
     * @param exception
     * @return
     */
    @ExceptionHandler(value = RuntimeException.class)
    @ResponseBody
    public Map<String, Object> requestExceptionHandler(HttpServletRequest request,RuntimeException exception){
        Map<String, Object> error = Maps.newHashMap();
        error.put("status",500);
        error.put("messager", exception.getMessage());
        return error;
    }
	//...
}

添加注解

@LocalLock(key = “book:arg[0]”);意味着会将arg[0]替换成第一个参数的值,生成后的新key将被缓存起来

@PostMapping(value = "/user")
@LocalLock(key = "book:arg[0]")
public int addUser(@RequestParam("name") String name){
    User user = new User();
    user.setUserId("202006241002203");
    user.setPhone("123123123");
    user.setUserName(name);
    return userService.addUser(user);
}

防止重复提交分布式锁

产生原因

当服务器集群,单机的Cache就不能生效,也就是服务器之间的Cache不互通【其实解决方式就是用第三方缓存,redis等等】

【AOP可以说一个动态代理,对类实现预处理,如果符合预期要求就执行,不符合就不让执行】

实现方式

自定义注解 + Spring Aop + Redis

优化:根据注解获取指定唯一参数

创建Lock注解

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface RedisLock {
    /**
     * redis锁的key  前缀
     */
    String perfix() default "";
}

创建Lock拦截器

import com.alibaba.druid.util.StringUtils;
import com.mycompany.myspringboot.user.annotation.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

@Aspect
@Configuration
@Slf4j
public class RedisMethodInterceptor {
    @Autowired
    private RedisTemplate<String, Object> template;

    @Around("execution(public * *(..)) && @annotation(com.mycompany.myspringboot.user.annotation.RedisLock)")
    public Object interceptor(ProceedingJoinPoint proceedingJoinPoint){
        ValueOperations<String, Object> opsForValue = template.opsForValue();
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = signature.getMethod();
        RedisLock lock = method.getAnnotation(RedisLock.class);
        String key = getKey(lock.perfix(),proceedingJoinPoint.getArgs());
        if (!StringUtils.isEmpty(key)){
            if (opsForValue.get(key) != null){
                log.error("表单重复提交了");
                throw new RuntimeException("请勿重复提交");
            }
            //设置键为key,值为key,时间为50,单位为秒
            opsForValue.set(key,key,50, TimeUnit.SECONDS);
        }
        try{
            return proceedingJoinPoint.proceed();
        }catch (Throwable throwable){
            throwable.printStackTrace();
            throw new RuntimeException("服务器异常");
        }
    }

    /**
     * key的生成策略
     */
    private String getKey(String keyExpress,Object[] args){
        for (int i = 0; i < args.length ; i++ ){
            keyExpress = keyExpress.replace("arg[" + i + "]",args[i].toString());
        }
        return keyExpress;
    }

}

添加注解

@PostMapping(value = "/user")
//    @LocalLock(key = "book:arg[0]")
@RedisLock(perfix = "redis")
public int addUser(@CacheParam(key = "name") @RequestParam("name") String name){
    User user = new User();
    user.setUserId("202006241002203");
    user.setPhone("123123123");
    user.setUserName(name);
    return userService.addUser(user);
}

优化获取key值

创建获取key注解

@Target({ElementType.PARAMETER,ElementType.METHOD}) //表明可以注解在方法和参数上
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface CacheParam {
    /**
     * 获取字段名字 为指定唯一
     */
    String key() default "";
}

创建getKey拦截器

import com.alibaba.druid.util.StringUtils;
import com.mycompany.myspringboot.user.annotation.CacheParam;
import com.mycompany.myspringboot.user.annotation.RedisLock;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

@Component
public class LocalKeyGenerator {
    /**
     *
     * @param proceedingJoinPoint
     * @return
     */
    public String getLockKey(ProceedingJoinPoint proceedingJoinPoint){
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = signature.getMethod();
        RedisLock redisLock = method.getAnnotation(RedisLock.class);
        final Object[] args = proceedingJoinPoint.getArgs();
        final Parameter[] parameters = method.getParameters();
        StringBuilder builder = new StringBuilder();
        //获取RedisLock里面指定的参数
        for (int i = 0; i < parameters.length ; i++){
            final CacheParam annotation = parameters[i].getAnnotation(CacheParam.class);
            if (annotation == null){
                continue;
            }
            builder.append(":").append(args[i]);
        }
        //获取实体里面包含的RedisLock里面指定的参数
        if (StringUtils.isEmpty(builder.toString())){
            final Annotation[][] parameterAnnotation = method.getParameterAnnotations();
            for (int i = 0; i < parameterAnnotation.length ;i++){
                final Object object = args[i];
                final Field[] fields = object.getClass().getDeclaredFields();
                for (Field field : fields){
                    final CacheParam annotation = field.getAnnotation(CacheParam.class);
                    if (annotation == null){
                        continue;
                    }
                    field.setAccessible(true);
                    builder.append(":").append(ReflectionUtils.getField(field,object));
                }
            }
        }
        return redisLock.perfix() + builder.toString();
    }
}

使用CacheParam注解

修改Lock连接器获取key方式

@Around("execution(public * *(..)) && @annotation(com.mycompany.myspringboot.user.annotation.RedisLock)")
public Object interceptor(ProceedingJoinPoint proceedingJoinPoint){
    ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
    //        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
    //        Method method = signature.getMethod();
    //        RedisLock lock = method.getAnnotation(RedisLock.class);
    String key = localKeyGenerator.getLockKey(proceedingJoinPoint);
    if (!StringUtils.isEmpty(key)){
        if (opsForValue.get(key) != null){
            log.error("表单重复提交了");
            throw new RuntimeException("请勿重复提交");
        }
        //设置键为key,值为key,时间为50,单位为秒
        opsForValue.set(key,key,50, TimeUnit.SECONDS);
    }
    try{
        return proceedingJoinPoint.proceed();
    }catch (Throwable throwable){
        throwable.printStackTrace();
        throw new RuntimeException("服务器异常");
    }
}

修改调用分布锁的Controller的参数注解

@PostMapping(value = "/user")
//    @LocalLock(key = "book:arg[0]")
@RedisLock(perfix = "redis")
public int addUser(@CacheParam(key = "name") @RequestParam("name") String name){
    User user = new User();
    user.setUserId("202006241002203");
    user.setPhone("123123123");
    user.setUserName(name);
    return userService.addUser(user);
}

你可能感兴趣的:(spring,boot,mybatis,java,hibernate)