spring boot web

1,142 阅读13分钟

SpringBoot对静态资源的映射规则

访问网上的静态资源映射

  • 所有/webjars/**,都去classpath:/META-INF/resources/webjars/找资源;webjars 文件代表以jar包的形式去引入静态资源,具体见webjars;

  • webjars 使用方式很简单,以 jquery 引入为例:

    // pom.xml
    <!-- 引入jQuery -->在访问的时候只需要写**/webjars/文件夹下面的资源名称即可
    <dependency>
    	<groupId></groupId>
    	<artifactId>jquery</artifactId>
    	<version>3.3.1</version>
    </dependency>
    

    访问http://localhost:8080/webjars/jquery/3.3.1/jquery.js即可访问到导入静态资源文件夹中的jquery.js文件

访问自身的静态资源文件映射

  • 所有访问/**访问当前项目的静态资源(css,img,js等文件),都会去以下路径查找是否含有该文件

    "classpath:/META-INF/resources/",
    "classpath:/resources/",
    "classpath:/static/",  // 习惯使用这个 **/src/main/resources/static/
    "classpath:/public/",  // 习惯使用这个 **/src/main/resource/public/
    "/" // 当前项目跟路径
    

    访问http://localhost:8080/assets/css/signin.css 其实是访问/src/main/resource/static/assets/css/signin.css

访问首页(欢迎页)文件映射

访问/默认显示的页面,也叫欢迎页面,都会去静态资源所在的路径寻找index.html文件显示出来

