SpringBoot学习记录
近来在做web开发,以spring boot作为web服务器,从0开始一些简单的学习。
下面是一些基本的spring boot概念,在学习时逐步记录下来。
SpringBoot入门
存在意义
- Spring boot是spring全家桶的默认简装版,简化我们使用spring全家桶的存在。
- 基本IoC,AOP使用的是spring framework
- 安全用spring security
- 数据访问使用 spring data,支持关系型非关系型数据库
- 分布式部署可用spring cloud
- Spring boot也是桥接spring cloud的分布式解决方案的桥梁。
- Spring boot目的可以快速构建一个微服务,然后通过spring cloud进行协调,再通过spring cloud data flow进行connect连接这些微服务。这就是spring官方的指导方案。
优点
- 使用非常方便,可以快速搭建spring项目
- 嵌入servlet容器,不用打war包,直接打成jar包独立运行
- starters启动器,都可以帮我们做好自动依赖和版本控制
- 不需要XML配置,也不进行代码生成,使用时很方便
- 有生产环境的运行时监控
- 支持热启动
- 天然集成云计算
缺点
- 入门容易,精通难,集成度高,不容易了解底层
- 会自动绑定一堆依赖,会有额外的一些冗余
前提基本知识
- Spring框架的使用经验
- 使用Maven进行项目构建和依赖管理经验
- 熟练使用eclipse/IDEA
HelloWorld
设置好maven依赖,直接使用springInitializr的 start.spring.io 框架创建即可
创建MainApplication.java 和 HelloController.java
@SpringBootApplication
public class Demo1Application {
public static void main(String[] args) {
SpringApplication.run(Demo1Application.class, args);
}
}
// RestController == Controller + ResponseBody
@RestController
public class HelloController {
@RequestMapping("/hello")
public String hello() {
return "Hello Spring Boot";
}
}
运行,然后本地浏览器 localhost:8080/hello测试
创建可执行jar包,在pom.xml中加入
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 使用 maven 的lifecycle进行package打包,会在target目录下获得一个可执行 java 包,然后使用
$java -jar xxoo.1.0.jar
版本依赖
- 默认我们pom中会有下面的父项目依赖,spring-boot-starter-parent,打开它可以看到它依赖于 spring-boot-dependencies, 再打开dependencies可以看到我们内部依赖的全部版本号。
- 所以我们大部分情况下,加入新依赖,是无需额外制定依赖版本的。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.6.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
- 所以我们大部分情况下,加入新依赖,是无需额外制定依赖版本的。
依赖库
- 上述默认情况,我们pom中会依赖 spring-boot-starter-web,我们打开它,可以看到内部依赖了更多的库,包括tomcat, json等等。
- 这个 spring-boot-starter-web 我们称为启动器。spring boot有更多的默认启动器,包括:
- web starter
- cache starter
- test starter
- data JPA/Neo4j/redis/mangodb/elasticsearch starter
- amqp/activemq starter
- mail starter
- 等等等等,官方大约55个,可以从这里看列表(https://github.com/spring-projects/spring-boot/tree/master/spring-boot-project/spring-boot-starters)
- 我们也可以开发定制自己的starter,这些starter的目的就是便于依赖管理,统一配置,使用更简单。
程序入口详解
我们看到了,spring boot的程序入口有个重要注解 @SpringBootApplication,这个注解跟进去看的话会发现它做了如下几件事:
- 首先它是一个配置类,本身包含了 @configuration 注解(而@configurarion配置类本身也是一个@component 组件类)
- 其次,它是一个@EnableAutoConfiguration 开启了自动配置功能类,最终实现的是,把@SpringBootApplication这个注解的入口类,所在的包内全部组件进行自动扫描并导入。
- 假设 com.fk.app.java是这个入口类的话,那么 com.fk 包下全部组件会被自动扫描导入。
- 然后,它自动 @import 了spring-boot-autoconfigure的包,将这些包自动导入容器中。
- 目录在:ExternalLibraries -> Maven:org-springframework.boot:spring-boot-autoconfigure -> META-INF -> spring.factories 的 org.springframework.boot.autoconfigure.EnableAutoConfiguration中。
核心目录说明
- src
- main
- java
- com.xxoo.xxproject 源代码
- resources
- static 一些静态资源文件,例如css, js, html, image等,包括vue工作路径一般在这里
- template 因为jar包内嵌tomcat,所以默认不支持jsp,但spring boot支持模板引擎,例如freemarker, thymeleaf
- application.peoperties 这是spring boot的配置文件,可以覆盖修改默认设置。(例如添加一行 server.port=8081 ,那么启动后tomcat就会监听8081)
- test 测试目录,其包结构通常和src类一致。
SpringBoot配置
SpringBoot配置
默认支持类型
两种全局配置文件类型
- application.properties
- application.yaml 或 application.yml
配置文件的作用
- 对sprint boot的一些默认配置值进行修改
存放位置
- src/main/resources目录下
- 类路径/config 下
绑定容器对象
@ImportResource(“classpath:xxx.xml”)
它的注解中应该是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"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
<context:property-placeholder location="classpath:/config.properties"/>
</beans>
它是spring支持的方式,因为sping boot 并不能自动识别spring的xml配置,于是就需要importresource来特殊指定xml配置文件名。
它一般用在启动类上,进行启动配置指定。
@PropertySource(“classpath:xxx.properties”)
这个直接后面一般是 .properties或者 .yml文件,用来给java bean注入绑定对象。
它一般放置在java bean的类名上。
它是spring boot推荐的方式,更简单。
@Bean
它是一种更简单的给容器注入bean对象的方式。
它也是sprint boot 推荐的方式,即,全注解的方式来进行配置,而不是使用xml配置的方式。所以,如下面代码所示,专门创建一个 @configuration 配置类,然后使用 @bean来进行注入,是最佳方式。而不是xml 的
@Configuration
public class MyAppConfig {
//将方法的返回值添加到容器中;容器中这个组件默认的id就是方法名
// 之后,这个 HelloService组件 就被容器管理了,getbean("helloService001")就可以获得bean了
@Bean
public HelloService helloService0011(){
System.out.println("配置类@Bean给容器中添加组件");
return new HelloService();
}
}
配置绑定
将配置从yaml文件中进行获取,则使用 @ConfigurationProperties 进行配置绑定。例如
@Component // 注意,如要使用配置绑定功能,必须是容器中的组件
@ConfigurationProperties(prefix="personXXOO") // 将会从 personXXOO 节点进行读取填充
public class Person{
private String name;
private Integer age;
private List<Object> lists;
private Dog dog;
}
public class Dog{
private String name;
private Integer age;
}
配置文件
personXXOO:
name: 张三
age: 20
lists:
- item1
- item2
dog:
name: 小白
age: 1
注意:使用该方式,需要pom导入一个配置文件处理器。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
这里也可以使用 application.properties 进行配置,例如下面也是可以的
personXXOO.name=张三
personXXOO.age=20
personXXOO.lists=item1,item2
personXXOO.dog.name=小白
personXXOO.dog.age=1
配置补充
配置中可以使用的特殊符号
例如:
personXXOO.name=张三${random.uuid} # 这个是随机
personXXOO.age=${random.int[0, 100]} # 这个也是随机
personXXOO.lists=item1,item2
personXXOO.dog.name=${personXXOO.name}的狗 # 这个是值引用
personXXOO.dog.age=${personXXOO.age:20} # 这个是 : 后面加 默认值。
Properties配置编码问题
上面的properties里面可能有编码乱码问题。因为intellijIdea默认properties是ASC2编码,我们需要在setting->editor->file encodings-> 下方,开启 transparent native-to-ascii conversion.
配置绑定的其他方式
之前我们提过使用
@ConfigurationProperties(prefix= "xxxoo")
的方式为一个类的值进行配置绑定。
我们还可以使用传统的XML方式进行配置绑定,如下
<bean class="Person">
<property name="age" value=12></property>
</bean>
我们还可以使用value注解来绑定值,如下
@Component
public class Person{
@Value("${personXXOO.name}") // 这个等同于XML中的value赋值,也就是从配置文件中找到personXXOO.name并将其值赋给 name 变量。注意,有 $ 号
private String name;
@Value("true") // 也可以直接赋值,不从配置文件读取
private Boolean isBoss;
@Value(#{11*2}) // 支持springEL语法,可以用 # 进行运算
private Integer age;
}
@ConfigurationProperties | @Value | |
---|---|---|
用法 | 可以一次就批量注入整个配置文件的属性 | 需要一个个的指定 |
灵活性 | 支持松散语法绑定,配置中的 last-name 变量名等同于 lastName 变量名 | 不支持松散语法绑定 |
SpEL | 不支持 #{11*2} 这种方式 | 支持 |
JSR303校验 | 支持 ,支持Validated。即标注变量格式 @Email,要求变量格式必须是邮箱,会自行进行校验 | 不支持 |
配置文件 | 两边都支持yml, yaml, properties | 支持yml, yaml, properties |
复杂类型 | 可以支持,支持map,list等 | 不支持map,list等 |
使用情况 | 如果对一个bean进行全部配置,则使用这个省事 | 特殊使用个别配置的时候,尽量使用这个 |
多环境配置
存在目的
多环境(例如 开发/运营 环境)之间,快速的进行参数配置的切换
方式
多profile文件的方式。注意文件名的格式必须为application-xxoo.properties的格式。例如 application-dev.properties, application-prod.propertiese
使用yaml多文档的方式。例如
--- server: port: 8080 spring: profiles: active: dev --- server: port: 8081 spring: profiles: dev
激活指定环境的方式
使用配置文件方式
properties文件中都默认会读取标准的application.properties内的配置。除非指定了spring.profiles.active = xxoo
yaml文件中默认读取第一段不做指定的配准配置。除非指定了
spring: profiles: active: dev
使用命令行 –spring.profiles.active=xxoo
命令行配置,优先于配置文件,将会导致配置文件中的设定无效化。
- 【不推荐】使用jvm参数 –spring.profiles.active=xxoo
多配置的调用顺序
- 首先命令行为最高优先
- 【不推荐】其次是修改JNI环境,Java系统环境,操作环境变量等
- 加载jar包外部的 properties, yaml文件
- 加载jar包内部(src目录内)的properties, yaml文件
- 加载@configuration注解类上的 @propertySource 属性值
SpringBoot日志
可选框架
日志抽象层 | 日志的实际实现 |
---|---|
slf4J,JCL,Jboss-logging (推荐slf4j) | log4j, JUL, jog4j2, logback (推荐logback) |
springboot使用的默认也是 slf4j 和 logback
JUL是java.util.logging的简写
配置和使用slf4J
基本准则
因为slf4j是抽象层,所以我们应该调用slf4j这种抽象层,而不是调用实现层函数。
配置文件,使用实现层本身的配置方式,而是不使用slf4j的。
- 例如logback的配置文件 logback-spring.xml/logback.xml/logback-spring.groovy/logback.groovy
- log4j2的配置文件 log4j2-spring.xml/log4j2.xml
- JUL的配置文件 logging.properties
这些都是不变的。
我们可以使用springboot的配置application.properties/application.yaml进行log配置;也可以使用上面描述的logback的配置文件进行log配置,这样可以更加灵活的指定每个jar库自己的log格式。
logback自己的配置文件比spring boot的配置文件优先度更高。
logback自己的配置文件分为两种:
logback.xml 这个被日志框架slf4j直接识别,不被spring boot识别
logback-spring.xml 这个更强大,被spring boot识别,然后通知slf4j。所以可以指定不同的环境使用不同的格式,例如
<springProfile name="dev"> <pattern>%d{yyyy-MM-dd} [%thread] ---> %-5level %logger{50} - %msg%n</pattern> </springProfile> <springProfile name="!dev"> <pattern>%d{yyyy-MM-dd} %-5level %logger{20} - %msg%n</pattern> </springProfile>
上面的配置,则表示在 Dev 环境模式下,使用第一套格式输出,否则使用第二套。
(spring更改模式是 –spring.profiles.active=xxoo, 上文有说明)
使用代码
import org.slf4j.Logger
import org.slf4j.LoggerFactory
public class HelloFK{
public static void main(String[] args){
Logger logger = LoggerFactory.getLogger(getClass());
logger.trace("HelloWorld");
logger.debug("HelloWorld");
logger.info("HelloWorld"); // 默认使用 info 级别日志
logger.warn("HelloWorld");
logger.error("HelloWorld");
}
}
logback配置
- 【输出日志级别】loggging.level.com.freeknight=trace
- 【控制台格式】logging.pattern.console=%d{yyyy-MM-dd} [%thread] %-5level %logger{50} - %msg%n
- 【文件格式】logging.pattern.file=
- 【输出文件路径】logging.path=/mydir/log
- - 不指定路径,则在当前项目下生成 mytest.log - 也可指定绝对路径和相对路径 - 当设置了logging.file,那么logging.path将无效化 - 推荐使用logging.path
结构图
[标准模式] APP->slf4j-api.jar-> logback-core.jar+logback-classic.jar
[底层Log4j模式] APP->slf4j-api.jar-> slf4j-log412.jar->log4j.jar
[底层JUL模式] APP->slf4j-api.jar-> slf4j-jdk14.jar->JVM runtime
其中 slf4j-api是抽象API层。 slf4j-jdk14.jar/slf4j-log412.jar 是适配adaptation层,最后的log4j.jar,logback. jar是实现层
配置文件,使用实现层本身的配置方式,而是不使用slf4j的。
遗留问题
springboot使用的是 slf4j 和 logback
ring底层使用的是commons-logging
Hibernate底层使用的是 jboss-logging
所以结构图如下
app -> commons-logging api
-> log4j api
-> JUL api
-> slf4j-api.jar -> logback-classic.jar+logback-core.jar
不统一的话,首先难以管理,其次还容易出现库版本冲突
进行统一
我们想统一使用 slf4j 和 logback 的话,需要做如下操作:
- 去除原本的api库,例如 commons-logging api, log4j api, JUL api 等
- 这一步可以通过 intelljIdea的 diagram->Show dependencies,从依赖图中进行删除
- 添加中间转换库
- 使用 jcl-over-slf4j.jar 替换commons-logging api
- 使用 log4j-over-slf4j.jar 替换log4j api
- 使用 jul-to-slf4j.jar 替换JUL api
- 这一步可以通过pom.xml手动添加依赖
- 正常导入slf4j的实现日志类,例如我们用的logback,则正常导入 logback-classic.jar+logback-core.jar
结构图如下
app -> jcl-over-slf4j.jar -> slf4j-api.jar -> logback-classic.jar+logback-core.jar
-> log4j-over-slf4j.jar -> slf4j-api.jar -> logback-classic.jar+logback-core.jar
-> jul-to-slf4j.jar -> slf4j-api.jar -> logback-classic.jar+logback-core.jar
-> slf4j-api.jar -> logback-classic.jar+logback-core.jar
spring boot的log机制
默认pom.xml中加载spring-boot-starter中,加载了 spring-boot-starter-logging依赖
spring-boot-starter-logging 里面自动依赖了jcl-over-slf4j.jar, log4j-over-slf4j.jar, jul-to-slf4j.jar,这三个库的底层依赖都是 slf4j-api
这个 slf4j-api 的实现是依赖 logback-classic.jar, loggback-classic的底层依赖是 logback-core.jar
当我们引入第三方库的时候,记得要移除其自身的log包,例如,我们引入 spring 的话,则需要做如下配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring.core</artifactId>
<exclusions>
<exclusion> <!-- 这里我们在移除spring自带的commmons-logging -->
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
结论
springboot底层是slf4j+logback
springboot自身已经帮我们添加了中间转换包,无需额外再pom.xml添加
但第三方包内自带的log实现库,需要我们在添加第三方包的时候,手动移除那些不需要的log实现库
SpringBoot的web开发
静态资源映射规则
直接在static目录里加入自己的css或js,其合法目录在
- src/main/resources/META-INF/resources/
- src/main/resources/resources/
- src/main/resources/static/
- src/main/resources/public/
- src/main/resources/
使用webjars将第三方资源使用jar包的方式引入项目,例如引入jquery, vue等
- pom.xml中加入需要的js依赖 (下面的配置可以直接从 www.webjars.org 官网获取)
</dependencies> <!-- 引用jquery --> <dependency> <groupId>org.webjars</groupId> <artifactId>jquery</artifactId> <version>3.1.1</version> </dependency> <dependency> <groupId>org.webjars.npm</groupId> <artifactId>vue</artifactId> <version>2.6.11</version> </dependency> </dependencies>
然后在资源文件夹下新建index.html,合法的html存放的资源文件夹包括:
- src/main/resources/META-INF/resources/
- src/main/resources/resources/
- src/main/resources/static/
- src/main/resources/public/
- src/main/resources/
默认使用的首页是 index.html ,默认使用的网页图标识 favicon.ico
- 如果是spring mvc,则需要实现 webMvcConfigurer接口来定义这些静态文件的访问入口,如下
@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry .addResourceHandler("/webjars/**") .addResourceLocations("classpath:/META-INF/resources/webjars/"); } }
而spring boot项目则无需额外处理,底层自动做了 classpath:/META-INF/resources/webjars/ 到 /webjars/** 的映射。
- 这一步的意思是:我们访问 http://localhost:8080/wabjars/jquery/3.1.1/jquery.min.js 则等同于去访问 jquery-3.1.1.jar包内的 /META-INF/resources/webjars/jquery/3.1.1/jquery.min.js 文件。
- 上面解释中的 http://localhost:8080/wabjars/jquery/3.1.1/jquery.min.js 可以在spring boot启动后本地访问进行测试。
- 上面解释中的 “jquery-3.1.1.jar包内的 /META-INF/resources/webjars/jquery/3.1.1/jquery.min.js” 可以直接在intellijIdea的项目工程中去直接展开查看。
- 我们创建自己的html时,里面的引用的css,js文件路径则是
<script src="/webjars/jquery/3.1.1/jquery.min.js"></script> <script src="/webjars/bootstrap/3.3.7-1/js/bootstrap.min.js"></script> <title>WebJars Demo</title> <link rel="stylesheet" href="/webjars/bootstrap/3.3.7-1/css/bootstrap.min.css" />
如果我们想省略版本号,动态获取最新版本,则可以在pom.xml中增加配置
<dependency> <groupId>org.webjars</groupId> <artifactId>webjars-locator</artifactId> <version>0.31</version> </dependency>
webjars-locator会自动更新wabjars版本号,我们就可以省略wabjars的更新和路径了,我们可以设置html的加载如下 (下面都去除了版本号)
<script src="/webjars/jquery/jquery.min.js"></script> <script src="/webjars/bootstrap/js/bootstrap.min.js"></script> <title>WebJars Demo</title> <link rel="stylesheet" href="/webjars/bootstrap/css/bootstrap.min.css" />
- 访问本地 localhost:8080 即可看到效果
SpringBoot的数据访问
这里一般推荐使用docker创建mysql容器,用navicat查看mysql的更变会比较方便。
基本概念
- 数据源,数据库,连接池, 数据库驱动 分别是什么。
- 用户程序 访问【数据源】
- 【数据源】可以通过jdbc创建一个连接;也可以创建多个连接,然后存放到【连接池】,用的时候再从【连接池】中获取连接
- 【连接池】负责维护这些jdbc连接
- jdbc连接通过不同的【数据库驱动】访问不同类型的【数据库】
- JDBC是什么
- JDBC是一套规范的API接口,用来方便用户使用同一套接口访问多种不同的数据库。
- 在不同的数据库驱动对应关系下,可以访问不同类型的数据库,并对其进行创建连接,销毁连接,sql执行等操作。
- Druid是什么
- Druid是一个连接池 + 一个内置JDBC组件库 + 一些额外监控支持功能包。
- MyBatis是什么
- MyBatis是一个JDBC的封装库,使我们更方便使用JDBC。
- 它没有实现JPA接口
- JPA是什么
- JPA是一个ORM思想的接口框架,存在于用户应用程序和JDBC之间的一个中间层。它让用户开发时使用ORM面对对象的开发机制,它自己来进行对JDBC的映射。
- Hibernate是什么
- 它是一种ORM框架,实现了JPA的接口。它也是spring data 默认实现JPA层的库。
sprint data
SpringBoot默认采用SpringData的方式统一访问nosql和sql。
SpringData项目的目的是为了简化构建基于spring框架应用,所创建的一种简化数据访问的技术。
SpringData项目特点包括:
- 默认支持很多数据访问方式,包括jpa,jdbc, keyvalue, mangoDB, redis, solr, neo4j, elasticsearch, cassandra等。
- 使用任何一个数据访问,只需要导入对应的starters. 一般命名为:
- 特殊的有原生的jdbc,第三方的mybatis,其starter为
spring-boot-starter-data-jpa
spring-boot-starter-data-redis
spring-boot-starter-data-mangodb
spring-boot-starter-jdbc 【原生】
mybatis-sprint-boot-starter 【第三方】
- 它为我们对数据库访问提供了统一的CRUD(增删改查),排序和分页的功能接口。
- Repository 统一接口
- RevisionRepository 基于乐观锁机制,继承Repository
- CrudRepository 基本的CRUD操作,继承Repository
- PagingAndSortingRepository 基本CRUD以及分页操作,继承CRUDRepository
- JPARepository,继承PagingAndSortingRepository
- 它为我们进行数据访问提供了统一的模板类,例如
- RedisTemplate
- MangoTemplate
jdbc
使用整合流程
自动导入jdbc的starter,以及mysql驱动依赖
进行数据源的配置编写
spring:
datasource:
username: root
password: 123456
url: jdbc::mysql://192.168.12.22:3306/myDB
driver-class-name: com.mysql.jdbc.Driver
- 代码进行连接访问测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class MyTest{
@Autowired
DataSource dataSource;
@Test
public void TestContextLoader(){
System.out.println(dataSource.getClass());
Connection connection = dataSource.getConnnection();
System.out.println(connection);
connection.close();
}
}
/* 将打印出
class org.apache.tomcat.jdbc.pool.DataSource # 默认数据源是tomcat的
com.mysql.jdbc.JDBC4Connection@ba56f951 # 证明连接成功
*/
- spring boot默认支持三种数据源,也可以自己指定数据源
org.apache.tomcat.jdbc.pool.Datasource
HikariDataSource
BasicDataSource
默认sql语句执行机制
- 默认在程序启动时会先执行以下sql语句,用来创建表结构
schema.sql
schema-all.sql
以及
schema-*.sql // 其中的 * 必须是当前环境名,例如 dev, prod 等
- 然后会执行如下sql语句,用来初始化表数据
data.sql
data-all.sql
以及
data-*.sql // 其中的 * 必须是当前环境名,例如 dev, prod 等
如果不想遵循上述规则,可以在配置中进行指定
spring: datasource: username: root password: 123456 url: jdbc::mysql://192.168.12.22:3306/myDB driver-class-name: com.mysql.jdbc.Driver schema: # 程序每次启动时,都会运行这两个sql用来建表 - classpath: sql/mySchema1.sql - classpath: sql/mySchema2.sql
- 自动配置了jdbcTemplate,可用来操作数据库。
// 设立我们写个controller用来做测试。
@Controller
public class HelloController{
@Autowired
JdbcTemplate jdbcTemplate;
@ResponseBody
@GetMapping("/query")
public Map<String, Object> map(){
// 主要就是这里使用jdbcTemplate进行访问
List<String, Object> list = jdbcTemplate.quertForList("select * FROM myTable");
return list.get(0);
}
}
/* 访问 localhost:8080/query/ 将打印出数据库中第一行的数据
{
id: 1,
colume2: myContext...
}
*/
使用druid数据源
实际开发中,我们很少直接使用 jdbc 默认的数据源org.apache.tomcat.jdbc.pool.Datasource,通常会考虑使用 druid数据源,因为它有更多的监控和扩展功能。
想使用druid数据源,需要做如下操作:
引入druid
访问 maven 仓库: mvnrepository.com 查找druid,找到引入方式
在pom.xml引入库:
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.8</version> </dependency>
- 修改applation.properties/yaml ,指定数据源
spring: datasource: username: root password: 123456 url: jdbc::mysql://192.168.12.22:3306/myDB driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource
添加并使用jdbc的各种配置
- 先编写配置
spring: datasource: username: root password: 123456 url: jdbc::mysql://192.168.12.22:3306/myDB driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource initialSize: 5 minIdle: 5 maxAction: 20 maxWait: 60000 ....等等
- 此时上述配置无效,必须我们手动为其指定配置类
@Configration public class DruidConfig{ @ConfigrationProperties(prefix="spring.datasource") // 这里指定的从application.peoperties文件中读取 spring.datasource 组下的配置 @Bean public DataSource druid(){ return new DruidDataSource(); } }
开启druid的监控
- 配置一个管理后台的servlet,并初始化一些值,放到DruidConfig类中
@Bean public ServletRegisterationBean statViewServlet(){ ServletRegisterationBean bean = new ServletRegisterationBean(new StatViewServlet, "/druid/"); Map<String, String> initParams = new HashMap<>(); initParams.put("loginUsername", "admin"); // loginUsername这些是在druid内定义的 initParams.put("loginPassword", "123456"); bean.setInitParameters(initParams); return bean; }
- 配置一个监控的filter,并初始化一些值,放到DruidConfig类中
@Bean public FilterRegisterationBean webStatFilter(){ FilterRegisterationBean bean = new FilterRegisterationBean(); bean.setFilter (new WebStatFilter()); Map<String, String> initParams = new HashMap<>(); initParams.put("exclusions", "*.js,*.css,/druid/"); bean.setInitParameters(initParams); bean.setUrlPatterns(Arrays.asList("/*")); // 拦截全部请求 return bean; }
- 上面两部配置完毕后,就可以通过访问 localhost:8080/duird/ 访问到监控页面。
Mybatis
基本概念
它是对JDBC的进一步封装,但MyBatis并不是标准的ORM实现,它主要是帮我们减少JDBC的一些操作。
例如一般JDBC操作有如下四步:
//获取连接
connection = DriverManager.getConnection(url, username, password);
connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
connection.setAutoCommit(false);
//准备sql
String sql = "select * from book where book_id = ?";
statement = connection.prepareStatement(sql);
statement.setInt(1, 4);
//执行sql
ResultSet resultSet = statement.executeQuery();
ResultSetMetaData rsmd = resultSet.getMetaData();
int columnCount = rsmd.getColumnCount();
while (resultSet.next()) {
for (int i = 1; i <= columnCount; i++) {
System.out.println(i + " label:" + rsmd.getColumnLabel(i) + " type:" + rsmd.getColumnType(i));
}
}
//提交关闭
connection.commit();
connection.close();
使用MyBatis的话,则可以仅仅将注意力集中在SQL语句部分,对于前后的获取连接关闭连接都可以不用关心了。
使用整合流程
导入MyBatis的starter,以及JDBC以及mysql驱动。
修改配置,修改数据源,指定数据源配置等(上面jdbc的那些步骤)
在数据库中创建表(可以使用 schema.sql)
创建javaBean
- 假设我们在数据库中创建了两个表
- Employee表,5个字段
- Department表,2个字段
- 那么我们在java中创建两个对应的bean(下面代码省略了getter/setter)
private Integer id;
private String lastName;
private Integer gender;
private String email;
private Integer dId;
}
public class Department{
private Integer id;
private String departmentName;
}
使用MyBatis操作数据库
有两种方式可用:注解法 ,和配置法。
注解法
- 使用MyBatis对数据库进行增删改查
// DepartmentMapper.java @Mapper // 指定这是一个操作数据库的mapper public interface DepartmentMapper{ @Select("select * from department where id=#{id}") public Department getDepartmentById(Integar id); @Delete("delete from department where id=#{id}") public int deleteDepartmentById(Integar id); // 因为id自增,所以声明id为自增组件,而SQL插入时,也仅仅插入 Department 的 departmentName变量 @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); }
上面使用注解进行增删改查,我们可以写个controller进行测试
@RestController public class DepartmentController{ @Autowired DepartmentMapper departmentMapper; @GetMapping("/department/{id}") public Department getDepartment(@PathVariable("id") Integer id){ return departmentMapper.getDepartmentById(id); } @GetMapping("/department") public Department insertDepartment(Department department){ departmentMapper.insertDepartment(department); return department; } } /* 访问 localhost:8080/department?departmentName=TEST 则会发现页面返回 { id: 1, departmentName: TEST } 并且数据库也的确多了一条数据。 访问 localhost:8080/department/1 则会发现显示与上面相同,的确进行了数据库的查询。 */
批量注解法
当我们有大量的数据库表的时候,会有大量的bean ,于是也将需要大量的mapper对象进数据库操作。我们需要简化创建Mapper注解的方法
此时我们可以在 springBootApplication 上做@mapperScan标识,MapperScan所指定的包目录内,所有类都视为mapper,无需每个类指定Mapper
@MapperScan(value="com.freeeknight.myTest.mapper") @SpringBootApplication public class MyTestApplication{ public static void main(String[] args){ SpringApplication.run(MyTestApplication.class); } }
配置法
- 和上面一样,先将类扫描装配到容器中,但不用注解实现SQL语句
// DepartmentMapper.java @Mapper // 指定这是一个操作数据库的mapper,如果有了MapperScan,则这里可以不用指定mapper public interface DepartmentMapper{ public Department getDepartmentById(Integar id); public int deleteDepartmentById(Integar id); }
- 在resources/mybatis/中创建一个 mybatis-config.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> <!-- 这里做一些全局设置,可以推荐看看下面 --> <!-- https://mybatis.org/mybatis-3/zh/ --> </settings> </configuration>
- 在resources/mybatis/mapper中创建一个DepartmentMapper.xml 的sql映射文件
<mapper namespace="com.freeknight.myTest.mapper.DepartmentMapper"> <!-- 对应上面的getDepartmentById函数参数,返回值,以及SQL语句 --> <select id="getDepartmentById" parameterType="int" resultType="com.freeknight.myTest.bean.Department"> SELECT * FROM department WHERE ID = #{id} </select> <insert id="deleteDepartmentById"> insert into department(department) values(#{departmentName}) </insert> </mapper>
- 最后在项目配置中,指定mybatis的配置文件位置
spring: datasource: username: root password: 123456 url: jdbc::mysql://192.168.12.22:3306/myDB driver-class-name: com.mysql.jdbc.Driver type: com.alibaba.druid.pool.DruidDataSource initialSize: 5 mybatis: config-location: classpath: mybatis/mybatis-config.xml mapper-locations: classpath: mybatis/mapper/*.xml # 直接指定文件夹
注解法和配置法可以混用。不过,个人推荐注解法。
JPA
JPA是一个ORM思想的接口框架,存在于用户应用程序和JDBC之间的一个中间层。它让用户开发时使用ORM面对对象的开发机制,它自己来进行对JDBC的映射。
JPA与SpringData
Application-> SpringData(CRUDRepository)-> SprintDataJPA(JPARepository)-> JPA-> JDBC-> DB
配置使用JPA
- 首先进行配置数据源和JPA的基本设置
spring:
datasource:
username: root
password: 123456
url: jdbc::mysql://192.168.12.22:3306/myDB
driver-class-name: com.mysql.jdbc.Driver
jpa: # jpa的配置在这里
hibernate:
hbm2ddl-auto: update # 更新或创建数据表结构
#validate 加载hibernate时,验证创建数据库表结构
#create 每次加载hibernate,重新创建数据库表结构,这会导致数据库表数据丢失。
#create-drop 加载hibernate时创建,退出是删除表结构
#update 加载hibernate自动更新数据库结构
show-sql: true # 每次增删改查时显示sql
- 编写一个实体类(bean)和数据表进行映射,并且配置好映射关系
// entity\User.java
// 使用JPA注解配置映射关系
@Entity // 告诉JPA这是一个和数据表进行映射的实体类
@Table(name="tbl_user") // 指定映射的表
public class User{
@Id // 表示这是一个主键
@GeneratedValue(strategy = GenerationType.INDENTITY) // 这是自增主键,指定其自增策略
private Integer Id;
@Column(name="last_name", length=50) // 数据库列属性
private String lastName;
@Column // 省略name,则默认列名就是属性名
private String email;
// 下面省去了getter,setter函数
}
- 操作本表,编写一个DAO进行该类操作
// Repository\UserRepository.java
// 这里继承 JpaRepository 进行数据库操作,如果使用 mybatis 对数据库操作,则继承 Repository 并做mapper即可
// JpaRepository<C, T> 模板类的第一个参数是需要被操作的类名,第二个参数是被操作类中主键的类型,上面是Interger Id, 是整形。
public interface UserRepository extends JpaRepository<User, Integer>{
}
- 写一个controller进行增删改查的测试
// controller\UserController.java
@RestController
public class UserController{
@Autowired // 注入UserRepository可以直接使用
UserRepository userRepository;
@GetMapping("user/{id}")
public User getUserById(Integer Id){
return userRepository.findOne(id); // findOne这些函数都是被JpaRepository直接提供的
}
}
上面我们看到JPA和myBatis都可以对数据库进行增删改查,什么时候用什么呢?
- 如果查询结构复杂,你喜欢sql语句,或者要用存储过程,则使用MyBatis
- 如果能设计好数据库的ORM结构,简单使用sql,用JPA就好
- 两者可以同时使用。
SpringBoot原理
总结:SpringBoot的常见各层
Enitity层
实体类,和数据库中的属性值保持一致,实现getter, setter方法(可能实现 tostring 方法)
样例代码
@Entity
public class User{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "head_image_url")
private String headImageUrl;
// getter/setter
public void setId(Long id) {
this.id = id;
}
public Long getId() {
return id;
}
//...
}
Mapper层/DAO层
持久层,数据库操作层,负责对数据持久化操作,它是负责对数据库进行增删改查操作的
- 可以用JPA或者mybatis实现
样例代码
@Mapper
@Repository
public interface UserMapper {
@Select("select * from tbl_user where id=#{id}")
public User getUserById(Integar id);
}
Service层
业务层。主要封装自己的业务逻辑,提供给contoller层使用。 部分情况会将具体的实现放置在 ServiceImpl 中。
样例代码
public interface UserService extends Service<User> {
@Autowired // 注入UserRepository可以直接使用
UserMapper userMapper;
public User getUserById(Integer Id){
return userMapper.getUserById(id);
}
}
Controller层/web层
控制层。主要负责接收前端消息,并调用 Service 层进行业务处理
样例代码
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
@PostMapping("/add")
public Result add(User user) {
userService.addUser(user);
return ResultGenerator.genSuccessResult();
}
}
View层
- 主要是一些 jsp, html文件
- 因为我不打算使用spring mvc,所以不说了。
总结:JAVA对象
PO/DO
- Persistent object 持久化对象。也被称为data对象(简写为DO),Entity对象。
- 可以简单认为一个PO对应的是数据库中一个表结构。
- PO 中不应该有任何数据库的操作。
VO
- View object 表现层对象。
- 它是将前端某个页面中,所需要展示的所有数据封装起来的一个结构。
- 它是面对前端界面的,和后端存储未必有关系。
DTO/TO
- Data transfer object 数据传输对象。也被称为TO
- 它就是在数据进行传输时,组装出来的一个数据流格式。
(DAO)
- 这不是一个java对象
- Data access object 数据访问对象,它是进行数据库访问的一个对象,它通常配合PO一起使用,它对PO进行数据库操作,它处于业务层和数据库之间。
BO
- Business object业务对象。
- 这个是一个业务定义,和业务相关,也可以称为是一个逻辑对象
POJO
- plain ordinary java object,普通的java对象
- 这就是说,这个对象,即不是po(数据库对象),也不是vo(显示层对象),也不是dto(传输层对象),也不是BO(业务对象),这个对象非常难以定义其特殊用途的时候,就称为POJO
- 经常Bean可以视为一个pojo
变换关系
POJO持久化后,成为PO,通过DAO进行操作
POJO进行传输过程中,成了DTO
POJO在表现层(网页)展示的时候,成为了VO
例子
例如数据库两个表分别有100,88个字段,那么这两个PO都分别有100,88个属性。
假设这俩表内的业务相同,都要在一起进行一些逻辑操作,那么这个POJO中有这俩个表的全部数据,则POJO有188个属性。
然后假设其中30个属性要给前端显示,那么后端给前端的消息中,就有一个拥有30个属性的DTO传输对象。
前端收到30个DTO的传输对象时,假如分为三个页面进行展示,那么应该会有3个分别属性为10条的VO对象。
总结:Spring注解大全
声明bean
- @Component 基本的组件
- 定义给pojo,没有特殊作用的组件
- 其他几个注解的基类
- @Service 业务层使用
- 针对BO业务对象
- @Repository 属于访问层使用
- 针对 DAO 层
- @Controller 在控制层使用
- 针对表现层,负责http接口
- 【@Configuration】 声明配置类
- 这个注解本身目的不是做bean,但底层实现了Component,也确实创建了bean
- 【@SpringBootApplication】 主类标识
- 这个注解本身目的不是做bean,但底层实现了Component,也确实创建了bean
注入bean
@Autowired 自动依赖注入。spring提供的支持
使用spring时,应该优先使用这个
和inject可以互换,两者实现机理相同
可以增加required属性
可以配合@Qualifier显式指定装配的对象
@Autowired(required=false) // 默认Autowired的required是为true。当该值为false时,则表示不强制进行依赖,此时customTest将会为 null. @Qualifier(value = "customerTest1") // 如果我们有多个该类对象,则在xml中创建多个对象,然后此处指定绑定哪个对象 private CustomerTest customerTest;
例如上面对应的xml
<beans xmlns="http://www.springframework.org/schema/beans""> <bean id="customerTest1" class="com.example.demo1.Testclass.customerTest"> <property name="name" value="name1"></property> <property name="age" value="2"></property> </bean> <bean id="customerTest2" class="com.example.demo1.Testclass.customerTest"> <property name="age" value="3"></property> <property name="name" value="name2"></property> </bean> </beans>
- 可以配合@Primary显式指定装配对象
@Primary @Bean(name = "customerTest1") private CustomerTest customerTest;
@Inject 自动依赖注入。jsr330规范
不使用spring时,可以使用这个
和Autowired可以互换,两者实现机理相同
可以配合@Qualifier显式指定装配的对象
可以配合@Name注解
@Inject @Named("myBean") // 如果没有该项,则要求变量名必须和配置一致 private MyBean myBean;
@Resource 自动依赖注入。jsr250规范
应当避免使用
可以配合@Qualifier显式指定装配的对象
一般会配合一个name属性
@Resource(name = "userMapper") private UserMapper userMapper;
配置类
@Configuration 声明当前类为配置类。
一个配置类,相当于xml形式的spring配置
该注解作用于 类。
该注解内部包含了 @component 注解,所以本注释也就声明了这个配置类是一个 @bean
可以搭配 @ConfigurationProperties 标注,来指定配置文件路径
@Configuration @ConfigurationProperties(prefix = "redis.config") public class RedisConfiguration { private int maxTotal; private int maxIdle; }
配置文件 redis.config
redis.config.maxTotal=5000 redis.config.maxIdle=10
@ConfigurationProperties 标注还可以进一步配合 @EnableConfigurationProperties 标注。
配置类还可以加 @AutoConfigureAfter,@AutoConfigureBefore 可以指定配置的先后依赖关系
@AutoConfigureAfter(DataSourceAutoConfiguration.class) // 指定这个配置类加载必须在DataSourceAutoConfiguration类配置之后。 public class MybatisAutoConfiguration { }
@SpringBootConfiguration 和Configuration一样,只是spring boot专有的
@Bean 声明一个函数,表示该函数将返回一个bean对象。
相当于xml配置中的一个
标签的bean初始化 该注解作用于 函数/方法。
通常存在在 @configuration 配置类中
不允许存在于@component 组件类中
通常配合使用@scope注解,来指定创建bean的方式
Singleton (单例,一个Spring容器中只有一个bean实例,默认模式),
Protetype (每次调用新建一个bean)
Request (web项目中,给每个http request新建一个bean),
Session (web项目中,给每个http session新建一个bean)
GlobalSession(给每一个 global http session新建一个Bean实例)
@Configuration public class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... } }
- 可以配合name属性指定别名
@Bean(name = { "dataSource", "subsystemA-dataSource"}) public DataSource dataSource() { // ... }
- 可以配合initMethod,destroyMethod属性指定bean初始化函数和释放函数
@Bean(initMethod = "init") public Foo foo() { return new Foo(); } @Bean(destroyMethod = "cleanup") public Bar bar() { return new Bar(); }
@ComponentScan 用于对@Component的包扫描
- 一般作用在主入口类上
- 相当于xml中的
,全部bean记录
@EnableAutoConfiguration 开启自动配置注解
- 开启本注解,则会自动将 “spring-boot-autoconfigure.jar/META-INF/spring.factories文件中org.springframework.boot.autoconfigure.EnableAutoConfiguration的value值列表” 中所有 xxxAutoConfiguration 的类进行配置加载。
- 我们一般不会手动使用该注解。
@WishlyConfiguration 相当于@Configuration与@ComponentScan的组合注解,可以替代这两个注解
@Import 导入指定的配置类
@Configuration
@Import({ CustomerConfig.class, SchedulerConfig.class })
public class AppConfig {
}
- @ImportResource 加载xml文件,在xml 文件中再指定配置文件
@Configuration
@ImportResource("classpath:cons-injec.xml") //导入xml配置项
public class SoundSystemConfig {
}
配置文件 cons-injec.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans ">
<bean id="compactDisc" class="com.jiaobuchong.soundsystem.BlankDisc">
<constructor-arg> <!--这里就是配置项,在配置一个bean的构造函数->
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
</list>
</constructor-arg>
</bean>
</beans>
- @Value注解进行赋值
@Value("Michael Jackson") // 简易赋值
String name;
@Value("#{domeClass.name}") // 赋值其他bean的属性
String name;
@Value("${book.name}") // 注入配置中的属性(假设test.properties配置文件中有如下一行 book.name=《三体》)
String bookName;
环境配置注释
- @Profile
@Component
@Profile("dev")
public class DevDatasourceConfig{
}
配合application.properties配置中的下列项共同作用
spring.profiles.active=dev
定时任务注释
- @EnableScheduling 在配置类上使用,开启计划任务的支持
- 作用于 类
- @Scheduled 来申明这是一个任务,包括cron,fixDelay,fixRate等类型
- 作用于 方法/函数
异步操作注释
- @EnableAsync 配置类中,通过此注解开启对异步任务的支持
- 作用于 类
- @Async 在实际执行的bean方法使用该注解来申明其是一个异步任务
- 作用于 方法/函数
功能开启类注释
- Enable* 这是一系列的注解,用来开启对某功能的支持,例如
- @EnableConfigurationProperties 开启对@ConfigurationProperties注解配置Bean的支持
- @EnableAsync 开启异步方法的支持
- @EnableScheduling 开启计划任务的支持
测试相关类注释
- @RunWith 运行器,在Spring中通常用于对JUnit的支持
@RunWith(SpringJUnit4ClassRunner.class)
- @ContextConfiguration 用来加载配置,和 import有些类似,但它仅作用于 RunWith 测试环境下,和import不可互换
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestConfig.class})
public class TestConfig{
// ...
}
入口类
- @SpringBootApplication 用在主类上,标识这是一个spring boot应用
- 这是个最重要的核心类
- 实际上这个注解是 @SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan三个注解的合集。
条件类
- @Conditional 它是一个系列,上文有提及,主要配合 @bean @configration使用,当符合指定条件下,@bean, @configration才有效。
@Component
@ConditionalOnBean(name="redisTemplate") // 当有这个bean,这个类才会算为bean被IOC容器管理
public class RedisOperBean {
private final RedisTemplate redisTemplate;
// ...
}
@Configuration
@ConditionalOnClass({ Feign.class }) // 当这个类存在,该配置类才有效
public class OAuth2FeignAutoConfiguration {
}
TODO List:
SpringBoot与缓存:redis
SpringBoot与消息:RebbitMQ
SpringBoot与检索:ES
SpringBoot与任务:邮件任务, 定时任务, 异步任务
SpringBoot与安全:SpringSecurity
SpringBoot与分布式:ZooKeeper, dubbo, springCloud
SpringBoot与开发热部署
SpringBoot与监控管理
附注:YAML语法
基本语法
以 空格 为缩进,控制层级关系(tab不允许使用)。空格空多少,无所谓,只要空格数量相同,则视为同一级。
K: V 表示一个键值对。注意:K:后面必须加 空格,然后再加 V。
#号是注释
属性和值都是大小写敏感的。
例如
server:
port: 8080
path: /hello
值的语法
字符串通常不需要加引号。
双引号 包装的字符串,会做自动转义工作; 单引号包装的字符串则会视为字符串。默认不加引号,会视为加了单引号。例如
name: "zhangsan \nnihao" # 输出就是 zhangsan 换行nihao
name: 'zhangsan \nnihao' # 输出就是 zhangsan\nnihao
name: zhangsan \nnihao # 输出就是 zhangsan\nnihao
布尔类型用小写 true, false 表示
空值用 null 或 ~ 表示
日期使用 iso-8601标准 : yyyy/MM/dd HH:mm:ss
文本块可以用 | 符号表示,比较烦,不细说了
引用。& 表示锚点, * 表示引用锚点数据。例如
name: &a freeknight
anothername: *a
names:
- demacia
- *a
# 输出anothername: freeknight
# 输出names: [demacia,freeknight]
defaults: &defaultconfig
adapter: postgres
host: localhost
development:
database: myapp_development
<<: *defaultconfig
# 等同于
defaults:
adapter: postgres
host: localhost
development:
database: myapp_development
adapter: postgres
host: localhost
- 对象
person:
name: freeknight
age: 30
# 或者单行写法
person: {name: freeknight,age: 30}
- 数组
myArrays:
- 12
- 22
# 或者单行写法
myArrays: [12,22]
允许双感叹号的类型强转
name: !!str true # 这里的name是 string, 不是布尔型。
附注:深入自动配置原理
流程分析
启动时,spring boot通过main入口的 @SpringBootApplication 注解进行解释。
查看@SpringBootApplication注解,我们会发现@EnableAutoConfiguration字段,它进行配置自动加载。
查看@EnableAutoConfiguration注解,我们会发现它进行了 @import({AutoConfigurationImportSelector.class}) ,用它向容器中导入组件
我们看 AutoConfigurationImportSelector类中的 getAutoConfigurationEntry()函数会发现,
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
- 继续跟踪 getCandidateConfigrations 函数,我们会发现:
List<String> configurations = SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader());
- 继续跟踪 loadFactoryNames 函数,我们会发现
Enumeration<URL> urls = classLoader != null ? classLoader.getResources("META-INF/spring.factories") : ClassLoader.getSystemResources("META-INF/spring.factories");
while(urls.hasMoreElements()) {
Properties properties = PropertiesLoaderUtils.loadProperties(resource);
}
从上面可知,spring boot通过扫描所有jar包内的 META-INF/SPRING.FACTORIES 文件,来进行组件的加载的同时,加载其中的 @propertySource 的值。
打开项目中 external libraries中的Mavens:org.springframework.bootLspring-boot-autoconfigure:2.x.x包,找到其中的META-INF/spring.factories文件,我们看到
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.web.servlet.HttpEncodingAutoConfiguration,\
.....一大堆类....\
org.springframework.boot.autoconfigure.webservices.WebServicesAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
- 我们拿其中一个类 HttpEncodingAutoConfiguration 为例进行查看
// 说明这是一个配置类,可以给容器中添加组件
@Configuration(
proxyBeanMethods = false
)
// 启用指定类的 configration properties功能,即将配置文件中的值,和httpProperties这个类的变量进行了绑定。下面我们会看 HttpProperties 类内容
@EnableConfigurationProperties({HttpProperties.class})
// 这个注解意思是说,当符合指定的condition(本例中意思是,必须是 web 项目)。这个 HttpEncodingAutoConfiguration 类才会生效,否则本类无效,
@ConditionalOnWebApplication(
type = Type.SERVLET
)
// 判断当前项目中是否有 CharacterEncodingFilter 这个类(该类是spring mvc中解决乱码的标准过滤器类)。如果有的话,则当前的这个 HttpEncodingAutoConfiguration 类生效,否则本类无效,
@ConditionalOnClass({CharacterEncodingFilter.class})
// 继续判断当前项目是否有 “spring.http.encoding.value” 这个配置的属性,有的话,则读取;如果不存在,这个 spring.http.encoding.value 依然认为是 enabled(因为matchIsMissing)。
@ConditionalOnProperty(
prefix = "spring.http.encoding",
value = {"enabled"},
matchIfMissing = true
)
public class HttpEncodingAutoConfiguration {
// 这个properties中的值,已经被 @EnableConfigurationProperties({HttpProperties.class}) 这个注解和 HttpProperties 类绑定了。
private final Encoding properties;
// 这个Bean说明,该配置类如果有效,则会创建bean。并且,这个bean中的属性,会从 properties进行读取
@Bean
@ConditionalOnMissingBean
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
return filter;
}
// ....其他代码
}
我们打开httpProperties类,可见该类中有大量需要进行配置的变量。
// 这个注解说明,本类中的值,是从配置文件中进行读取的
@ConfigurationProperties(
prefix = "spring.http"
)
public class HttpProperties {
public static final Charset DEFAULT_CHARSET;
private Charset charset;
private Boolean force;
private Boolean forceRequest;
private Boolean forceResponse;
private Map<Locale, Charset> mapping;
public Encoding() {
this.charset = DEFAULT_CHARSET;
}
public Charset getCharset() {
return this.charset;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public boolean isForce() {
return Boolean.TRUE.equals(this.force);
}
// ....其他代码
}
上面的属性类中的变量,也就是我们在配置文件application.properties里面可以配置项名。例如:
spring.http.charset=utf-8
spring.http.force=true
spring.http.forceRequest=true
spring.http.forceResponse=true
于是,上面我就知道了。spring boot里面是如何创建大量bean(看 spring.factories 文件),bean对象所在类如何绑定属性类(HttpProperties),以及这些属性类是如何读取配置的全部流程。
实战意义
- 我们知道了spring boot启动时会创建大量默认组件,并加载大量的自动配置类。
- 我们做任何事情,应该
- 先去检查是否有spring boot默认写好的自动配置类
- 然后,我们看看这个自动配置类内有哪些组件
- 然后,我们看这些组件,从properties里面获取了哪些属性
- 最后,我们在配置文件中如何配置这些属性
配置判断
上面我们的例子中有使用 @ConditionalOnWebApplication 等condition注解,只有这些注解判断成功后,那个配置类才会真正生效,否则,配置类内的自动配置和自动添加组件将会无效。
那么我们还包括哪些配置条件注解呢?
@Conditional扩展注解 | 作用(判断是否满足当前指定条件) |
---|---|
@ConditionalOnJava | 系统的java版本是否符合要求 |
@ConditionalOnBean | 容器中存在指定Bean; |
@ConditionalOnMissingBean | 容器中不存在指定Bean; |
@ConditionalOnExpression | 满足SpEL表达式指定 |
@ConditionalOnClass | 系统中有指定的类 |
@ConditionalOnMissingClass | 系统中没有指定的类 |
@ConditionalOnSingleCandidate | 容器中只有一个指定的Bean,或者这个Bean是首选Bean |
@ConditionalOnProperty | 系统中指定的属性是否有指定的值 |
@ConditionalOnResource | 类路径下是否存在指定资源文件 |
@ConditionalOnWebApplication | 当前是web环境 |
@ConditionalOnNotWebApplication | 当前不是web环境 |
@ConditionalOnJndi | JNDI存在指定项 |
补充:
- 配置判断注解,不仅仅可以作用于配置类,还可以作用于函数
- 我们还可以通过重写@conditional 注解类中的match()函数,来实现自定义的配置条件判断
- 我们可以在 application.properties / yaml 中添加 debug = true 的属性,让控制台在启动spring boot时打印自动配置报告,可以看出自动加载了哪些配置,哪些配置因为什么原因而没有加载。
附注:Docker
核心概念
- DockerHost(主机):安装了docker程序的机器。
- Docker是安装到操作系统上的一个软体。
- Docker安装后,将会运行守护进程和其容器。
- DockerImages(镜像):将一组软件打包后的一个docker模板,用来快速创建容器。
- 一个镜像可以启动多个容器
- 容器内的端口是独立的,不会冲突。例如开启了三个mysql容器都监听3306,是没有问题的。
- DockerContanier(容器):将镜像启动后的一组独立运行的一个或一组应用实例。
- DockerClient(客户端):用来连接docker主机进行操作,通过命令行或UI方式和docker守护进程通信。
- DockerRegistry(仓库):用来保存打包好的docker镜像的地方。
- 推荐的公共仓库是 hub.docker.com
简要用流程
- 安装docker
- 去docker镜像仓库找到对应软件的镜像
- 使用docker client运行这个镜像,该镜像就会生成一个容器
- 关闭容器,就是关闭软件
常用操作
进入命令行或powershell
镜像操作
docker version:查看docker版本
docker search 关键字:搜索指定镜像。等同于直接访问hub.docker.com进行搜索。
PS C:\user\freeknight> docker search redis
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
redis Redis is an open source key-value… 8191 [OK]
bitnami/redis Bitnami Redis Docker Image 145 [OK]
...
- docker pull 镜像名:tag 其中后面的tag可选,表示镜像的版本,默认是 latest
PS C:\user\freeknight> docker pull bitnami/redis:5.51
...
868de938a9c1b30: pull complte
...
Digest: sha256: 868de938a9c1b30868de938a9c1b30868de938a9c1b30868de938a9c1b30
- docker images 查看本地的所有镜像
PS C:\user\freeknight> docker images
RESPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 5d4d51c2ea08 3 days ago 25.2MB
- docker rmi image-id 删除本地指定镜像,传入上面的IMAGE ID
PS C:\user\freeknight> docker rmi 5d4d51c2ea08
deleted: sha256: 868de938a9c1b30868de938a9c1b30868de938a9c1b30868de938a9c1b30
容器操作
- docker run –name container-name -d image-name : 参数container-name为自定义容器名,image-name为镜像模板名,-d表示后台运行。运行一个镜像,这将产生一个新容器。
- -p 8888:8080: -p 是 进行端口映射,从主机端口8888(映射到)容器内部端口8080。例如我们容器内启动了一个tomcat,它监听8080端口,但我们在开发机上访问本机8080是无效的,必须将端口进行映射出来。于是我们将容器中的8080映射到主机的8888端口,然后我们在开发机访问8888端口即可。
- -e PASSWORD=root : -e 是进行启动时的参数传入
- -v /config/:/etc/config : -v 是文件配置传入,将本地的/config/文件夹内的内容,传递给容器内的/etc/config/内,通常用作配置文件的传递。
- docker ps: 查看当前有哪些容器被运行状态
- docker stop container-id 或者 docker stop contanier-name : 停止运行中的容器
- docker ps -a 查看所有容器(包括运行中的和被停止的)
- docker start container-id 或者 docker start contanier-name : 启动一个停止中的容器
- docker rm container-id : 删除一个容器(该容器必须是被停止状态)
- docker logs container-id或container-name: 查看一个容器日志
docker错误
虚拟机中启动提示:
虚拟机管理服务无法启动虚拟机“DockerDesktopVM”,因为一个 Hyper-V 组件尚未运行
解决方法:
- 设置virtualbox, 设置->系统->处理器-> 开启嵌套VT-x
- 进入windows虚拟机,设置->程序与应用->启用或关闭windows功能->开启container和hyper-V功能
- 重启电脑,进入系统,任务管理器->性能->查看【虚拟机】项的值是否是 【enable/是】。
- 右键点击docker desktop图标,switch to windows containers
- 应该OK了
下载镜像时提示:
no matching manifest for windows/amd64
解决方法:
1. 打开docker desktop的setting界面
2. 选择Docker engine,设置 "experimental": true (默认该值为falses)
3. apply and restart
附注:MyBatis
基本概念
与JDBC和Hibernate关系
JDBC
- 是一个对多种数据库的统一API接口,底层接各种数据库驱动,统一方式调用。
- 它使用jdbc template进行数据库增删改查
- 它需要自己处理如下操作
- 编写sql
- 预编译
- 设置参数
- 执行sql
- 封装结果
Hibernate
- 它是一个全自动,全映射的ORM框架,是对JDBC的封装。
- 其目的是去SQL化,需要使用SQL语句,就可以通过对象操作进行数据库的增删改查
- 将JDBC的上述五步操作全部封装。
- 缺点:
- 因为封装了SQL,我们无法设置SQL,导致无法进行优化。
- 因为是全映射的,所以当我们想取出某一条字段数据时,是无法达成的。它一次query就是整个object的全部属性(即数据库中整条数据全部列)的获取。
- 为解决上述问题,可以学习一个特殊的语法 HQL,但这东西太复杂,学习成本反而增加了。
MyBatis
它是一个半自动框架。
类似Hibernate,它也做了封装,但它将后面的四步完全自动化,即下面四步不用管了
预编译
设置参数
执行sql
封装结果
但第一步“编写sql”这一步开放给用户,通过配置文件或注解的方式给了用户,这样的好处就是: 可以优化SQL,可定制SQL。解决了hibernate的问题。
基本使用MyBatis
- 下载
https://github.com/mybatis/mybatis-3
- 添加依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>x.x.x</version>
</dependency>
- 通过全局配置xml文件创建mybatis的sqlSessionFactory。这个sqlSessionFactory就是一个sql连接池,mybatis负责维护。
<!-- mybatis-config.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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://loclhost:3306/myTestDB"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- sql mapper配置文件 -->
<mapper resource="mybatis/EmployeeMapper.xml"/>
</mappers>
</configuration>
上面的全局配置文件,也可以使用代码的方式实现。下面的代码等同于上述上面的xml.
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource(); TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("development", transactionFactory, dataSource); Configuration configuration = new Configuration(environment); configuration.addMapper(EmployeeMapper.class); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
创建初始类,用来创建sqlSessionFactory
然后从sqlSessionFactory中获取一个sqlsession实例,一个sqlseesion实例就是一个和数据库的一次会话。
然后进行sql执行增删改查
最后关闭这个session会话
public class MyBatisTest{
@Test
public void Test1(){
String resource = "mybatis/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try(SqlSession openSession = sqlSessionFactory.openSession()){
// 前面这个参数是xml中 命名空间+id,后面是参数 id 的值
Employee employee = session.selectOne("com.freeknight.mybatis.EmployeeMapper.selectEmployee", 1);
}finally{
// 最后,关闭session
openSession.close();
}
}
}
- 创建一个 mapper 的xml配置文件,内含sql语句
<!-- EmployeeMapper.xml -->
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- 命名空间只是拿来限制作用域 -->
<mapper namespace="com.freeknight.mybatis.EmployeeMapper">
<!-- id 是这个语句的唯一标识,resulttype是返回值类型 -->
<select id="selectEmployee" resultType="com.freeknight.mybatis.Employee">
select * from tbl_employee where id = #{id}
</select>
</mapper>
上面的xml文件也可以使用编码标注方式实现,编码标注方式也是spring boot推荐的方式。下面文件等同于上面的xml配置文件。
package com.freeknight.mybatis; public interface EmployeeMapper { @Select("select * from tbl_employee where id = #{id}") Employee selectEmployee(int id); }
使用编码标注的方式的话,那么session进行 sql调用时可以使用一个更安全简单的方式
@Test public void Test2(){ // 这里无需重新new了,直接获取即可 SqlSessionFactory sqlSessionFactory = getSqlSessionFactory(); try(SqlSession openSession = sqlSessionFactory.openSession()){ EmployeeMapper mapper = session.getMapper(EmployeeMapper.class); Employee employee = mapper.selectEmployee(101); /* 上面两行等同于之前的 Employee employee = session.selectOne("com.freeknight.mybatis.EmployeeMapper.selectEmployee", 1); */ }finally{ openSession.close(); } }
样例po entity类
public class Employee{
private Integer id;
private String lastName;
private String email;
//getter/setter/tostring
}
全局配置文件的属性
https://mybatis.org/mybatis-3/zh/configuration.html#mappers
- 配置文件可以引入第三方配置文件中的配置,例如
<configuration>
<!-- 这里表示加载第三方配置 -->
<properties resource="application.properties"></properties>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="&{jdbc.driver}"/>
<property name="url" value="&{jdbc.url}"/>
<property name="username" value="&{jdbc.username}"/>
<property name="password" value="&{jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis/EmployeeMapper.xml"/>
</mappers>
</configuration>
# application.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://loclhost:3306/myTestDB
jdbc.username=root
jdbc.password=123456
映射文件的属性
https://mybatis.org/mybatis-3/zh/sqlmap-xml.html
cache
– 该命名空间的缓存配置。cache-ref
– 引用其它命名空间的缓存配置。resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。sql
– 可被其它语句引用的可重用语句块。insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。(我们下面的例子中,使用的是这个)
public interface EmployeeMapper {
// 下面的 @Select 表示这是
@Select("select * from tbl_employee where id = #{id}")
Employee selectEmployee(int id);
}
注意点
- sqlSession是非线程安全的,所以不要把它作为一个成员变量,以避免多线程竞争。每次使用时都应该从sqlSessionFactory中去获取。