1 | SpringBoot相关知识 |
Spring常用注解
springboot的启动类的@SpringBootApplication, 控制器的@Controller, @RestController, 还有与配置相关的一系列注解.
查找XML文档与配置<bean>元素这两个工作同样可以使用注解来完成. 使用@Component, @Service, 或者@Configuration注解来修饰一个java类, 这些java类会被Spring自动检测并注册到容器中. 在类里面使用@Bean注解修饰的方法, 会被作为一个bean存放到Spring容器中.
@Autowired, @Primary, @Resource
@Autowired默认按类型注入, 找不到就按名称注入.
还可以使用构造器注入
1 | InjectBean myBean; |
@Resource默认按名称注入, 找不到就按类型注入
不能修饰构造器
@Primary注解 注入
如果容器中配置两个相同类型的bean, 我们在想要用的Bean上添加@Primary注解, 我们再使用@Autowired自动注入, 就不会报错了, 就会自动注入带@Primary的bean 了. 如果有多个bean都带了@Primary, 就会报异常.
例子
User.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package cn.lacknb.beans;
public class User {
private String username;
public User(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
}BeanConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package cn.lacknb.beans;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
public class BeanConfig {
public User getUser(){
return new User("ccc");
}
public User getUser2(){
return new User("bbb");
}
}测试类
1
2
3
4
5
6
7
8
private User user;
public void test03(){
System.out.println(user.getUsername());
// 这里会输出 bbb
}
Scope注解
在配置Bean的时候, 可以指定bean的作用域, 一般的bean可以配置爱为单例(singleton)或非单例(prototype). 配置为singleton的话, Spring的bean工厂都会只返回同一个bean实例, 而配置prototype, 则每次都会创建一个新的实例
1 | package cn.lacknb.test; |
AOP注解
加入依赖
1 | <dependency> |
AopService.java
1 | package cn.lacknb.aop; |
ProxyService.java
1 | package cn.lacknb.aop; |
1 |
|
根据结果可知, 我们的业务方法已经被代理, 因此在方法调用前和调用后, 都会执行通知到的方法, 输出的代理类为经过cglib处理的类. 在springboot2.0中, 默认情况下, 不管是代理接口还是类, 都使用cglib代理. 我们将spring.aop.proxy-target-class配置为false, 这样在代理接口时, 会使用jdk 动态代理. *据本人测试, 无论false或者true, 输出的都是cglib处理的类. *
ComponentScan注解
ComponentScan注解主要用于检测使用@Component装饰的组件, 并把它们注册到Spring容器中. 除了使用@Component修饰的组件外, 还有间接使用@Component的组件(如@Service, @Repository, @Controller, 或自定义注解), 都会被检测到,. 在使用Springboot时, 我们一般很少接触@ComponentScan注解, 但实际上每个应用都使用它, 它只是被放到@SpringBootApplication里面了. 在使用ComponentScan注解时, 要注意过滤器.
高级Spring注解
限定注解
@Primary注解, 当存在多个同类型的bean时, 可以指定优先注入的bean, 如果想对bean的注入选择做进一步的控制, 则可以使用限定注解. 限定注解可以与特定的参数关联起来, 缩小类型的匹配范围, 最后一个选择符合条件的bean来注入.
例子:
需要到上面的user类
QuaConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package cn.lacknb.beans;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
public class QuaConfig {
public User quaBeanA(){
return new User("quaBeanA");
}
public User quaBeanB(){
return new User("quaBeanB");
}
}创建测试类
1
2
3
4
5
6
7
8
9
10
11
12
"quaBeanB") (
private User user;
/*
* 测试 限定注解
* */
public void test04(){
System.out.println(user.getUsername()); // 输出quaBeanB
}
自定义限定注解
当存在两个相同类型的注解时, 可以根据bean的名称来指定注入的bean, 如果需要根据特定的属性来指定注入的bean, 则可以自定义限定注解.
例子:
使用上面的User实体类
创建自定义注解TestQualifer.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package cn.lacknb.beans;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
({ElementType.FIELD, ElementType.METHOD})
(RetentionPolicy.RUNTIME)
public TestQualifer {
String type();
}创建CustomConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28package cn.lacknb.beans;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/*
* 首先先定义一个TestQualifer注解, 该注解使用了@Qualifer修饰, 并且需要设置type属性
* 在配置bean的时候, 需要为相应的bean设置不同的类型
* spring容器提供了不同类型的user bean, 并使用@TestQualifer为它们进行了标识.
* 在使用这些bean时, 同样使用@TestQualifer并指定type属性, 就可以拿回相应的bean
* */
public class CustomConfig {
"jerry") (type =
public User userBean(){
return new User("and dog");
}
"tom") (type =
public User catBean(){
return new User("cat");
}
}创建测试类
1
2
3
4
5
6
7
8
9
10
11
12/*
* 测试自定义限定注解
* */
"jerry") (type =
private User user;
public void test05(){
System.out.println(user.getUsername()); // 输出 and dog.
}
自定义bean的生命周期
前面介绍了, Scope注解主要用于配置爱bean在容器中的生命周期, 常被配置为singleton, 和 prototype (多态)的, 在web环境下, 还可以配置为request, session等值. 表示Spring容器会为一次请求或一个会话分配一个bean的实例. 如果对bean的生命周期有特殊的需求, 则可以考虑使用自定义的scope, 自己写代码来定义bean的生命周期.
需求: 一个bean需要被使用4次之后, 就需要获取新的bean实例, 针对这样的需求, 可编写一个自定义的scope
创建MyScope.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70package cn.lacknb.util;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import java.util.HashMap;
import java.util.Map;
public class MyScope implements Scope {
/*
记录使用bean的使用次数
*/
private Map<String, Integer> beanCounts = new HashMap<String, Integer>();
/*
* 保存实例
* */
private Map<String, Object> beans = new HashMap<String, Object>();
public Object get(String s, ObjectFactory<?> objectFactory) {
if (beanCounts.get(s) == null){
beanCounts.put(s, 0);
}
// 第一次使用, 放到实例的Map中
Integer beancount = beanCounts.get(s);
if (beancount == 0){
Object newObject = objectFactory.getObject();
beans.put(s, newObject);
}
// 获取对象
Object bean = beans.get(s);
// 计数器加1
Integer newBeanCount = beancount + 1;
// 使用次数超过4次了, 设置为0
if (newBeanCount >= 4){
newBeanCount = 0;
}
//设置新的次数
beanCounts.put(s, newBeanCount);
// 返回实例
return bean;
}
public Object remove(String s) {
return null;
}
public void registerDestructionCallback(String s, Runnable runnable) {
}
public Object resolveContextualObject(String s) {
return null;
}
public String getConversationId() {
return null;
}
}创建ScopeConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39package cn.lacknb.util;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.CustomScopeConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import javax.annotation.PostConstruct;
public class ScopeConfig {
/*
* @PostConstruct注解 用于在依赖关系注入完成之后需要执行的方法上
*
*
* */
BeanFactory factory;
public void customScopeConfigurer(){
// ScopeConfig初始化后执行本方法, 创建MyScope的实例
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("four", new MyScope());
configurer.postProcessBeanFactory((ConfigurableListableBeanFactory) factory);
}
"four") (scopeName =
public ScopeBean beanA(){
return new ScopeBean();
}
}这里的@PostConstruct注解 用于在依赖关系注入完成之后需要执行的方法上, 以执行任何初始化. 此方法必须在将类放入服务之前调用, 支持依赖关系注入的所有类都必须支持此注释. 即使类中没有请求注入任何资源, 用PostConstruct注释的方法也必须被调用. 只有一个方法可以用此进行注释. 应用PostConstruct注释的方法必须遵守以下所有标准:
- 该方法不得有任何参数, 除非是在EJB拦截器的情况下, 根据EJB规范的定义, 在这种情况下它将带有一个invocationContext对象
- 该方法的返回类型必须为void
- 该方法不得抛出已检查异常
- 应用PostConstruct的方法可以是Public, protected, package private或private
- 除了应用程序之外, 该方法不能是static, 该方法可以是final
- 如果该方法抛出未检查异常, 那么不得将次类放入服务中, 除非是能够处理异常并可从中恢复的EJB .
ScopeBean.java
1
2
3
4
5
6
7
8package cn.lacknb.util;
public class ScopeBean {
public ScopeBean() {
System.out.println("ScopeBean初始化....");
}
}测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*
* 测试一个bean实例使用4次
* */
ApplicationContext context;
public void test06(){
for (int i = 0; i < 7; i++) {
System.out.println(context.getBean("beanA"));
}
}输出结果
1
2
3
4
5
6
7
8
9ScopeBean初始化....
cn.lacknb.util.ScopeBean@3af4e0bf
cn.lacknb.util.ScopeBean@3af4e0bf
cn.lacknb.util.ScopeBean@3af4e0bf
cn.lacknb.util.ScopeBean@3af4e0bf
ScopeBean初始化....
cn.lacknb.util.ScopeBean@245a26e1
cn.lacknb.util.ScopeBean@245a26e1
cn.lacknb.util.ScopeBean@245a26e1
Spring MVC常用注解
@ Controller, @RequestMapping, @GetMapping等.
@RestController
@RestController注解的目的是为了让我们更加简便地使用@Controller和@ResponseBody这两个注解, 查看@RestController源码就可以知道, 这个注解本身就使用了@Controller与@ResponseBody来修饰. ResponseBody注解可以修饰控制方法, 方法的返回值将会被写到HTTP响应体中, 所返回的内容, 将不会放到模型中, 也不会被解释为视图的名称.
@RequestAttribute, @SessionAttribute, @SessionAttributes等注解也较为常用.
@MatrixVariable注解
前面的@PathVariable注解主要是用来获取单一的URI参数, 如果通过URI来传输一些复杂的参数, 则可以考虑使用@MatrixVariable注解. 使用时需要遵守一定的规范, 参数的名称与值使用key-value形式, 多个参数之间使用(;)号隔开, 如果一个参数拥有多个值, 则值与值之间用逗号隔开. 这种URI的参数表现形式, 成为矩阵变量.
1 | /car/{id};color=red;year=2019 |
该URI传入了id参数, 并且传入了color与year参数, 值分别为red与2019. 对于上面的URI, 如何获取呢? 例子:
首先开启矩阵变量的支持
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20package cn.lacknb.controller;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
public class WebConfig extends WebMvcConfigurationSupport {
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
final RequestMappingHandlerMapping requestMappingHandlerMapping = super.requestMappingHandlerMapping();
/*
* 将 RequestMappingHandlerMapping的RemoveSemicolonContent的属性设置为false, 即可开启对矩阵变量的支持
* */
requestMappingHandlerMapping.setRemoveSemicolonContent(false);
return requestMappingHandlerMapping;
}
}控制器
1
2
3
4
5
6
7
8
9"/car/{id}") (
public String test03(@PathVariable Integer id, @MatrixVariable String color, @MatrixVariable String year){
System.out.println(id);
System.out.println(color);
System.out.println(year);
return "test";
}测试
url为:
http://localhost:8080/car/11;color=red;year=2019
控制台输出id color和year的值.
文件上传
fileUpload.html
1 |
|
application.yml
1 | spring: |
控制器
1 | "/file/upload") ( |
多文件上传
创建实体类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package cn.lacknb.beans;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
public class MyFile {
private List<MultipartFile> files;
public List<MultipartFile> getFiles() {
return files;
}
public void setFiles(List<MultipartFile> files) {
this.files = files;
}
}upload.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form action="http://localhost:8080/files/many" method="post" enctype="multipart/form-data">
上传文件: <input type="file" name="files" /> <br>
上传文件: <input type="file" name="files" /> <br>
上传文件: <input type="file" name="files" /> <br>
上传文件: <input type="file" name="files" /> <br>
上传文件: <input type="file" name="files" /> <br>
上传文件: <input type="file" name="files" /> <br>
<input type="submit" value="上传">
</form>
</body>
</html>控制器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27/*
* 多文件上传
* */
"/files/many") (
public String test05(@ModelAttribute MyFile files, Model model) throws IOException {
String path = "/media/nianshao/nie475/java项目/files";
File filePath = new File(path);
if (!filePath.exists()){
filePath.mkdirs();
}
List<MultipartFile> files11 = files.getFiles();
int count = 0;
for (int i = 0; i < files11.size(); i++) {
if (!files11.get(i).isEmpty()) {
String fileName = UUID.randomUUID() + files11.get(i).getOriginalFilename();
File targetFile = new File(filePath, fileName);
files11.get(i).transferTo(targetFile);
count++;
}
}
model.addAttribute("aaa", "成功上传" + count + "个文件");
if (count == 0){
model.addAttribute("aaa", "上传的文件为空");
}
return "test01";
}
SpringBoot的条件注解
SpringBoot提供了自动配置, 一些内置的或者常用的组件Spring已经帮我们配置好了, 我们在使用这些组件的时候, 加入相关依赖就可以了, 并不需要额外进行配置, 从而减少了搭建项目的工作量. 自动配置是否执行, 取决于条件注解.
类条件注解
当类路径中存在或不存在某个类时, 再决定是否执行自动配置. 存在该类则使用@ConditionalOnClass, 不存在则使用@ConditionalOnMissingClass, 它们都可以修饰类或者方法. 这两个注解通过配置value属性来指定一个类, 这个类有可能并不存在于实际的运行环境中, 也可以使用name属性来指定权限定类名. 假设项目中存在MySQL的驱动类, 则会启用某个配置类, 此时则可以使用@ConditionalOnClass.
MyBean.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14package cn.lacknb.bean;
public class MyBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}OnClassConfig.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package cn.lacknb.config;
import cn.lacknb.bean.MyBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
"com.mysql.jdbc.Driver") (name =
public class OnClassConfig {
public MyBean myBean(){
return new MyBean();
}
}当项目有mysql的依赖时, 就会创建 MyBean的实例
测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24package cn.lacknb.contoller;
import cn.lacknb.bean.MyBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
public class OnClassController {
ApplicationContext context;
"/bean") (
public String getBean(){
Map<String, MyBean> beans = context.getBeansOfType(MyBean.class);
System.out.println(beans.size());
return "success";
}
}代码中的getBean方法会到容器中获取MyBean类型的bean, 并以Map形式返回, 最后输出Map的size. 在pom.xml文件中, 加入MySql依赖, 测试运行, 控制台输出为1, 将mysql的依赖注释掉, 则输出为0. 使用@ConditionalOnMissingClass则有相反的效果.
发布和调用REST服务
REST是英文 Representational State Transfer 的缩写, 一般翻译为 “表述性状态转移”. REST本身只是分布式系统中的一种架构风格, 并不是某种标准或者规范. 而是一种 轻量级的基于HTTP协议的Web Service风格.
发布REST服务
加入spring-boot-starter-web依赖,
1 | package cn.lacknb.REST; |
使用RestTemplate调用服务
1 | package cn.lacknb.REST; |
测试类
1 | package cn.lacknb.REST; |
使用feign调用服务
Feign是github上的一个开源项目, 其目的就是为了简化web service客户端的开发. 加入依赖
1 | <dependency> |
1 | package cn.lacknb.feign; |
1 | package cn.lacknb.feign; |
SpringBoot与Servlet
无论是创建servlet, listener 还是filter, Springboot启动类都需要加 注解@ServletComponentScan(“需要扫描的包”)
1 | package cn.lacknb.Test; |
创建Servlet
这里使用注解@WebServlet(“/tets”)
1 | package cn.lacknb.servlet; |
浏览器访问 http://localhost:8080/test
创建监听器
1 | package cn.lacknb.servlet; |
创建过滤器
1 | package cn.lacknb.servlet; |
使用Springboot构建可部署的war包
1 | <parent> |
1 | package cn.lacknb.main; |
启动类继承SpringBootServletInitializer类 这里需要实现重写父类的configure方法
SpringBootServletInitializer类将会调用configure方法来得到一个SpringApplicationBuilder
实例, 根据名称所知, 这个SpringApplicationBuilder会帮我们创建上下文, 实际上它会帮我们创建
WebApplicationContext实例, 其在创建Spring上下文的过程中, 会查找使用了@Configuration注解的
配置类, 然后对它们进行初始化
– 对于启动类, 我们可以这样理解: 在eclipse运行main方法时, 会使用原来的方式(执行jar包的方式)来启动web应用(含服务器), 如果将项目打包成一个war包放到web服务器上运行, 就会创建WebApplicationContext, 在创建的过程中, 会调用configure方法. 另外, 父类调用一个方法来实现部分工作, 但该方法需要由继承的子类实现, 这种模式是GoF设计模式的一种: 模板方法.
将war包放在tomcat的webapp目录下, 然后直接启动tomcat, 在浏览器地址栏输入: localhost:8080/test-war/hello
使用其他服务器
使用jetty
首先先排除依赖tomcat, 这个是springboot默认使用的.
1 | <dependency> |
添加jetty服务器
1 | <!--使用jetty服务器--> |
使用Undertow
方法和上面一样
1 | <dependency> |
服务器访问日志
服务器访问日志与服务器日志不是同一个概念, 我们一般在tomcat控制台中看到的日志, 是服务器日志, 而服务器访问日志, 则会记录服务处理的消息, 默认情况下, tomcat并不会记录fangwen 日志. 创建一个maven项目, 将application.yml文件 添加如下信息.
1 | server: |
日志显示格式为:
1 | ip: 127.0.1.1, response code: 200, time: [21/Nov/2019:18:01:50 +0800] |
banner配置 (自定义显示图案)
在根目录创建banner.txt
将ASCII图案放在这里
除了文本外, 还可以提供图片文件用于显示, 图片格式可以为jpg, png或gif, Springboot会将图片转换为ASCII, 以文本的方式将图片显示到控制台中.
application.yml如下
1 | spring: |
在springboot中配置banner, 可以在Application.yml中配置一下属性:
- spring.banner.charset: 如果banner的文本文件中有utf-8以外的编码, 则需要配置该项
- spring.banner.image.location: 指定banner图片的位置
- spring.banner.image.width: banner图片经转换后的字符长度, 默认为76
- spring.banner.image.height: 图片经转换后的字符高度
- spring.banner.image.margin: 设置图片的显示边距 默认为2
- spring.banner.image.invert: 配置为true 则将图片进行转换显示, 以适应深色的终端风格