private Optional<Resource> getWelcomePage() {
    String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
    return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
private Resource getIndexHtml(String location) {
    return this.resourceLoader.getResource(location + "index.html");
}

网页tab图标资源映射favicon.ico

访问/favicon.ico会直接映射到classpath:/public|static|resources/favicon.ico

SpringBoot访问数据库数据

classpath:/resources/下的application.yml文件中配置数据库连接信息

spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://localhost:3306/jdbc
    driver-class-name: com.mysql.cj.jdbc.Driver
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
	<scope>runtime</scope>
</dependency>
  • mysql驱动类mysql-connector-java版本在6以上,那么 driver-class-name => com.mysql.cj.jdbc.Driver;
  • mysql驱动类mysql-connector-java版本6以下,那么 driver-class-name => com.mysql.jdbc.Driver;

默认数据源:

​ SpringBoot创建默认DataSource时,规则如下:

  • 优先寻找创建Tomcat连接池 // 默认先使用tomcat连接池
  • 如果没有Tomcat连接池,会查找创建HikariCP
  • 如果没有HikariCP连接池,会查找创建dbcp
  • 如果没有dbcp连接池,会查找创建dbcp2
  • 可以使用spring.datasource.type属性指定连接池类型数据源spring.datasource.type=org.apache.commons.dbcp.BasicDataSource

参考资料:

SpringBoot更换数据源-druid数据源(后台监控sql及web请求)

  • 引入druid,切换数据源为druid数据源

    type: com.alibaba.druid.pool.DruidDataSource // 指定为druid数据源
    // 其他属性
    #上半区公共部分对应的是 org.springframework.boot.autoconfigure.jdbc.DataSourceProperties 中的属性
    #下半区属性对应的是 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
    
  • 配置druid数据源自动装配

    // SpringBoot启动类SpringBootDataJdbcApplication的同级文件夹中创建config.DruidConfig.java文件
    package com.dyh.www.springbootjdbc.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.alibaba.druid.support.http.StatViewServlet;
    import com.alibaba.druid.support.http.WebStatFilter;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.boot.web.servlet.FilterRegistrationBean;
    import org.springframework.boot.web.servlet.ServletRegistrationBean;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    
    @Configuration
    public class DruidConfig {
    
        @ConfigurationProperties(prefix = "spring.datasource")
        @Bean
        public DataSource druidDataSource() {
            return new DruidDataSource();
        }
    
        // 配置druid的监控
        // 配置一个管理后台的servlet
        @Bean
        public ServletRegistrationBean statViewServlet() {
            ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
            Map<String, String> initParams = new HashMap<>();
            initParams.put("loginUsername", "admin");
            initParams.put("loginPassword", "123456");
            initParams.put("allow", "");
            initParams.put("deny", "");
            bean.setInitParameters(initParams);
            return bean;
        }
    
        // 配置一个web监控的拦截filter
        @Bean
        public FilterRegistrationBean webStatFilter() {
            FilterRegistrationBean bean = new FilterRegistrationBean();
            bean.setFilter(new WebStatFilter());
            Map<String, String> initParams = new HashMap<>();
            initParams.put("exclusions", "*.js, *.css, /druid/*"); // 静态资源js,css文件不拦截过滤
            bean.setInitParameters(initParams);
            bean.setUrlPatterns(Arrays.asList("/*"));
            return bean;
        }
    }
    

SpringBoot整合mybatis

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

注解版

// 这是操作数据库的mapper
@Mapper // 数据库与数据模型的映射注解,如果这里没有,则需要在SpringBootApplication启动类上添加扫描全局mapper的注解,并指定相应的扫描路径
public interface DepartmentMapper {
    // 数据库的增删改查sql接口数据映射
    @Select("select * from department")
    public List<Department> getDepartmentList();

    @Select("select * from department where id=#{id}")
    public Department getDepartmentById(Integer id);

    @Delete("delete from department where id=#{id}")
    public int deleteDepartmentById(Integer id);

    @Options(useGeneratedKeys = true, keyProperty = "id") // 指定主键
    @Insert("insert into department(departmentName) values(#{departmentName})")
    public int insertDepartment(Department department);

    @Update("update department set departmentName=#{departmentName} where id=#{id}")
    public int updateDepartment(Department department);
}
// 这里的 departmentName 对应数据库的 departmentName 字段
// 如果这里的 department_name 相对应数据库的 departmentName 字段 转换成驼峰命名方式,则可以增加转换的自动配置
import com.dyh.springbootmybatis.bean.Department;
import com.dyh.springbootmybatis.mapper.DepartmentMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
// web API接口
@RestController
public class DepartmentController {

    @Autowired
    DepartmentMapper departmentMapper;

    @RequestMapping(value = "/dept/{id}", method=RequestMethod.GET)
    public Department getDepartmentById(@PathVariable("id") Integer id) {
        return departmentMapper.getDepartmentById(id);
    }

    @RequestMapping(value = "/dept/list", method=RequestMethod.GET)
    public List<Department> getDepartmentList() {
        return departmentMapper.getDepartmentList();
    }

    @RequestMapping(value = "/dept", method = RequestMethod.GET)
    public Department insertDepartment(Department department) {
        departmentMapper.insertDepartment(department);
        return department;
    }
}
  • 转换的自动配置

    在config/文件夹下创建自定义mybatis的映射规则, 给Spring容器添加一个ConfigurationCustomizer组件即可

    @org.springframework.context.annotation.Configuration // 表明这是一个自动配置类
    public class MybatisConfig {
    
        @Bean // 表明这是一个Spring容器组件
        public ConfigurationCustomizer configurationCustomizer() {
            return new ConfigurationCustomizer() {
                @Override
                public void customize(Configuration configuration) {
                    configuration.setMapUnderscoreToCamelCase(true);
                }
            };
        }
    }
    
  • 使用MapperScan批量扫描mapper文件

    @MapperScan(value = "com.dyh.springbootmybatis.mapper") // 这里批量扫描mapper文件,并且使用value指定路径
    @SpringBootApplication
    public class SpringBootMybatisApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(SpringBootMybatisApplication.class, args);
    	}
    
    }
    

