文章目录
一、springboot工程的概述和构建
1.1.Spring的发展
1.1.1 Spring1.x 时代
在Spring1.x时代,都是通过xml文件配置bean,随着项目的不断扩大,需要将xml配置分放到不同的配置文件中,需要频繁的在java类和xml配置文件中切换。appliationContext.xml 所有的对象和对象之间的关系 全部用xml文件配置!
<bean id="userDAO" class="com.blog.dao.UserDAOImpl">
<property name="hibernateTemplate" ref="hibernateTemplate"></property>
</bean>
<bean id="userService" class="com.blog.service.UserServiceImpl">
<property name="userDAO">
<ref bean="userDAO"/>
</property>
</bean>
<bean id="loginAction" class="com.blog.action.LoginAction" scope="prototype">
<property name="userService">
<ref bean="userService"/>
</property>
</bean>
1.1.2 Spring2.x时代
随着JDK 1.5带来的注解支持,Spring2.x可以使用注解对Bean进行申明和注入,大大的减少了xml配置文件,同时也大大简化了项目的开发。
@Configuration
把一个类作为一个IoC容器,它的某个方法头上如果注册了@Bean,就会作为这个Spring容器中的Bean。@Scope
注解 作用域@Lazy(true)
表示延迟初始化@Service
用于标注业务层组件、@Controller
用于标注控制层组件(如struts中的action)@Repository
用于标注数据访问组件,即DAO组件。@Component
泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。@Scope
用于指定scope作用域的(用在类上)
那么,问题来了,究竟是应该使用xml还是注解呢?
最佳实践:
–应用的基本配置用xml,比如:数据源、加载资源文件等;
–业务开发用注解,比如:Service中注入bean等; @Resource @Autowire @Servevice @Controller…
项目:XML(数据源、加载资源文件) + 注解 (@Resource @Autowire @Servevice @Controller…
XML(基础的配置)+注解(对象之前的关系) 开发模式!!
1.1.3 Spring3.x到Spring4.x、Spring5.x
使用Java的类作为一个配置文件把传统 xml配置文件全部替换掉!
从Spring3.x开始提供了Java配置方式(使用Java中一个类来作为配置文件,使用类把XML文件替换),使用Java配置方式可以更好的理解你配置的Bean,现在我们就处于这个时代,并且Spring4.x和Spring boot都推荐使用java配置的方式。
后期开发的时候可以完全不用xml文件。全部用户注解和配置类方式完成!
绿皮火车------ Spring XML配置
动车 --------- Spring XML配置 + 注解配置
高铁----------SpringBoot 脚手架 零XML配置 SSM SSH SSS
SpringBoot简化传统的项目搭建方式~
1.2.SpringBoot快速入门
1.2.1.简介
Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。通过这种方式,Spring Boot致力于在蓬勃发展的快速应用开发领域(rapid application development)成为领导者的。
SpringBoot封装了底层,使用起来超级简单!底层原理需要看源码!
在以前的spring项目中,都会面对大量繁琐的配置,使用的时候基本上都是大量的复制黏贴。而Spring Boot 则能让我们在不需要过多的配置下,轻松快速地搭建Spring Web应用,开箱即用,没有代码生成,也无需XML配置,从而快速使用spring框架。
以往的spring应用往往需要大量的xml配置,为了改变这一现状spring团队引入了java config(主要依赖@Configuration、@Bean等注解)配置方案,但在整合第三方库时依然需要配置很多固定的Bean,这和xml配置一样仍然有些繁琐,因此spring boot项目应运而生,采用自动化配置方案简化spring应用开发的配置工作。
1.2.2 SpringBoot的特点
- 为所有 Spring 的开发者提供一个非常快速的、广泛接受的入门体验
- 绝对没有代码生成,也无需 XML 配置。
- 快速创建独立运行的Spring项目以及与主流框架集成
- 使用嵌入式的Servlet容器,应用无需打成WAR包
- starters(场景启动器)自动依赖与版本控制
- 大量的自动配置,简化开发,也可修改默认值
- 无需配置XML,无代码生成,开箱即用
- 准生产环境的运行时应用监控
- 与云计算的天然集成
- 与Spring Cloud (Alibaba)微服务框架天然集成
更多细节,大家可以到官网查看。
1.2.3.HelloWorld
需求:
浏览器发送hello请求,服务器接受请求并处理,响应Hello World字符串;
别着急,现在我们的项目与SpringBoot还没有什么关联。SpringBoot提供了一个名为spring-boot-starter-parent的工程,里面已经对各种常用依赖(并非全部)的版本进行了管理,我们的项目需要以这个项目为父工程,这样我们就不用操心依赖的版本问题了,需要什么依赖,直接引入坐标即可!
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bruceliu.springboot.hello</groupId>
<artifactId>springboot-hello</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/>
</parent>
</project>
- 导入spring boot 的Web启动器
为了让SpringBoot帮我们完成各种自动配置,我们必须引入SpringBoot提供的自动配置依赖,我们称为启动器。因为我们是web项目,这里我们引入web启动器:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
需要注意的是,我们并没有在这里指定版本信息。因为SpringBoot的父工程已经对版本进行了管理了。
这个时候,我们会发现项目中多出了大量的依赖:
这些都是SpringBoot根据spring-boot-starter-web这个依赖自动引入的,而且所有的版本都已经管理好,不会出现冲突。
- 管理jdk版本
默认情况下,maven工程的jdk版本是1.5,而我们开发使用的是1.8,因此这里我们需要修改jdk版本,只需要简单的添加以下属性即可.如果是1.8的JDK,那么可以不配置。
<properties>
<java.version>1.8</java.version>
</properties>
- 完整的POM文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.bruceliu.springboot.hello</groupId>
<artifactId>springboot-hello</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
/**
* @author bruceliu
* @create 2019-04-30 23:52
* @description
*/
@SpringBootApplication
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class, args);
}
}
/**
* @author bruceliu
* @create 2019-04-30 23:54
* @description
*/
@RestController
public class HelloController {
@GetMapping("hello")
public String hello(){
return "hello, spring boot!";
}
}
-
启动测试
接下来,我们运行main函数,查看控制台,并且可以看到监听的端口信息:
1)监听的端口是8080
2)SpringMVC的映射路径是:/
3)/hello路径已经映射到了HelloController中的hello()方法
1.2.4.Jar包部署
1.2.4.1.插件支持
<build>
<plugins>
<!-- springboot maven打包-->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
1.2.4.2.打包
1.2.4.3.运行
窗口运行:java -jar xxx.jar
后台运行(linux): nohup java -jar XXX.jar &
1.3.SpringBoot另外构建方式
1.3.1.方法一:在 Spring Boot官方Initializer页面在线构建工程再导入到Idea中
1.3.2.方法二:使用IDEA自动构建SpringBoot工程
直接在Idea中Create New Project
–> Spring Initializr
–> 填写group、artifact –>钩上web –> 点下一步就行了
IDE都支持使用Spring的项目创建向导快速创建一个Spring Boot项目;选择我们需要的模块;向导会联网创建Spring Boot项目;
默认生成的Spring Boot项目;
- 主程序已经生成好了,我们只需要我们自己的逻辑
- resources文件夹中目录结构
- static:保存所有的静态资源; js css images;
- templates:保存所有的模板页面;(Spring Boot默认jar包使用嵌入式的Tomcat,默认不支持JSP页面);可以使用模板引擎(freemarker、thymeleaf);
- application.properties:Spring Boot应用的配置文件;可以修改一些默认设置;
1.4.自定义Banner
启动Spring Boot项目后会看到这样的图案:
这个图片其实是可以自定义的:
现在该怎么做呢?
1.打开网站:
http://patorjk.com/software/taag/#p=display&h=1&f=Graffiti
2.拷贝生成的字符到一个文本文件中,并且将该文件命名为banner.txt
将banner.txt拷贝到项目的resources目录中:
3.重新启动程序,查看效果:
如果不想看到任何的banner,也是可以将其关闭的:
二、springboot的配置
SpringBoot全局配置文件默认为src/main/resources下的application.properties,另外它还可以重命名为.yml格式(即SpringBoot对着两种格式均支持)。
properties 属性文件 key=value
yml
2.1.修改默认配置
如修改SpringBoot内嵌Tomcat的启动端口为9080(.yml格式)
server:
port: 9080
启动项目即可在控制台启动日志中看到
2018-06-24 17:42:25.784 INFO 2658 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9080 (http) with context path ''
这时在浏览器输入localhost:9080即可正常访问
附Spring Boot 全部配置项 SpringBoot Common application properties
数据格式转换: https://www.toyaml.com/index.html
2.2.自定义属性配置
我们也可以在SpringBoot配置文件中自定义属性配置,如
com:
example:
girl:
age: 18
cupSize: B
name: hobby
然后通过@Value("${属性名}")
注解来加载对应的配置属性
com/example/springbootconfiguration/controller/GirlController.java
package com.bruceliu.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GirlController {
@Value("${com.example.girl.name}")
private String girlName;
@Value("${com.example.girl.age}")
private Integer girlAge;
@Value("${com.example.girl.cupSize}")
private String girlCupSize;
@GetMapping("/girl")
public String getGirlInfo() {
return "girlName: " + girlName + " girlAge: " + girlAge + " girlCupSize: " + girlCupSize;
}
}
启动工程,访问:localhost:9080/girl,浏览器显示:
girlName: baby girlAge: 18 girlCupSize: B
属性注入成功
2.3.属性配置间的引用
在SpringBoot全局配置文件中的各个属性之间可以通过直接引用来使用
com:
example:
girl:
name: baby
age: 18
cupSize: B
desc: name:${com.example.girl.name} age:${com.example.girl.age} cupSize:${com.example.girl.cupSize}
同样可以使用@Value注解将girl.desc属性配置注入到某一属性中,如
package com.bruceliu.controller;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GirlController {
@Value("${com.example.girl.name}")
private String girlName;
@Value("${com.example.girl.age}")
private Integer girlAge;
@Value("${com.example.girl.cupSize}")
private String girlCupSize;
@Value("${com.example.girl.desc}")
private String girlDesc;
@GetMapping("/girl")
public String getGirlInfo() {
// return "girlName: " + girlName + " girlAge: " + girlAge + " girlCupSize: " + girlCupSize;
return girlDesc;
}
}
再次启动工程,访问:localhost:9080/girl,浏览器显示:
name:baby age:18 cupSize:B
2.4.将属性配置赋给实体类
当我们属性配置很多的时候,使用@Value注解一个一个的注入将会变得很繁琐,这时SpringBoot提供了将属性配置与实体类结合的方式,具体先来看一下SpringBoot中官方的使用如org.springframework.boot.autoconfigure.data.redis.RedisProperties
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
package org.springframework.boot.autoconfigure.data.redis;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(
prefix = "spring.redis"
)
public class RedisProperties {
private int database = 0;
private String url;
private String host = "localhost";
private String password;
private int port = 6379;
private boolean ssl;
private int timeout;
private RedisProperties.Pool pool;
private RedisProperties.Sentinel sentinel;
private RedisProperties.Cluster cluster;
...
}
对于上面我们自己关于girl的一些配置,同理我们可以创建一个GirlProperties
类,如
package com.bruceliu.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "com.example.girl")
public class GirlProperties {
private String name;
private Integer age;
private String cupSize;
// ... getter and setters
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getCupSize() {
return cupSize;
}
public void setCupSize(String cupSize) {
this.cupSize = cupSize;
}
@Override
public String toString() {
return "GirlProperties{" +
"name='" + name + '\'' +
", age=" + age +
", cupSize='" + cupSize + '\'' +
'}';
}
}
当你在Idea中敲完这些代码Idea可能会提示Spring Boot Configuration Annotation Processor not found in classpath
点击Open Documentation
会打开Generating Your Own Metadata by Using the Annotation Processor
页面
嗯,主要是少了个依赖spring-boot-configuration-processor
主要是少了个依赖spring-boot-configuration-processor
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
如何使用?在需要使用的类上加@EnableConfigurationProperties
注解,同时使用@Autowired
注解注入即可,如
package com.bruceliu.controller;
import com.bruceliu.bean.GirlProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableConfigurationProperties(value = {GirlProperties.class})
public class GirlController {
@Autowired
private GirlProperties girlProperties;
@GetMapping("/girl2")
public GirlProperties getGirlInfo2() {
return girlProperties;
}
}
再次启动工程,访问:localhost:9080/girl2,浏览器显示:
2.5.使用随机值
Spring Boot的属性配置文件中可以通过${random}
来产生随机int
、long
、uuid
或者string
字符串,来支持属性的随机值。
比如我们给girl随机来个年龄
age: ${random.int}
2.6.自定义配置文件
虽然SprinBoot提供了application.properties或application.yml全局配置文件,但有时我们还是需要自定义配置文件,如将上文关于girl的属性配置提取到girl.properties文件中
girl.name: baby
girl.age: 18
girl.cupSize: B
GirlProperties2.java
package com.bruceliu.bean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
@Component
@PropertySource("classpath:girl.properties")
@ConfigurationProperties(prefix = "girl")
public class GirlProperties2 {
private String name;
private Integer age;
private String cupSize;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getCupSize() {
return cupSize;
}
public void setCupSize(String cupSize) {
this.cupSize = cupSize;
}
@Override
public String toString() {
return "GirlProperties2{" +
"name='" + name + '\'' +
", age=" + age +
", cupSize='" + cupSize + '\'' +
'}';
}
}
GirlController.java
package com.bruceliu.controller;
import com.bruceliu.bean.GirlProperties;
import com.bruceliu.bean.GirlProperties2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@EnableConfigurationProperties(value = {GirlProperties.class, GirlProperties2.class})
public class GirlController {
@Autowired
private GirlProperties2 girlProperties2;
@GetMapping("/girl3")
public GirlProperties2 getGirlInfo3() {
return girlProperties2;
}
}
再次启动工程,访问:localhost:9080/girl3,浏览器显示:
2.7.多环境配置
实际开发中可能会有不同的环境,有开发环境、测试环境、生产环境。对于每个环境相关配置都可能有所不同,如:数据库信息、端口配置、本地路径配置等。
如果每次切换不同环境都需要修改application.properties,那么操作是十分繁琐的。在spring boot中提供了多环境配置,使得我们切换环境变得简便。
在Spring Boot中多环境配置文件名需要满足application-{profile}.properties的格式,其中{profile}对应你的环境标识,比如:
application-test.yml:测试环境
server:
servlet:
context-path: /test
application-dev.yml:开发环境
server:
servlet:
context-path: /dev
application-prod.yml:生产环境
server:
servlet:
context-path: /prod
激活profile,在application.yml文件中通过spring.profiles.active属性来设置,其值对应{profile}值,如
spring:
profiles:
active: dev
这时启动应用访问localhost:9080/girl3
就访问不到了,需访问localhost:9080/dev/girl3
才行:
三、springboot整合mybatis
## 3.1.SpringBoot和MyBatis整合
3.1.2. 准备数据
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for singer
-- ----------------------------
DROP TABLE IF EXISTS `singer`;
CREATE TABLE `singer` (
`id` int(4) NOT NULL AUTO_INCREMENT,
`name` varchar(20) DEFAULT NULL,
`age` int(3) DEFAULT NULL,
`birthday` date DEFAULT NULL,
`works` varchar(50) NOT NULL,
`sex` char(1) DEFAULT NULL,
`idcard` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `idcard` (`idcard`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=gbk;
-- ----------------------------
-- Records of singer
-- ----------------------------
INSERT INTO `singer` VALUES ('1', '汪峰', '50', '2010-10-10', '飞得更高', '男', '34535434535675467567');
INSERT INTO `singer` VALUES ('2', '小A', '18', '2010-10-20', '双杰伦', '男', '345354345356754673');
INSERT INTO `singer` VALUES ('3', '哈林', '56', '2010-11-10', '情非得已', '男', '3453543453567543');
INSERT INTO `singer` VALUES ('4', '娜姐', '60', '2010-10-13', '征服', '女', '345354345356754676');
INSERT INTO `singer` VALUES ('7', '小A', '18', '2001-10-10', '乡村爱情进行曲', '男', '3454354354354385');
INSERT INTO `singer` VALUES ('8', '赵四', '55', '2001-10-10', '乡村爱情进行曲', '男', '3454354354354775');
INSERT INTO `singer` VALUES ('9', '赵四', '55', '2001-10-10', '乡村爱情进行曲', '男', '3454354366664775');
INSERT INTO `singer` VALUES ('10', '小沈阳', '55', '2001-10-10', '乡村爱情进行曲', '男', '3554354366664775');
INSERT INTO `singer` VALUES ('11', '小花', '18', '2010-10-13', '协奏曲', '女', '23423423424324');
3.1.2. 加入依赖
<!--导入MyBatis的场景启动器-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.28</version>
</dependency>
3.1.3. 加入Mapper接口
import com.bruceliu.bean.Singer;
import java.util.List;
/**
* @BelongsProject: SpringBoot-helloworld
* @BelongsPackage: com.bruceliu.mapper
* @CreateTime: 2020-10-19 15:23
* @Description: TODO
*/
public interface SingerMapper {
List<Singer> findSingers();
}
3.1.4. 加入Mapper接口XML文件
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bruceliu.mapper.SingerMapper">
<select id="findSingers" resultType="com.bruceliu.bean.Singer">
select * from singer
</select>
</mapper>
3.1.5. 数据库配置信息
# 配置数据的连接信息
spring.datasource.url=jdbc:mysql://127.0.0.1/singerdb?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
#MyBatis参数配置
# 指定Mapper.xml位置
mybatis.mapper-locations=classpath:mappers/*.xml
# 给实体类取别名
mybatis.type-aliases-package=com.bruceliu.bean
3.1.6 Mapper接口注解
@Mapper
public interface SingerMapper {
List<Singer> findSingers();
}
3.2.SpringBoot执行单元测试
3.2.1.导入测试依赖
<!--SpringBoot和Junit整合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
3.2.2.测试类
@RunWith(SpringRunner.class) //测试SpringBoot框架。启动SpringBoot框架
@SpringBootTest(classes = APP.class)
public class TestMyBatis {
@Resource
SingerMapper singerMapper;
@Test
public void test1(){
List<Singer> singers = singerMapper.findSingers();
for (Singer singer : singers) {
System.out.println(singer);
}
}
}
四、springboot整合thymeleaf
## 4.1.Thymeleaf简介
Thymeleaf是跟Velocity、FreeMarker类似的模板引擎,它可以完全替代JSP,相较与其他的模板引擎,它主要有以下几个特点:
-
Thymeleaf在有网络和无网络的环境下皆可运行,即它可以让美工在浏览器查看页面的静态效果,也可以让程序员在服务器查看带数据的动态页面效果。这是由于它支持 html 原型,然后在 html 标签里增加额外的属性来达到模板+数据的展示方式。浏览器解释 html 时会忽略未定义的标签属性,所以thymeleaf的模板可以静态地运行;当有数据返回到页面时,Thymeleaf 标签会动态地替换掉静态内容,使页面动态显示。
-
Thymeleaf开箱即用的特性。它提供标准和spring标准两种方言,可以直接套用模板实现JSTL、OGNL表达式效果,避免每天套模板、改jstl、改标签的困扰。同时开发人员也可以扩展和创建自定义的方言。
-
Thymeleaf提供spring标准方言和一个与SpringMVC完美集成的可选模块,可以快速的实现表单绑定、属性编辑器、国际化等功能。
Thymeleaf官网:http://www.thymeleaf.org
4.2.Thymeleaf整合SpringBoot
- 在pom.xml文件引入thymeleaf
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 在application.properties(application.yml)文件中配置thymeleaf
# thymeleaf
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.check-template-location=true
spring.thymeleaf.suffix=.html
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.cache=false
- 新建编辑控制层代码HelloController,在request添加了name属性,返回到前端hello.html再使用thymeleaf取值显示。
@Controller
public class HelloController {
@RequestMapping("/hello")
public String hello(HttpServletRequest request, @RequestParam(value = "name", defaultValue = "springboot-thymeleaf") String name) {
request.setAttribute("name", name);
return "hello";
}
}
- 新建编辑模板文件,在resources文件夹下的templates目录,用于存放HTML等模板文件,在这新增hello.html,添加如下代码。
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8"/>
<title>springboot-thymeleaf demo</title>
</head>
<body>
<p th:text="'hello, ' + ${name} + '!'" />
</body>
</html>
切记:使用Thymeleaf模板引擎时,必须在html文件上方添加该行代码使用支持Thymeleaf。
<html lang="en" xmlns:th="http://www.thymeleaf.org">
- 启动项目,访问http://localhost:8080/hello,看到如下显示证明SpringBoot整合Thymeleaf成功。
五、springboot整合swagger2
5.1.引言
由于Spring Boot能够快速开发、便捷部署等特性,相信有很大一部分Spring Boot的用户会用来构建RESTful API。而我们构建RESTful API的目的通常都是由于多终端的原因,这些终端会共用很多底层业务逻辑,因此我们会抽象出这样一层来同时服务于多个移动端或者Web前端。
这样一来,我们的RESTful API就有可能要面对多个开发人员或多个开发团队:IOS开发、Android开发或是Web开发等。为了减少与其他团队平时开发期间的频繁沟通成本,传统做法我们会创建一份RESTful API文档来记录所有接口细节,然而这样的做法有以下几个问题:
- 由于接口众多,并且细节复杂(需要考虑不同的HTTP请求类型、HTTP头部信息、HTTP请求内容等),高质量地创建这份文档本身就是件非常吃力的事,下游的抱怨声不绝于耳。
- 随着时间推移,不断修改接口实现的时候都必须同步修改接口文档,而文档与代码又处于两个不同的媒介,除非有严格的管理机制,不然很容易导致不一致现象。
为了解决上面这样的问题,本文将介绍RESTful API的重磅好伙伴Swagger2,它可以轻松的整合到Spring Boot中,并与Spring MVC程序配合组织出强大RESTful API文档。它既可以减少我们创建文档的工作量,同时说明内容又整合入实现代码中,让维护文档和修改代码整合为一体,可以让我们在修改代码逻辑的同时方便的修改文档说明。另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API。具体效果如下图所示:
下面来具体介绍,如果在Spring Boot中使用Swagger2。
5.2.需求准备
回顾并详细说明一下在SpringMVC中使用的@Controller、@RestController、@RequestMapping注解。
@Controller:修饰class,用来创建处理http请求的对象
@RestController:Spring4之后加入的注解,原来在@Controller中返回json需要@ResponseBody来配合,如果直接用@RestController替代@Controller就不需要再配置@ResponseBody,默认返回json格式。
@RequestMapping:配置url映射
下面我们尝试使用Spring MVC来实现一组对User对象操作的RESTful API,配合注释详细说明在Spring MVC中如何映射HTTP请求、如何传参、如何编写单元测试。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bruceliu.springboot.swagger2</groupId>
<artifactId>springboot-swagger2</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动类
package com.bruceliu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author bruceliu
* @create 2019-10-19 12:44
* @description
*/
@SpringBootApplication
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class,args);
}
}
实体类
package com.bruceliu.bean;
/**
* @author bruceliu
* @create 2019-10-19 12:48
* @description
*/
public class User {
private Long id;
private String name;
private Integer age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
实现对User对象的操作接口
package com.bruceliu.controller;
import com.bruceliu.bean.User;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* @author bruceliu
* @create 2019-10-19 12:49
* @description
*/
@RestController
@RequestMapping(value="/users") // 通过这里配置使下面的映射都在/users下
public class UserController {
// 创建线程安全的Map
static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());
@RequestMapping(value="/", method=RequestMethod.GET)
public List<User> getUserList() {
// 处理"/users/"的GET请求,用来获取用户列表
// 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
List<User> r = new ArrayList<User>(users.values());
return r;
}
@RequestMapping(value="/", method= RequestMethod.POST)
public String postUser(@ModelAttribute User user) {
// 处理"/users/"的POST请求,用来创建User
// 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数
users.put(user.getId(), user);
return "success";
}
@RequestMapping(value="/{id}", method=RequestMethod.GET)
public User getUser(@PathVariable Long id) {
// 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
// url中的id可通过@PathVariable绑定到函数的参数中
return users.get(id);
}
@RequestMapping(value="/{id}", method=RequestMethod.PUT)
public String putUser(@PathVariable Long id, @ModelAttribute User user) {
// 处理"/users/{id}"的PUT请求,用来更新User信息
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return "success";
}
@RequestMapping(value="/{id}", method=RequestMethod.DELETE)
public String deleteUser(@PathVariable Long id) {
// 处理"/users/{id}"的DELETE请求,用来删除User
users.remove(id);
return "success";
}
}
5.3.整合swagger2
5.3.1.导入依赖
在pom.xml中加入Swagger2的依赖
<!-- swagger-spring-boot -->
<dependency>
<groupId>com.spring4all</groupId>
<artifactId>swagger-spring-boot-starter</artifactId>
<version>1.7.0.RELEASE</version>
</dependency>
5.3.2.application.yml配置
在Application.java同级创建Swagger2的配置类Swagger2。
swagger.base-package=com.bruceliu.controller
swagger.title=JAVA1908哈哈哈Swagger!!!
swagger.contact.name=BRUCELIU
swagger.contact.url=http://www.bruceliu.com
swagger.version=2.3
通过@EnableSwagger2Doc注解来启用Swagger2。
5.3.3.添加文档内容
在完成了上述配置后,其实已经可以生产文档内容,但是这样的文档主要针对请求本身,而描述主要来源于函数等命名产生,对用户并不友好,我们通常需要自己增加一些说明来丰富文档内容。如下所示,我们通过@ApiOperation注解来给API增加说明、通过@ApiImplicitParams
、@ApiImplicitParam
注解来给参数增加说明。
package com.bruceliu.controller;
import com.bruceliu.bean.User;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import java.util.*;
/**
* @author bruceliu
* @create 2019-10-19 12:49
* @description
*/
@RestController
@RequestMapping(value = "/users") // 通过这里配置使下面的映射都在/users下
public class UserController {
// 创建线程安全的Map
static Map<Long, User> users = Collections.synchronizedMap(new HashMap<Long, User>());
@ApiOperation(value = "获取用户列表", notes = "")
@RequestMapping(value = "/", method = RequestMethod.GET)
public List<User> getUserList() {
// 处理"/users/"的GET请求,用来获取用户列表
// 还可以通过@RequestParam从页面中传递参数来进行查询条件或者翻页信息的传递
List<User> r = new ArrayList<User>(users.values());
return r;
}
@ApiOperation(value = "创建用户", notes = "根据User对象创建用户")
@ApiImplicitParam(name = "user", value = "用户详细实体user", required = false, dataType = "User")
@RequestMapping(value = "/", method = RequestMethod.POST)
public String postUser(@ModelAttribute User user) {
// 处理"/users/"的POST请求,用来创建User
// 除了@ModelAttribute绑定参数之外,还可以通过@RequestParam从页面中传递参数
users.put(user.getId(), user);
return "success";
}
@ApiOperation(value = "获取用户详细信息", notes = "根据url的id来获取用户详细信息")
@ApiImplicitParam(name = "id", value = "用户ID", required = false, dataType = "Long")
@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUser(@PathVariable Long id) {
// 处理"/users/{id}"的GET请求,用来获取url中id值的User信息
// url中的id可通过@PathVariable绑定到函数的参数中
return users.get(id);
}
@ApiOperation(value = "更新用户详细信息", notes = "根据url的id来指定更新对象,并根据传过来的user信息来更新用户详细信息")
@ApiImplicitParams({
@ApiImplicitParam(name = "id", value = "用户ID", required = true, dataType = "Long"),
@ApiImplicitParam(name = "user", value = "用户详细实体user", required = false, dataType = "User")
})
@RequestMapping(value = "/{id}", method = RequestMethod.PUT)
public String putUser(@PathVariable Long id, @ModelAttribute User user) {
// 处理"/users/{id}"的PUT请求,用来更新User信息
User u = users.get(id);
u.setName(user.getName());
u.setAge(user.getAge());
users.put(id, u);
return "success";
}
@ApiOperation(value = "删除用户", notes = "根据url的id来指定删除对象")
@ApiImplicitParam(name = "id", value = "用户ID", required = false, dataType = "Long")
@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public String deleteUser(@PathVariable Long id) {
// 处理"/users/{id}"的DELETE请求,用来删除User
users.remove(id);
return "success";
}
}
5.3.4.需要在启动类中添加注解
package com.bruceliu;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author bruceliu
* @create 2019-10-19 12:44
* @description
*/
@SpringBootApplication
@EnableSwagger2Doc
public class APP {
public static void main(String[] args) {
SpringApplication.run(APP.class,args);
}
}
5.3.5.需要在启动类中添加注解
完成上述代码添加上,启动Spring Boot程序,访问:http://localhost:8080/swagger-ui.html
。就能看到前文所展示的RESTful API的页面。我们可以再点开具体的API请求,以POST类型的/users请求为例,可找到上述代码中我们配置的Notes信息以及参数user的描述信息,如下图所示。
5.3.6.API文档访问与调试
在上图请求的页面中,我们看到user的Value是个输入框?是的,Swagger除了查看接口功能外,还提供了调试测试功能,我们可以点击上图中右侧的Model Schema(黄色区域:它指明了User的数据结构),此时Value中就有了user对象的模板,我们只需要稍适修改,点击下方“Try it out!”按钮,即可完成了一次请求调用!
此时,你也可以通过几个GET请求来验证之前的POST请求是否正确。
相比为这些接口编写文档的工作,我们增加的配置内容是非常少而且精简的,对于原有代码的侵入也在忍受范围之内。因此,在构建RESTful API的同时,加入swagger来对API文档进行管理,是个不错的选择。
六、springboot日志框架管理
## 6.1.为什么需要日志框架
通过日志的方式记录系统运行的过程或错误以便定位问题。
6.2.常见日志框架
6.2.1.日志框架介绍
对于我们日常开发日志是经常使用的,当然以前的我们可能还傻傻的各种System.out.println("重要数据")
在控制台输出各种重要数据呢,投入生产的时候再注释掉。到现在为止呢,已经有很多日志可供选择了,而市面上常见的日志框架有很多,比如:JCL
、SLF4J
、Jboss-logging
、jUL
、log4j
、log4j2
、logback
等等,我们该如何选择呢?
6.2.2.市面上的日志框架
JUL、JCL、Jboss-logging、logback、log4j、log4j2、slf4j…
日志的抽象层 | 日志实现 |
---|---|
Log4j JUL(java.util.logging) Log4j2 Logback |
左边选一个抽象层、右边来选一个实现;类似与我们经常使用的JDBC一样,选择不同的数据库驱动。
下面我们先看看日志的抽象层:JCL大家应该很熟悉,Commons Logging,spring中常用的框架最后一次更新2014年~~~;jboss-logging使用的场景太少了;就剩下SLF4j了也是我们springboot中使用的日志抽象层。
日志实现:大家应该看着都很熟悉把Log4j大家应该用的挺多的,Logback是Log4j的升级版本出至于同一个人开发的,考虑到以后的升级使用等问题,又写出了SLF4j的日志抽象层使用起来更加灵活。JUL(java.util.logging)一看就知道是java util包下的;Log4j2 咋一看像是Log4j的升级版本,其实并不是,它是apache下生产的日志框架。
SpringBoot底层是Spring框架, Spring框架默认使用JCL.
SpringBoot选用SLF4j和Logback
6.3.Slf4j使用
6.3.1.如何在系统中使用SLF4j
slf4j官网: https://www.slf4j.org
开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法;
给系统里面导入slf4j的jar和 logback的实现jar就可以。
使用示例:
package com.bruceliu.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("Hello World");
}
}
我们现在已经知道了springboot中使用的是 SLF4j和logback,但是如果我们想使用log4j该怎么办呢,从上面的图示中我们可以看出想要使用log4j我们肯定还是要使用 SLF4j作为抽象层,但是中间给我们加入了一层适配层(Adaptation layer)然后使用log4j进行实现,那么我们需要导入图示中的jar包即可,其他的也是一样了。所以说,以后开发的时候,日志记录方法的调用,不应该来直接调用日志的实现类,而是调用日志抽象层里面的方法; 每一个日志的实现框架都有自己的配置文件。使用slf4j以后,配置文件还是做成日志实现框架自己本身的配置文件;
6.3.2.可能存在的问题
现在开发中我么想使用slf4j+logback,但是对于一些遗留项目中例如Spring(commons-logging)、Hibernate(jboss-logging)…等等,如何去做到日志同一呢?
你想到的问题SLF4j能想不到吗?答案是可以的,我们看看下面的图就明白了
如何让系统中所有的日志都统一到slf4j;
1、将系统中其他日志框架先排除出去;
2、用中间包来替换原有的日志框架;
3、我们导入slf4j其他的实现
其实通过idea我们创建一个springboot项目也可以查看日志依赖(截取其中部分):
SpringBoot使用它来做日志功能:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.3.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F56Dnv1n-1668614126094)(F:/javaee教案/第四阶段/Day20220309-SpringBoot框架SSM-上午/SpringBoot讲义/SpringBoot系列课程(六)]-SpringBoot日志管理.assets/1646720762874.png)
总结:
1)、SpringBoot底层也是使用slf4j+logback的方式进行日志记录
2)、SpringBoot也把其他的框架的日志都替换成了slf4j;
3)、如果我们要引入其他框架?
SpringBoot能自动适配所有的日志,而且底层使用slf4j+logback的方式记录日志,引入其他框架的时候,只需要把这个框架依赖的日志框架排除掉即可
示例:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
6.4.SpringBoot日志的默认配置
package com.bruceliu;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class Springboot1ApplicationTests {
// 日志记录器
private Logger logger = LoggerFactory.getLogger(getClass());
@Test
public void contextLoads() {
// System.out.println();
// 日志的级别:由低到高
// trace<debug<info<warn<error
// 可以调整需要输出的日志级别: 日志就只会在这个级别及以后的高级别生效
logger.trace("这是trace日志...");
logger.debug("这是debug日志...");
// SpringBoot默认使用的是info级别的, 没有指定级别的就使用SpringBoot默认规定的级别
logger.info("这是info日志...");
logger.warn("这是warn日志...");
logger.error("这是error日志...");
}
}
其他配置
设置日志的级别: logging.level
#修改日志的级别,默认root是info
logging.level.root=trace
指定日志的文件名: logging.file ,会在当前项目下生成springboot.log日志
# 不指定路径在当前项目下生成springboot.log日志
logging.file=springboot.log
# 可以指定完整的路径;
logging.file=d://springboot.log
指定日志的文件的目录: logging.path , 日志文件名称使用SpringBoot默认的输出日志文件名.
# 在当前磁盘的根路径下创建spring文件夹和里面的log文件夹;使用spring.log 作为默认文件
logging.path=/spring/log
如果同时指定了 logging.file , 则使用 logging.file , 不会使用 logging.path
指定日志在控制台输出的格式: logging.pattern.console
# 在控制台输出的日志的格式
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n
指定文件中日志输出的格式: logging.pattern.file
# 指定文件中日志输出的格式
logging.pattern.file=%d{yyyy-MM-dd} === [%thread] === %-5level === %logger{50} ==== %msg%n
6.5.SpringBoot指定日志文件
如果还是不够用的话,可以自定义配置。给类路径下放上每个日志框架自己的配置文件即可;SpringBoot就不使用他默认配置的
logback.xml示例
logback.xml:直接就被日志框架识别了;
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<jmxConfigurator />
<!-- 日志添加到控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
<appender name="rollingFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/quickstart.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/smt.%d{yyyy-MM-dd}.log</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="businessLogFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 按每小时滚动文件,如果一个小时内达到10M也会滚动文件, 滚动文件将会压缩成zip格式 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/smt-%d{yyyy-MM-dd_HH}.%i.zip</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>10MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
</rollingPolicy>
<!-- 独立的pattern -->
<encoder>
<pattern>%d{HH:mm:ss.SSS},%msg%n</pattern>
</encoder>
</appender>
<!-- project default level 本身没有指定appender不打印 传递给上级root-->
<logger name="com.github.miemiedev.smt" level="DEBUG" />
<logger name="org.mybatis.spring.SqlSessionFactoryBean" level="DEBUG" />
<!--log4jdbc -->
<!-- <logger name="jdbc.sqltiming" level="INFO"/> -->
<root level="WARN">
<appender-ref ref="console" />
<appender-ref ref="rollingFile" />
</root>
</configuration>
logback-spring.xml:日志框架就不直接加载日志的配置项,由SpringBoot解析日志配置,可以使用SpringBoot 的高级Profile功能
<!-- 日志添加到控制台 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<springProfile name="dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ----> [%thread] ---> %-5level %logger{50} - %msg%n</pattern>
</springProfile>
<springProfile name="!dev">
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} ==== [%thread] ==== %-5level %logger{50} - %msg%n</pattern>
</springProfile>
</layout>
</appender>
#指定当前模式
spring:
profiles:
active: dev
如果使用logback.xml作为日志配置文件,还要使用profile功能,会有以下错误 no applicable action for [springProfile]
6.6.切换日志框架
弃用SpringBoot官方指定的logback日志框架,改用别的日志框架实现。但没太大实际意义,因为logback是最先进的版本,一般没有必要替换。(不推荐,只是教大家如何切换)
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>logback-classic</artifactId>
<groupId>ch.qos.logback</groupId>
</exclusion>
<exclusion>
<artifactId>log4j-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
在resource目录下添加log4j.properties
### set log levels ###
log4j.rootLogger = debug,stdout,D,E
### 输出到控制台 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = %d{ABSOLUTE} ===== %5p %c{ 1 }:%L - %m%n
#### 输出到日志文件 ###
#log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
#log4j.appender.D.File = logs/log.log
#log4j.appender.D.Append = true
#log4j.appender.D.Threshold = DEBUG ## 输出DEBUG级别以上的日志
#log4j.appender.D.layout = org.apache.log4j.PatternLayout
#log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
#
#### 保存异常信息到单独文件 ###
#log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
#log4j.appender.D.File = logs/error.log ## 异常日志文件名
#log4j.appender.D.Append = true
#log4j.appender.D.Threshold = ERROR ## 只输出ERROR级别以上的日志!!!
#log4j.appender.D.layout = org.apache.log4j.PatternLayout
#log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
切换为log4j2框架
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<artifactId>spring-boot-starter-logging</artifactId>
<groupId>org.springframework.boot</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
七、springboot自动化配置原理
## 7.1.前言
不论在工作中,亦或是求职面试,Spring Boot已经成为我们必知必会的技能项。除了某些老旧的政府项目或金融项目持有观望态度外,如今的各行各业都在飞速的拥抱这个已经不是很新的Spring启动框架。
当然,作为Spring Boot的精髓,自动配置原理的工作过程往往只有在“面试”的时候才能用得上,但是如果在工作中你能够深入的理解Spring Boot的自动配置原理,将无往不利。
Spring Boot的出现,得益于“习惯优于配置
”的理念,没有繁琐的配置、难以集成的内容(大多数流行第三方技术都被集成),这是基于Spring 4.x提供的按条件配置Bean的能力。
7.2.SpringBoot的入口
我们开发任何一个Spring Boot项目,都会用到如下的启动类
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
从上面代码可以看出,Annotation定义(@SpringBootApplication
)和类定义(SpringApplication.run
)最为耀眼,所以要揭开SpringBoot的神秘面纱,我们要从这两位开始就可以了。
7.3.SpringBootApplication背后的秘密
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
...
}
虽然定义使用了多个Annotation进行了原信息标注,但实际上重要的只有三个Annotation:
@Configuration(@SpringBootConfiguration点开查看发现里面还是应用了@Configuration)
@EnableAutoConfiguration
@ComponentScan
所以,如果我们使用如下的SpringBoot启动类,整个SpringBoot应用依然可以与之前的启动类功能对等:
@Configuration
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
每次写这3个比较累,所以写一个@SpringBootApplication
方便点。接下来分别介绍这3个Annotation。
7.4.@Configuration
这里的@Configuration
对我们来说不陌生,它就是JavaConfig形式的Spring Ioc容器的配置类使用的那个@Configuration
,SpringBoot社区推荐使用基于JavaConfig的配置形式,所以,这里的启动类标注了@Configuration之后,本身其实也是一个IoC容器的配置类。
举几个简单例子回顾下,XML跟config配置方式的区别:
表达形式层面
基于XML配置的方式是这样:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
default-lazy-init="true">
<!--bean定义-->
</beans>
而基于JavaConfig的配置方式是这样:
@Configuration
public class MockConfiguration{
//bean定义
}
任何一个标注了@Configuration
的Java类定义都是一个JavaConfig配置类。
注册bean定义层面
基于XML的配置形式是这样:
<bean id="mockService" class="..MockServiceImpl">
...
</bean>
而基于JavaConfig的配置形式是这样的:
@Configuration
public class MockConfiguration{
@Bean
public MockService mockService(){
return new MockServiceImpl();
}
}
任何一个标注了@Bean
的方法,其返回值将作为一个bean定义注册到Spring的IoC容器,方法名将默认成该bean定义的id。
表达依赖注入关系层面
为了表达bean与bean之间的依赖关系,在XML形式中一般是这样:
<bean id="mockService" class="..MockServiceImpl">
<propery name ="dependencyService" ref="dependencyService" />
</bean>
<bean id="dependencyService" class="DependencyServiceImpl"></bean>
而基于JavaConfig的配置形式是这样的:
@Configuration
public class MockConfiguration{
@Bean
public MockService mockService(){
return new MockServiceImpl(dependencyService());
}
@Bean
public DependencyService dependencyService(){
return new DependencyServiceImpl();
}
}
如果一个bean的定义依赖其他bean,则直接调用对应的JavaConfig类中依赖bean的创建方法就可以了。
7.5.@ComponentScan扫描bean
我们原来使用spring的使用不会在xml中一个一个配置bean,我们在再类上加上@Repository
,@Service
,@Controller
,@Component
,并且注入时可以使用@AutoWired的注解注入。 这一切的功能都需要我们配置包扫描<context:component-scan base-package="com.bruceliu"/>.
然而现在注解驱动开发已经没有了配置文件,不能配置。但是提供了@ComponentScan,我们可以在配置类上面加上这个注解也是一样,并且也能扫描配置包项目的相关注解,也能完成自动注入。
接下来我们先来看扫描组件,后面再看注入
package com.bruceliu.service;
import com.bruceliu.bean.User;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public User getUser(Long id){
System.out.println("userservice...");
return null;
}
}
package com.bruceliu.controller;
import com.bruceliu.bean.User;
import org.springframework.stereotype.Controller;
@Controller
public class UserController {
//先不拷贝页面,直接打印即可
public User getUser(Long id){
System.out.println("usercontroller...");
return null;
}
}
//注解类==配置文件
@Configuration //告诉spring这是一个注解类
@ComponentScan("com.bruceliu")
public class MainConfig {
//相当于在xml中配置了<bean id="" class="com.bruceliu.dao.UserDao"><bean/>
@Bean("userDao") //指定bean的名字
public UserDao userDao01(){
return new UserDaoImpl();
}
}
public class MainConfigTest {
@Test
public void testIoc(){
ApplicationContext context = new AnnotationConfigApplicationContext(MainConfig.class);
for (String beanName : context.getBeanDefinitionNames()) {
System.out.println(beanName);
}
}
}
注:所以SpringBoot的启动类最好是放在root package下,因为默认不指定basePackages。
7.6.@EnableAutoConfiguration
@EnableAutoConfiguration
作为一个复合Annotation,其自身定义关键信息如下:
@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
...
}
其中,最关键的要属@Import(EnableAutoConfigurationImportSelector.class)
,借助EnableAutoConfigurationImportSelector
,@EnableAutoConfiguration
可以帮助SpringBoot应用将所有符合条件的@Configuration
配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样借助于Spring框架原有的一个工具类:SpringFactoriesLoader
的支持,@EnableAutoConfiguration
可以智能的自动配置功效才得以大功告成!
而这个注解也是一个派生注解,其中的关键功能由@Import提供,其导入的AutoConfigurationImportSelector的selectImports()
方法通过SpringFactoriesLoader.loadFactoryNames()扫描所有具有META-INF/spring.factories
的jar包。spring-boot-autoconfigure-x.x.x.x.jar里就有一个这样的spring.factories文件。
这个spring.factories文件也是一组一组的key=value的形式,其中一个key是EnableAutoConfiguration类的全类名,而它的value是一个xxxxAutoConfiguration的类名的列表,这些类名以逗号分隔,如下图所示:
这个@EnableAutoConfiguration注解通过@SpringBootApplication被间接的标记在了Spring Boot的启动类上。在SpringApplication.run(…)的内部就会执行selectImports()方法,找到所有JavaConfig自动配置类的全限定名对应的class,然后将所有自动配置类加载到Spring容器中。
7.7.自动配置生效
每一个XxxxAutoConfiguration自动配置类都是在某些条件之下才会生效的,这些条件的限制在Spring Boot中以注解的形式体现,常见的条件注解有如下几项:
@ConditionalOnBean:当容器里有指定的bean的条件下。
@ConditionalOnMissingBean:当容器里不存在指定bean的条件下。
@ConditionalOnClass:当类路径下有指定类的条件下。
@ConditionalOnMissingClass:当类路径下不存在指定类的条件下。
以ServletWebServerFactoryAutoConfiguration
配置类为例,解释一下全局配置文件中的属性如何生效,比如:server.port=8081
,是如何生效的(当然不配置也会有默认值,这个默认值来自于org.apache.catalina.startup.Tomcat)。
在ServletWebServerFactoryAutoConfiguration
类上,有一个@EnableConfigurationProperties
注解:开启配置属性,而它后面的参数是一个ServerProperties类,这就是习惯优于配置的最终落地点。
在这个类上,我们看到了一个非常熟悉的注解:@ConfigurationProperties
,它的作用就是从配置文件中绑定属性到对应的bean上,而@EnableConfigurationProperties
负责导入这个已经绑定了属性的bean到spring容器中(见上面截图)。那么所有其他的和这个类相关的属性都可以在全局配置文件中定义,也就是说,真正“限制”我们可以在全局配置文件中配置哪些属性的类就是这些XxxxProperties类,它与配置文件中定义的prefix关键字开头的一组属性是唯一对应的。
至此,我们大致可以了解。在全局配置的属性如:server.port等,通过@ConfigurationProperties注解,绑定到对应的XxxxProperties配置实体类上封装为一个bean,然后再通过@EnableConfigurationProperties注解导入到Spring容器中。
而诸多的XxxxAutoConfiguration自动配置类,就是Spring容器的JavaConfig形式,作用就是为Spring 容器导入bean,而所有导入的bean所需要的属性都通过xxxxProperties的bean来获得。
可能到目前为止还是有所疑惑,但面试的时候,其实远远不需要回答的这么具体,你只需要这样回答:
Spring Boot启动的时候会通过@EnableAutoConfiguration注解找到META-INF/spring.factories配置文件中的所有自动配置类,并对其进行加载,而这些自动配置类都是以AutoConfiguration结尾来命名的,它实际上就是一个JavaConfig形式的Spring容器配置类,它能通过以Properties结尾命名的类中取得在全局配置文件中配置的属性如:server.port,而XxxxProperties类是通过@ConfigurationProperties注解与全局配置文件中对应的属性进行绑定的。
EnableAutoConfigurationImportSelector,@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。就像一只“八爪鱼”一样。
@EnableAutoConfiguration自动配置的魔法骑士就变成了:从classpath中搜寻所有的META-INF/spring.factories配置文件,并将其中org.springframework.boot.autoconfigure.EnableutoConfiguration对应的配置项通过反射(Java Refletion)实例化为对应的标注了@Configuration的JavaConfig形式的IoC容器配置类,然后汇总为一个并加载到IoC容器。
SpringBoot的启动原理基本算是讲完了,为了方便记忆,我根据上面的分析画了张图
SpringBoot自动化配置关键组件关系图
mybatis-spring-boot-starter、spring-boot-starter-web等组件的META-INF文件下均含有spring.factories文件,自动配置模块中,SpringFactoriesLoader收集到文件中的类全名并返回一个类全名的数组,返回的类全名通过反射被实例化,就形成了具体的工厂实例,工厂实例来生成组件具体需要的bean。
可以发现其最终实现了ImportSelector(选择器)和BeanClassLoaderAware(bean类加载器中间件),重点关注一下AutoConfigurationImportSelector的selectImports方法。
public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(this.beanClassLoader);
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(autoConfigurationMetadata, annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}
该方法在springboot启动流程——bean实例化前被执行,返回要实例化的类信息列表。我们知道,如果获取到类信息,spring自然可以通过类加载器将类加载到jvm中,现在我们已经通过spring-boot的starter依赖方式依赖了我们需要的组件,那么这些组建的类信息在select方法中也是可以被获取到的,不要急我们继续向下分析。
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you are using a custom packaging, make sure that file is correct.");
return configurations;
}
该方法中的getCandidateConfigurations方法,通过方法注释了解到,其返回一个自动配置类的类名列表,方法调用了loadFactoryNames方法,查看该方法
public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
String factoryClassName = factoryClass.getName();
return (List)loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
}
private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
MultiValueMap<String, String> result = (MultiValueMap)cache.get(classLoader);
if (result != null) {
return result;
} else {
try {
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
LinkedMultiValueMap result = new LinkedMultiValueMap();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
UrlResource resource = new UrlResource(url);
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
Iterator var6 = properties.entrySet().iterator();
在上面的代码可以看到自动配置器会根据传入的factoryClass.getName()到项目系统路径下所有的spring.factories文件中找到相应的key,从而加载里面的类。我们就选取这个mybatis-spring-boot-autoconfigure下的spring.factories文件
进入org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration中,主要看一下类头:
发现Spring的@Configuration,俨然是一个通过注解标注的springBean,继续向下看,
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class})这个注解的意思是:当存在SqlSessionFactory.class, SqlSessionFactoryBean.class这两个类时才解析MybatisAutoConfiguration配置类,否则不解析这一个配置类,make sence,我们需要mybatis为我们返回会话对象,就必须有会话工厂相关类。
@CondtionalOnBean(DataSource.class):只有处理已经被声明为bean的dataSource。
@ConditionalOnMissingBean(MapperFactoryBean.class)这个注解的意思是如果容器中不存在name指定的bean则创建bean注入,否则不执行(该类源码较长,篇幅限制不全粘贴)
以上配置可以保证sqlSessionFactory、sqlSessionTemplate、dataSource等mybatis所需的组件均可被自动配置,@Configuration注解已经提供了Spring的上下文环境,所以以上组件的配置方式与Spring启动时通过mybatis.xml文件进行配置起到一个效果。通过分析我们可以发现,只要一个基于SpringBoot项目的类路径下存在SqlSessionFactory.class, SqlSessionFactoryBean.class,并且容器中已经注册了dataSourceBean,就可以触发自动化配置,意思说我们只要在maven的项目中加入了mybatis所需要的若干依赖,就可以触发自动配置,但引入mybatis原生依赖的话,每集成一个功能都要去修改其自动化配置类,那就得不到开箱即用的效果了。所以Spring-boot为我们提供了统一的starter可以直接配置好相关的类,触发自动配置所需的依赖(mybatis)如下:
这里是截取的mybatis-spring-boot-starter的源码中pom.xml文件中所有依赖:
因为maven依赖的传递性,我们只要依赖starter就可以依赖到所有需要自动配置的类,实现开箱即用的功能。也体现出Springboot简化了Spring框架带来的大量XML配置以及复杂的依赖管理,让开发人员可以更加关注业务逻辑的开发。
八、springboot重点
8.1 springboot框架日志移除
8.2 springboot内置的框架
8.3 maven的生命周期
8.4 springboot自动配置流程
8.5 springboot热启动
在开发过程中,当写完一个功能我们需要运行应用程序测试,可能这个小功能中存在多个小bug,我们需要改正后重启服务器,这无形之中拖慢了开发的速度增加了开发时间,SpringBoot
提供了spring-boot-devtools
,使我们在更改应用和配置文件的时候,自动重启应用!
8.5.1.打开pom.xml文件添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
只要classpath
下的文件有变动,它就会自动重启。这在使用IDE
时非常有用,因为可以很快得到代码改变的反馈。默认情况下,classpath
下任何指向文件夹的实体都会被监控,注意一些资源的修改比如静态assets,视图模板不需要重启应用。
由于DevTools
监控classpath
下的资源,所以唯一触发重启的方式就是更新classpath
。在Eclipse里,保存一个修改的文件将引起classpath
更新,并触发重启。在IntelliJ IDEA
中,默认是不自动编译的,我们需要设置自动编译。
8.5.2.设置IDEA自动编译:
(1).快捷键Ctrl+Alt+S打开设置,在Build
,Execotion
,Deployment->Compiler->勾选Build Project automatically
项
(2).快捷键Ctrl + Shift + Alt + /,选择Registry
(3).勾选 Compiler autoMake allow when app running
重启应用,当改动代码时,观察控制台输出,你会发现Spring Boot已经检测到了文件变化,并重新启动,你会发现体验为何如此之棒,爽极了!
九、自定义启动器
为了加深对SpringBoot中自动装配的理解,我们自定义一个starter来实现,具体步骤如下
9.1.自定义starter
9.1.1.IDEA中创建maven项目
9.1.2.配置依赖
在pom配置文件中添加如下依赖,增加SpringBoot自身的自动配置作为依赖。
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<version>2.1.4.RELEASE</version>
</dependency>
</dependencies>
9.1.3.属性配置类
package com.bruceliu.service;
import org.springframework.boot.context.properties.ConfigurationProperties;
@ConfigurationProperties(prefix = "hello")
public class HelloServiceProperties {
private static final String MSG = "world";
private String msg = MSG;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
9.1.4.判断依据类
package com.bruceliu.service;
public class HelloService {
private String msg;
public String sayHello(){
return "Hello "+msg;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
根据此类的存在与否来创建这个类的bean
9.1.4.自动配置类
package com.bruceliu.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@EnableConfigurationProperties(HelloServiceProperties.class)
@ConditionalOnClass(HelloService.class)
@ConditionalOnProperty(prefix = "hello",name="msg",hasvalue ="enabled",matchIfMissing = true)
public class HelloServiceAutoConfiguration {
@Autowired
private HelloServiceProperties helloServiceProperties;
@Bean
@ConditionalOnMissingBean(HelloService.class)
public HelloService helloService(){
HelloService helloService = new HelloService();
helloService.setMsg(helloServiceProperties.getMsg());
return helloService;
}
}
根据HelloServiceProperties
提供的参数,并通过@ConditionalOnClass
判断HelloService
这个类在类路径中是否存在,且当容器中没有这个Bean的情况下自动配置这个bean。
9.1.5.注册配置
若想自动配置生效,我们需要注册自动配置类,在src/main/resources
下新建META-INF/spring.factories
,如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.bruceliu.service.HelloServiceAutoConfiguration
如果有多个自动配置,则用“,”隔开。
9.2.使用自定义的starter
9.2.1.创建好SpringBoot项目
创建好一个SpringBoot项目。
9.2.2.引入我们自定义的starter
<dependency>
<groupId>com.bruce.myhello</groupId>
<artifactId>spring-boot-starter-myhello</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
9.3.查看引入的具体依赖
9.4.工具类中使用
@RestController
public class HelloStarterController {
@Resource
HelloService helloService;
@RequestMapping("/")
public String index(){
return helloService.sayHello();
}
}
9.5.启动测试
访问:http://localhost:8082/
然后我们在application.properties
中配置如下内容
hello.msg=SpringBoot
再次启动访问测试结果!
ok~自定义的starter搞定
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/94157.html