1. SpringBoot
1. SpringBoot
介绍
SpringBoot 是 Spring 中的一个成员, 可以简化 Spring, SpringMVC 的使用. 他的核心还是 IOC 容器.
特点:
Create stand-alone Spring applications
创建 spring 应用
Embed Tomcat, Jetty or Undertow directly (no need to deploy WAR files)
内嵌的 tomcat, jetty , Undertow
Provide opinionated 'starter' dependencies to simplify your build configuration
提供了 starter 起步依赖, 简化应用的配置.
比如使用 MyBatis 框架 , 需要在 Spring 项目中, 配置 MyBatis 的对象 SqlSessionFactory , Dao 的代理对象
在 SpringBoot 项目中, 在 pom.xml 里面, 加入一个 mybatis-spring-boot-starter 依赖
Automatically configure Spring and 3rd party libraries whenever possible
尽可能去配置 spring 和第三方库.叫做自动配置(就是把 spring 中的, 第三方库中的对象都创建好, 放到容器中, 开发人员可以直接使用)
Provide production-ready features such as metrics, health checks, and externalized configuration
提供了健康检查, 统计, 外部化配置
Absolutely no code generation and no requirement for XML configuration
不用生成代码, 不用使用 xml, 做配置
创建 Spring Boot 项目
使用 maven 创建
创建一个 maven 空项目
参考 SpringBoot 官网[Getting Started (spring.io)创建 SpringBoot 项目
导入 SpringBoot 父工程 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>myproject</artifactId>
<version>0.0.1-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>
<!-- Additional lines to be added here... -->
</project>
添加 web 依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
编写 "Hello World!"
@RestController
@SpringBootApplication
public class MyApplication {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
运行 main 方法后直接在浏览器访问 http://localhost:8080/ 即可, SpringBoot 已经集成 tomcat.
运行时可以查看控制台打印的日志信息:
使用模板创建
使用 Spring 提供的初始化器, 就是向导创建 SpringBoot 应用
默认地址: https://start.spring.io
国内的地址: https://start.SpringBoot.io
SpringBoot 项目的结构:
自动装配原理
依赖管理
SpringBoot 使用父项目做依赖管理
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>
我们的项目在继承了父项目后就无需关注其他依赖的版本号, 原因是 spring-boot-starter-parent
的父项目 spring-boot-dependencies
里面声明了几乎所有常用依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.3</version>
</parent>
当需要使用自己的版本依赖时可以和往常一样添加<version>
标签或者重写 SpringBoot 默认的版本号
<properties>
<mysql.version>5.1.43</mysql.version>
</properties>
开发导入 starter 场景启动器
- 见到很多 spring-boot-starter-*: *就某种场景
- 只要引入 starter,这个场景的所有常规需要的依赖我们都自动引入
- SpringBoot 所有支持的场景 Developing with Spring Boot
- 见到的 *-spring-boot-starter: 第三方为我们提供的简化开发的场景启动器
- 所有场景启动器最底层的依赖
自动装配
点开 spring-boot-starter-web
查看
- 自动配好 Tomcat
引入 Tomcat 依赖
配置 Tomct
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<version>2.7.3</version>
<scope>compile</scope>
</dependency>
- 自动配好 SpringMVC
引入 SpringMVC 全套组件
自动配好 SpringMVC 常用组件(功能)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.3.22</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.3.22</version>
<scope>compile</scope>
</dependency>
- 自动配好 Web 常见功能,如:字符编码问题
- SpringBoot 帮我们配置好了所有 web 开发的常见场景
main 中调用的 run 方法能够返回 ioc 容器, 可以查看全部的 SpringBoot 组件
@RestController
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
// 返回IOC容器
ConfigurableApplicationContext run = SpringApplication.run(MyApplication.class, args);
// 查看容器内的组件
for (String name : run.getBeanDefinitionNames()) {
System.out.println(name);
}
}
}
组件包含几乎所有常见配置
如前端控制器 dispatcherServlet
统一字符编码 characterEncodingFilter
视图解析器 viewResolver
文件上传解析器 multipartResolver
- 默认的包结构
主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
无需以前的包扫描配置
想要改变扫描路径: @SpringBootApplication(scanBasePackages = "com.example")
@SpringBootConfiguration 包含了 @ComponentScan
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}
所以主配置类也可以写成
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.example")
public class MyApplication {}
- 各种配置拥有默认值
- 默认配置最终都是映射到某个类上, 如: MultipartProperties
- 配置文件的值最终会绑定每个类上, 这个类会在容器中创建对象
- 按需加载所有自动配置项
- 非常多的 starter
- 引入了哪些场景这个场景的自动配置才会开启
- SpringBoot 所有的自动配置功能都在 spring-boot-autoconfigure 包里面
注解的使用
@SpringBootApplication
主方法中的 @SpringBootApplication 是一个复合注解:
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)})
public @interface SpringBootApplication {
}
@SpringBootConfiguration
该注解依旧是一个复合注解:
@Configuration
@Indexed
public @interface SpringBootConfiguration {
@AliasFor(
annotation = Configuration.class
)
boolean proxyBeanMethods() default true;
}
@Configuration
public @interface Configuration {
@AliasFor(annotation = Component.class)
String value() default "";
boolean proxyBeanMethods() default true;
}
因为包含了 @Configuration 注解 所以使用了 @SpringBootConfiguration 注解标注的类, 也可以作为配置文件使用的, 可以使用 Bean 声明对象, 注入到容器.
@EnableAutoConfiguration
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
Class<?>[] exclude() default {};
String[] excludeName() default {};
}
启用自动配置, 把 java 对象配置好, 注入到 spring 容器中. 例如可以把 mybatis 的对象创建好, 放入到容器中
@AutoConfigurationPackage
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
}
AutoConfigurationPackages.Registrar.class 类是自动装配的核心
static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
}
@Override
public Set<Object> determineImports(AnnotationMetadata metadata) {
return Collections.singleton(new PackageImports(metadata));
}
}
AnnotationMetadata metadata: 注解元信息, 即 @AutoConfigurationPackage, 又因为 @SpringBootApplication 包含了 @AutoConfigurationPackage, 所以 metadata 获取到的元数据就是主程序类
new PackageImports(metadata).getPackageNames(): 通过元数据获取到该类的所在的包
最后通过包名将该包下所有的组件都注册到容器内
@Import(AutoConfigurationImportSelector.class)
在源码内利用 getAutoConfigurationEntry(annotationMetadata);
方法给容器批量导入一些组件
最终利用 List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
获取所有需要导入到容器的组件
SpringBoot 2.7.3 版本有 144 个组件
getCandidateConfigurations(annotationMetadata, attributes)
方法最终利用工厂Map<String, List<String>> loadSpringFactories(ClassLoader classLoader)
加载所有方法
getCandidateConfigurations--> loadFactoryNames--> loadSpringFactories
@ComponentScan
public @interface ComponentScan {
@AliasFor("basePackages")
String[] value() default {};
@AliasFor("value")
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;
Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;
ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;
String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;
boolean useDefaultFilters() default true;
Filter[] includeFilters() default {};
Filter[] excludeFilters() default {};
boolean lazyInit() default false;
@Retention(RetentionPolicy.RUNTIME)
@Target({})
@interface Filter {
FilterType type() default FilterType.ANNOTATION;
@AliasFor("classes")
Class<?>[] value() default {};
@AliasFor("value")
Class<?>[] classes() default {};
String[] pattern() default {};
}
}
@ComponentScan 包扫描器, 找到注解, 根据注解的功能创建对象, 给属性赋值等等. 默认扫描的包: @ComponentScan 所在的类所在的包和子包(SpringBoot 主配置类在项目主目录的原因).
SpringBoot 的配置文件
配置文件名称: application
扩展名有: properties( k=v); yml(yaml) ( k: v)
使用 application.properties, application.yml
具体都可以配置哪些信息参考官方文档
Common Application Properties (spring.io)
properties
提示
properties 优先级最高
例 1: application.properties 设置 端口和上下文
#设置端口号
server.port=8082
#设置访问应用上下文路径, contextpath
server.servlet.context-path=/myboot
yml(yaml)
例 2: application.yml
server:
port: 8083
servlet:
context-path: /myboot2
相关信息
YAML 是 "YAML Ain't Markup Language"(YAML 不是一种标记语言)的递归缩写。
在开发的这种语言时,YAML 的意思其实是:"Yet Another Markup Language"(仍是一种标记语言)。
非常适合用来做以数据为中心的配置文件
基本语法
- key: value;kv 之间有空格
- 大小写敏感
- 使用缩进表示层级关系
- 缩进不允许使用 tab,只允许空格
- 缩进的空格数不重要,只要相同层级的元素左对齐即可
- '#'表示注释
- 字符串无需加引号,如果要加,''与""表示字符串内容 会被 转义/不转义
数据类型
字面量:单个的、不可再分的值。date、boolean、string、number、null
k: v
对象:键值对的集合。map、hash、set、object
# 行内写法: k: {k1:v1,k2:v2,k3:v3} # 或 k: k1: v1 k2: v2 k3: v3
数组:一组按次序排列的值。array、list、queue
# 行内写法: k: [v1,v2,v3] #或者 k: - v1 - v2 - v3
示例
@Data
public class Person {
private String userName;
private Boolean boss;
private Date birth;
private Integer age;
private Pet pet;
private String[] interests;
private List<String> animal;
private Map<String, Object> score;
private Set<Double> salarys;
private Map<String, List<Pet>> allPets;
}
@Data
public class Pet {
private String name;
private Double weight;
}
# yaml表示以上对象
person:
userName: zhangsan
boss: false
birth: 2019/12/12 20:12:33
age: 18
pet:
name: tomcat
weight: 23.4
interests: [篮球, 游泳]
animal:
- jerry
- mario
score:
english:
first: 30
second: 40
third: 50
math: [131, 140, 148]
chinese: { first: 128, second: 136 }
salarys: [3999, 4999.98, 5999.99]
allPets:
sick:
- { name: tom }
- { name: jerry, weight: 47 }
health: [{ name: mario, weight: 47 }]
配置提示
自定义的类和配置文件绑定一般没有提示
<!-- 配置提示 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!-- 项目打包时忽略该jar包 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
多环境配置
有开发环境, 测试环境, 上线的环境.
每个环境有不同的配置信息, 例如端口, 上下文件, 数据库 url, 用户名, 密码等等
使用多环境配置文件, 可以方便的切换不同的配置.
使用方式: 创建多个配置文件, 名称规则: application-环境名称.properties(yml)
创建开发环境的配置文件: application-dev.properties(application-dev.yml)
创建测试者使用的配置: application-test.properties
- 创建不同环境下的配置文件
在 properties 配置文件下指定当前使用的配置文件
指定对应文件后缀就行
# 激活使用哪个配置文件
spring.profiles.active=dev
@Value
从配置文件中获取数据
@Value("$(key)"), key 来自 application.properties(yml)
编写配置文件
#配置端口号 server.port=8082 #context-path server.servlet.context-path=/myboot
在 Controller 层使用@Value 注解获取配置文件的信息
@Controller public class HelloController { @Value("${server.port}") private Integer port; @Value("${server.servlet.context-path}") private String contextPath; @RequestMapping("/hello") @ResponseBody public String queryData() { return "port:" + port + " contextPath:" + contextPath; } }
启动项目并根据配置文件的配置信息进行访问
@ConfigurationProperties
@ConfigurationProperties: 把配置文件的数据映射为 java 对象.
只有在容器中的组件,才会拥有 SpringBoot 提供的强大功能, 一般需要配合 @Component 一起使用
属性: prefix 配置文件中的某些 key 的开头的内容.
@Data
@Component
@ConfigurationProperties(prefix = "school")
public class SchoolInfo {
private String name;
private String website;
private String address;
}
application.properties
#配置端口号
server.port=8082
#context-path
server.servlet.context-path=/myboot
#自定义key=value
school.name=动力节点
school.website=www.bjpowernode.com
school.address=北京的大兴区
controller
@Controller
public class HelloController {
@Resource
SchoolInfo schoolInfo;
@RequestMapping("/hello")
@ResponseBody
public String queryData() {
return schoolInfo.toString();
}
}
如果出现乱码检查一下配置文件的编码格式
使用 jsp
提示
SpringBoot 不推荐使用 jsp , 而是使用模板技术代替 jsp
使用 jsp 需要配置:
加入一个处理 jsp 的依赖. 负责编译 jsp 文件
<dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> </dependency>
如果需要使用 servlet, jsp, jstl 的功能
<dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency>
创建一个存放 jsp 的目录, 一般叫做 webapp
将目录修改为 web 资源目录
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> ${jsp} </body> </html>
需要在 pom.xml 指定 jsp 文件编译后的存放目录
<build> <!--指定jsp编译后的存放目录--> <resources> <resource> <!--jsp原来的目录--> <directory>src/main/webapp</directory> <!--指定编译后的目录--> <targetPath>META-INF/resources</targetPath> <!--指定处理的目录和文件--> <includes> <include>**/*.*</include> </includes> </resource> </resources> </build>
创建 Controller, 访问 jsp
@RequestMapping("/getJsp") public String getJsp(Model model) { // request.setAttribute("jsp", "getJsp"); model.addAttribute("jsp", "getJsp"); // 视图逻辑名称 return "index"; }
在 application.propertis 文件中配置视图解析器
#配置视图解析器 #/ = src/main/webapp spring.mvc.view.prefix=/ spring.mvc.view.suffix=.jsp
正确编译后 target 目录结构:
使用容器
通过代码, 从容器中获取对象.
通过 SpringApplication.run(Application.class, args);
返回值获取容器.
主配置类的 run()
方法源码:
public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
return run(new Class[]{primarySource}, args);
}
返回的数据类型 ConfigurableApplicationContext
的源码:
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
String CONFIG_LOCATION_DELIMITERS = ",; \t\n";
String CONVERSION_SERVICE_BEAN_NAME = "conversionService";
String LOAD_TIME_WEAVER_BEAN_NAME = "loadTimeWeaver";
String ENVIRONMENT_BEAN_NAME = "environment";
String SYSTEM_PROPERTIES_BEAN_NAME = "systemProperties";
String SYSTEM_ENVIRONMENT_BEAN_NAME = "systemEnvironment";
String APPLICATION_STARTUP_BEAN_NAME = "applicationStartup";
String SHUTDOWN_HOOK_THREAD_NAME = "SpringContextShutdownHook";
void setId(String id);
void setParent(@Nullable ApplicationContext parent);
void setEnvironment(ConfigurableEnvironment environment);
@Override
ConfigurableEnvironment getEnvironment();
void setApplicationStartup(ApplicationStartup applicationStartup);
ApplicationStartup getApplicationStartup();
void addBeanFactoryPostProcessor(BeanFactoryPostProcessor postProcessor);
void addApplicationListener(ApplicationListener<?> listener);
void setClassLoader(ClassLoader classLoader);
void addProtocolResolver(ProtocolResolver resolver);
void refresh() throws BeansException, IllegalStateException;
void registerShutdownHook();
@Override
void close();
boolean isActive();
ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
}
ConfigurableApplicationContext 接口是 ApplicationContext 的子接口
public interface ConfigurableApplicationContext extends ApplicationContext {}
使用
ConfigurableApplicationContext
或ApplicationContext
接收run()
方法返回的容器对象使用
getBeanDefinitionNames()
方法可以获取容器内所有的对象名称, 其中就可以找到我们自己创建的对象名称使用
getBean()
方法获取对象
@SpringBootApplication
public class SpringbootApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(SpringbootApplication.class, args);
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
SchoolInfo schoolInfo = context.getBean("schoolInfo", SchoolInfo.class);
System.out.println(schoolInfo);
}
}
CommandLineRunner 接口 , ApplicationRunner 接口
在开发中可能需要在容器启动后执行一些内容, 比如读取配置文件, 数据库连接之类的, SpringBoot 就提供了两个接口来实现该需求.
这两个接口都有一个 run() 方法, 执行时间在容器对象创建好后, 自动执行 run() 方法.
可以完成自定义的在容器对象创建好的一些操作.
CommandLineRunner
接口 run()
方法参数为 String
@FunctionalInterface
public interface CommandLineRunner {
void run(String... args) throws Exception;
}
ApplicationRunner
接口 run()
方法参数为 ApplicationArguments
@FunctionalInterface
public interface ApplicationRunner {
void run(ApplicationArguments args) throws Exception;
}
在主配置文件下测试
@SpringBootApplication
public class SpringbootApplication implements CommandLineRunner {
public static void main(String[] args) {
System.out.println("准备创建容器对象--run方法执行之前");
SpringApplication.run(SpringbootApplication.class, args);
System.out.println("容器对象创建之后--run方法执行之后");
}
@Override
public void run(String... args) throws Exception {
System.out.println("容器对象创建好之后, 自动执行run方法");
}
}
控制台打印结果
准备创建容器对象--run方法执行之前
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.7.3)
一些日志信息 ...
容器对象创建好之后, 自动执行run方法
容器对象创建之后--run方法执行之后
结果可以看到:
在
SpringApplication.run(SpringbootApplication.class, args);
执行时就调用了CommandLineRunner.run(String... args)
可以在 CommandLineRunner.run(String... args)
方法中调用容器中的对象
为 Student 类 加上 @Component 注解 将该类实例交给 spring 管理
@AllArgsConstructor @NoArgsConstructor @Data @Component public class Student { private String name; private int age; private String sex; }
在主配置类中使用自动注入获取 Student 对象并尝试在
CommandLineRunner.run(String... args)
方法中调用@SpringBootApplication public class SpringbootApplication implements CommandLineRunner { @Resource private Student student; public static void main(String[] args) { System.out.println("准备创建容器对象--run方法执行之前"); SpringApplication.run(SpringbootApplication.class, args); System.out.println("容器对象创建之后--run方法执行之后"); } @Override public void run(String... args) throws Exception { student.setName("小明"); System.out.println("CommandLineRunner.run()中调用容器对象:" + student); System.out.println("容器对象创建好之后, 自动执行run方法"); } }
可以成功调用
准备创建容器对象--run方法执行之前 . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v2.7.3) 一些日志信息 ... CommandLineRunner.run()中调用容器对象:Student(name=小明, age=0, sex=null) 容器对象创建好之后, 自动执行run方法 容器对象创建之后--run方法执行之后