配置版

  • 以Employee对象为例,首先定义数据实体类,与数据库表字段一一对应

    public class Employee {
        private Integer id;
        private String lastName;
        private String email;
        private Integer gender;
        private Integer departmentId;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public Integer getGender() {
            return gender;
        }
    
        public void setGender(Integer gender) {
            this.gender = gender;
        }
    
        public Integer getDepartmentId() {
            return departmentId;
        }
    
        public void setDepartmentId(Integer departmentId) {
            this.departmentId = departmentId;
        }
    }
    
  • 建立操作数据库表接口的mapper,并统一在SpringBoot的主配置类中配置扫描mapper的路径

    import com.dyh.springbootmybatis.bean.Employee;
    
    import java.util.List;
    // API接口
    public interface EmployeeMapper {
        public Employee getEmployeeById(Integer id);
        public List<Employee> getEmployeeList();
        public Integer deleteEmployeeById(Integer id);
    }
    
  • 配置接口mapper与数据库表操作对应的sql语句xml文件

    • 配置application.yml文件指定xml文件所在路径

      # 引入mybatis配置版文件
      mybatis:
        config-location: classpath:config/mybatis-config/global.xml
        mapper-locations: classpath:config/mybatis-config/mapper/*.xml
      
    • 配置全局mybatis的配置文件xml

      <!-- global.xml文件 -->
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration
              PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-config.dtd">
      <configuration>
          <settings>
              <setting name="mapUnderscoreToCamelCase" value="true"/>
          </settings>
      </configuration>
      
    • 配置mybatis操作数据库表的sql语句,与接口API的方法行成映射,具体配置见文档

      <!-- Employee.xml -->
      <!-- 接口API映射配置文件 -->
      <?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.dyh.springbootmybatis.mapper.EmployeeMapper">
          <!-- 唯一标识记号id值为API接口的对应方法 -->
          <select id="getEmployeeById" resultMap="employeeMap">
              select * from employee where id = #{id}
          </select>
          <select id="getEmployeeList" resultMap="employeeMap">
              select * from employee
          </select>
          <delete id="deleteEmployeeById">
              delete from employee where id = #{id}
          </delete>
          <resultMap id="employeeMap" type="com.dyh.springbootmybatis.bean.Employee">
              <!-- 数据库表列字段与实体类的实例属性映射规则 -->
              <id property="id" column="id" />
              <result  property="lastName" column="lastName"/>
              <result  property="gender" column="gender"/>
              <result  property="email" column="email"/>
              <result  property="departmentId" column="departmentId"/>
          </resultMap>
      </mapper>
      

SpringBoot整合Jpa

目的

​ JPA是作为Spring Data一部分,用于简化数据库的curd和分页列表获取等数据操作的持久化框架,通过配置继承封装好数据操作的接口,为我们提供统一的API。

SpringBoot通过配置整合Jpa

JPA(ORM): Object Relational Mapping

  • 编写一个实体类(entity.User),将数据表的字段和实体类的字段进行映射,同时配置好映射规则(主键、自增、别名等)

    import javax.persistence.*;
    
    // 使用JPA注解配置映射关系
    @JsonIgnoreProperties(value = { "hibernateLazyInitializer", "handler" }) // 标记不生成json对象的属性
    @Entity
    @Table(name = "tbl_user") // @Table指定该实体类的数据跟哪个数据表对应,如果省略,默认数据库表名为该类名小写user
    public class User {
        @Id // 这是一个主键
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;
        @Column(name = "last_name", length = 50) // 代表这是和数据库表对应的一个列
        private String lastName;
        @Column // 省略则默认列名就是属性名
        private String email;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getEmail() {
            return email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    }
    
  • 编写一个Dao接口来操作实体类对应的数据表,称为Repository,包含常见数据的增删改查,分页等操作

    import com.dyh.springbootjpa.entity.User;
    import org.springframework.data.jpa.repository.JpaRepository;
    
    // 继承JpaRepository来继承对数据的操作接口
    public interface UserRepository extends JpaRepository<User, Integer> {
    }
    
  • application.yml基本配置

    # jpa关于sql相关配置
    spring:
      jpa:
        hibernate:
          # 更新或创建数据表
          ddl-auto: update
        # 在每次执行sql语句时打印出执行的sql语句
        show-sql: true
    
  • 接口文件

    import com.dyh.springbootjpa.entity.User;
    import com.dyh.springbootjpa.repostitoy.UserRepository;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    public class UserController {
    
        @Autowired
        UserRepository userRepository;
    
        @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
        public User getUserById(@PathVariable("id") Integer id) {
            User user = userRepository.getOne(id);
            return user;
        }
    
        @RequestMapping(value = "/user/save", method = RequestMethod.GET)
        public User insertUser(User user) {
            User newUser = userRepository.save(user);
            return newUser;
        }
    
        @RequestMapping(value = "/user/all", method = RequestMethod.GET)
        public List<User> getUserList() {
            List<User> userList = userRepository.findAll();
            return userList;
        }
    
        @RequestMapping(value = "/user/test", method = RequestMethod.GET)
        public String userTest() {
            long count = userRepository.count();
            return "success:" + count;
        }
    }
    

SpringBoot与日志

日志介绍

SpringBoot底层是Spring框架, Spring框架默认使用JCL作为日志抽象层;

SpringBoot选用SLF4作为日志抽象层,logback作为日志实现层;

  • Jul - Java util logging

    java.util.logging包下面的一款日志框架,属于JDK自带的日志实现;

  • Log4j

    Apache的一个开源项目,可以不需要依赖第三方技术,直接记录日志;

  • Log4j2

    log4j2和log4j是同一个作者开发,只不过log4j2是重新架构的一款日志组件,抛弃了log4j的不足,以及吸取了优秀的logback的设计重新推出的一款新组件;

  • Jcl - Jakarta Commons Logging

    jcl是apache公司开发的一个抽象日志通用框架,本身不实现日志记录,但是提供了记录日志的抽象接口方法(info,debug,error…)。jcl默认使用jul打印日志,如果项目依赖了log4j jar包,则使用log4j打印日志;

  • Logback

    Logback是由log4j创始人设计的一个开源日志组件。LogBack被分为3个组件,logback-core, logback-classic 和 logback-access;

  • Slf4j - Simple Logging Facade for Java

    Java的简单日志门面(SLF4J)用作各种日志框架的简单门面或抽象,像jul、logback和log4j等。SLF4J允许最终用户在部署时插入所需的日志框架,如果在项目中使用slf4j必须加一个依赖jar,即slf4j-api-xxx.jar。slf4j本身不记录日志,通过绑定器绑定一个具体的日志框架来完成日志记录。

    日志抽象层 日志实现层
    Jcl、Slf4j、Jboss-logging Log4j、 Jul、Log4j2、Logback

SLF4j的使用

日志记录方法的调用,不应该直接调用日志的实现类,而是应该调用日志抽象层里面的方法。

日志统一的冲突解决方法(日志框架适配图)

自己的应用(slf4j+logback), spring(commons-logging), Hibernate(jboss-logging), Mybatis各框架都使用了不同的日志框架,如何统一日志记录,使得所有的框架都使用slf4j输出日志,这是需要解决的问题。

将系统中其他日志框架先排除掉,

用中间包替换掉原有被排除的日志框架

再导入slf4j实现统一的日志输出

SpringBoot使用的日志框架

底层依赖的日志框架

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-logging</artifactId>
  <version>2.1.6.RELEASE</version>
  <scope>compile</scope>
</dependency>

依赖关系

总结:

SpringBoot使用slf4j+logback方式进行日志记录;

SpringBoot也把其他日志换成了slf4j;

引入其他日志框架,必须把本框架下的默认依赖的日志框架移除掉(如果有的话);

// 低版本的Spring-core移除commons-logging日志框架
// 高版本的Spring-core默认不集成commons-logging日志框架
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-core</artifactId>
  <exclusions>
    <exclusion>
      <groupId>commons-logging</groupId>
      <artifactId>commons-logging</artifactId>
    </exclusion>
  </exclusions>
</dependency>

如何在SpringBoot中使用日志

  • 日志级别

    Logger logger = LoggerFactory.getLogger(SpringbootLoggingApplication.class);
    // 日志的级别
    // 由低到高 trace < debug < info < warn < error
    // 在配置文件中可以调整输出的日志级别,日志就会在该级别及以上的高级别生效输出日志
    logger.trace("这是trace信息...");
    logger.debug("这是debug信息...");
    // 本版本的SpringBoot默认输出info级别及以上的日志, 配置文件中没有指定级别的话默认使用info级别,也叫root级别 - logging.level.root
    logger.info("这是info信息...");
    logger.warn("这是warn信息...");
    logger.error("这是error信息...");
    
  • 日志输出配置文件—application.properties

    # application.properties
    #日志输出级别
    logging.level.com.dyh = trace
    
    # 日志输出路径
    logging.path=/Users/dyh/IdeaProjects/mvn/springboot-logging
    
    # 日志输出至文件名, 当然可以直接指定带路径的文件名,这样日志输出路径可不指定
    logging.file=springboot-logging.log
    
    # 控制台输出日志的格式
    logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n
    
    # 文件中输出日志的格式
    logging.pattern.file=%d{yyyy-MM-dd HH:mm:ss.SSS} === [%thread] %-5level === %logger{50} === %msg%n
    
  • 日志输出格式说明

    • %clr(%5p) 表示输出日志的颜色(根据日志级别)

    • %d 表示日期时间

    • %thread 表示线程名

    • %-5level 表示日志级别level向左对齐显示五个字符宽度

    • %logger{50} 表示日志名字logger最长50个字符(日志所在的包名),否则按句点分割

    • %msg 日志输出消息

    • %n 换行符

  • 日志输出配置文件—logback.xml || logback-spring.xml

    虽然application.properties能进行日志的配置,但是为了更方便、独立地配置日志,我们一般使用单独的日志配置文件来进行日志配置,如下:

    Logging System Customization
    Logback logback-spring.xml, logback-spring.groovy, logback.xml, or logback.groovy
    Log4j2 log4j2-spring.xml or log4j2.xml
    JDK (Java Util Logging) logging.properties

    SpringBoot应用会默认识别logback.xml文件作为日志的配置文件,但是在logback.xml中无法使用SpringBoot配置的高级功能,如spring.profiles.active=dev, 即在不同环境中使用不同的日志配置文件,因此一般使用logback-spring.xml作为指定的日志配置文件

    <!-- logback-spring.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration debug="false">
        <!--自定义属性,可以在之后使用;定义日志文件的存储地址 勿在LogBack的配置中使用相对路径-->
        <property name="LOG_HOME" value="/Users/dyh/IdeaProjects/mvn/springboot-logging" />
        <!-- 控制台输出 -->
        <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符, %yellow() 定义某个输出信息的颜色-->
                <pattern>%yellow(%d{yyyy-MM-dd HH:mm:ss.SSS}) %highlight([%thread] %-5level) %green(%logger{50}) - %cyan(%msg%n)</pattern>
            </encoder>
        </appender>
        <!-- 按照每天生成日志文件 -->
        <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--日志文件输出的文件名-->
                <FileNamePattern>${LOG_HOME}/TestWeb-%d{yyyy-MM-dd}.log</FileNamePattern>
                <!--日志文件保留天数-->
                <MaxHistory>30</MaxHistory>
            </rollingPolicy>
            <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
            </encoder>
            <!--日志文件最大的大小-->
            <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                <MaxFileSize>10MB</MaxFileSize>
            </triggeringPolicy>
        </appender>
    
        <!-- 日志输出级别 -->
        <!--根据不同的环境输出不同级别的日志信息-->
        <springProfile name="dev"> 
        <!-- 在application.properties文件中指定 spring.profiles.active=dev -->
            <root level="DEBUG">
                <appender-ref ref="STDOUT" />
            </root>
        </springProfile>
        <springProfile name="prod">
         <!-- 在application.properties文件中指定 spring.profiles.active=prod -->
            <root level="WARN">
                <appender-ref ref="STDOUT" />
            </root>
        </springProfile>
    </configuration>
    

    切换日志框架

    按照日志适配图进行切换即可