一个可以自动管理对象之间依赖关系的容器
- Spring
一个IoC容器,它是Java世界应用的事实标准(什么是事实标准? 到处都是文档,所有人都在用)
- SpringMVC
一个基于Spring和Servlet的Web应用框架
- SpringBoot
也是一个框架,只不过它的集成度和自动化程度更高
- Bean
容器中的最小工作单元,通常为一个Java对象
- BeanFactory/ApplicationContext
容器本身对应的Java对象
- 依赖注入(DI)
由容器负责注入所有依赖
- 控制反转(IoC)
用户将控制权交给了容器
这个例子将向你演示什么是依赖注入
1
2
3
4
5
6
|
public class UserDao {
// 模拟数据库查询对应id的用户
public String selectUserById(int id) {
return "User: {id=" + id + "}";
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
|
public class UserService {
private UserDao dao;
public UserService(UserDao dao) {
this.dao = dao;
}
// 打印对应id的用户信息
public void printUserInfo(int id) {
System.out.println(dao.selectUserById(id));
}
}
|
1
2
3
4
5
6
7
|
public class Main {
public static void main(String[] args) {
UserDao userDao = new UserDao(); // 我们需要实例化dao
UserService userService = new UserService(userDao); // 再把dao传递给service
userService.printUserInfo(1);
}
}
|
- 首先引入一个最小的Spring
Spring Context
1
2
3
4
5
6
|
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.13</version>
</dependency>
|
- 创建配置文件
spring-config.xml
,这个文件名是可以改的
1
2
3
4
5
6
7
8
9
10
11
|
<?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 https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/> <!-- 必须要有这个配置,否则不能用@Autowired注解 -->
<!-- bean definitions here -->
<bean id="userDao" class="com.github.wjinlei.UserDao"/>
<bean id="userService" class="com.github.wjinlei.UserService"/>
</beans>
|
- 利用
@Autowired
注解告诉Spring依赖UserDao
1
2
3
4
5
6
7
8
9
|
public class UserService {
@Autowired
private UserDao dao;
// 打印对应id的用户信息
public void printUserInfo(int id) {
System.out.println(dao.selectUserById(id));
}
}
|
1
2
3
4
5
6
7
|
public class Main {
public static void main(String[] args) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:spring-config.xml"); // 指定一个配置文件
UserService userService = (UserService) beanFactory.getBean("userService"); // 获取名为userService的Bean
userService.printUserInfo(1); // 调用它的printUserInfo方法
}
}
|
从上面的演示可以看出,使用Spring,我们根本不需要new UserDao,并传递给UserService这个步骤,而是直接获取UserService的Bean就可以了,获得的Bean对象直接就包含了我们需要的UserDao,这就是Spring为我们做的事情,它动态的帮我们把UserDao注入到了UserService这个Bean对象中,这个概念就是依赖注入
之前手动new对象并传递给Service的步骤是由我们自己控制的(这个步骤叫做对象装配),而在使用了Spring之后,这一切都由Spring帮我们做了,你不需要控制它们所有的依赖和装配的进行,而只需要声明它们的依赖,有人(这个人就是Spring)会自动帮你进行装配的过程,这个概念就叫做控制反转
1
2
|
userDao=com.github.wjinlei.UserDao
userService=com.github.wjinlei.UserService
|
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
70
71
72
73
74
|
import org.springframework.beans.factory.annotation.Autowired;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class MyIoCContainer {
private static final HashMap<String, Object> beans = new HashMap<>();
// 实现一个简单的IoC容器,使得:
// 1. 从beans.properties里加载bean定义
// 2. 自动扫描bean中的@Autowired注解并完成依赖注入
public static void main(String[] args) {
MyIoCContainer container = new MyIoCContainer();
container.start();
OrderService orderService = (OrderService) container.getBean("orderService");
orderService.createOrder();
}
// 启动该容器
public void start() {
Properties properties = readConfig();
properties.forEach((beanName, beanClass) -> newInstanceBean((String) beanName, (String) beanClass));
beans.forEach((beanName, beanInstance) -> dependencyInject(beanInstance, getAutowiredFields(beanInstance)));
}
private Properties readConfig() {
Properties properties = new Properties();
try {
properties.load(MyIoCContainer.class.getResourceAsStream("/beans.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}
return properties;
}
private void newInstanceBean(String beanName, String beanClass) {
try {
Class<?> clazz = Class.forName(beanClass);
Object newInstance = clazz.getConstructor().newInstance();
beans.put(beanName, newInstance);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void dependencyInject(Object beanInstance, List<Field> autowiredFieldList) {
autowiredFieldList.forEach(filed -> {
Object dependencyBeanInstance = beans.get(filed.getName());
try {
filed.setAccessible(true);
filed.set(beanInstance, dependencyBeanInstance);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
});
}
private List<Field> getAutowiredFields(Object beanInstance) {
Field[] fields = beanInstance.getClass().getDeclaredFields();
return Stream.of(fields)
.filter(field -> field.getAnnotation(Autowired.class) != null)
.collect(Collectors.toList());
}
// 从容器中获取一个bean
public Object getBean(String beanName) {
return beans.get(beanName);
}
}
|
1
2
3
4
5
6
7
8
|
public class Main {
public static void main(String[] args) {
MyIoCContainer container = new MyIoCContainer();
container.start();
UserService userService = (UserService) container.getBean("userService");
userService.printUserInfo(1);
}
}
|
Getting Started
Create a Simple Web Application
- 新建项目,直接通过官网的Spring Initializr. 创建一个项目
SpringBootApplication.java
1
2
3
4
5
6
7
8
9
10
11
|
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestSpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(TestSpringBootApplication.class, args);
}
}
|
- 添加一个
HelloController.java
1
2
3
4
5
6
7
8
9
10
11
12
|
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
}
|
- 启动
SpringBootApplication
,访问 http://localhost:8080
我们修改一下Controller
,添加一个对于/search
路径的处理函数,它接收一个Get类型
参数q
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@GetMapping("/")
public String index() {
return "Greetings from Spring Boot!";
}
@GetMapping("/search")
public String index(@RequestParam("q")String keyword) {
return "You are searching: " + keyword;
}
}
|
从这里开始,我将只贴关键代码,不再把一些无关代码贴上来了
- 两个
RequestParam
参数,其中required
属性表示这个参数不是必须的,可传可不传(它默认是true)
1
2
3
4
5
6
|
@GetMapping("/search")
public String index(
@RequestParam("q")String keyword,
@RequestParam(value = "charset", required = false)String charset) {
return "You are searching: " + keyword;
}
|
下面我们模拟GitHub删除仓库APIDELETE /repos/{owner}/{repo}
Name |
Type |
In |
Description |
owner |
string |
path |
repo owner |
repo |
string |
path |
repo name |
- 新建一个
RepoController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("repos") // 给类添加一个@RequestMapping将表示这个类下的路径都是以它为基础路径的
public class RepoController {
@DeleteMapping("/{owner}/{repo}") // 例如这个实际路径就是 /repos/{owner}/{repo}
public String deleteRepo(
@PathVariable("owner") String owner,
@PathVariable("repo") String repo) {
return "You delete: " + owner + "/" + repo;
}
}
|
- 发送测试请求
curl -X DELETE http://localhost/golang/go
你可能注意到了上面我们使用了@DeleteMapping
来映射一个DELETE
方法的HTTP请求
这是一种API的设计模式
,叫做RESTful API
,它的特点是使用HTTP
动词来描述对应请求的功能,而URL当作被操作的资源
- 对于添加,一般用
POST
方法
- 对于删除,一般用
DELETE
方法
- 对于修改,一般用
PUT
方法
- 对于查询,一般用
GET
方法
Springboot中对于这几种请求都有对应的注解,可以很方便的映射到对应方法
- @PostMapping
- @DeleteMapping
- @PutMapping
- @GetMapping
- @RequestMapping
@RequestMapping
可以映射所有方法,只需要加一个method
属性即可
@RequestMapping(value = "/{owner}/{repo}", method = RequestMethod.DELETE)
上面的和@DeleteMapping("/{owner}/{repo}")
是一样的
这种情况适用于参数比较多的情况
下面我们模拟GitHub创建 Issue APIPOST /repos/{owner}/{repo}/issues
Name |
Type |
In |
Description |
owner |
string |
path |
repo owner |
repo |
string |
path |
repo name |
title |
string |
body |
issue title |
body |
string |
body |
issue body |
在RepoController.java
中新增一个处理方法
1
2
3
4
5
6
7
8
9
|
@PostMapping("/{owner}/{repo}/issues")
public Issue create(
@PathVariable("owner") String owner,
@PathVariable("repo") String repo,
@RequestBody() Issue issue) /* 你可以直接把json类型的body映射成一个Bean */ {
System.out.println(issue.getTitle());
System.out.println(issue.getBody());
return issue;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
public class Issue {
private String title;
private String body;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}
|
发送测试请求
1
2
3
4
5
|
curl \
-X POST \
-H "Content-Type: application/json" \
http://localhost:8080/repos/golang/go/issues \
-d '{"title":"This is issue title", "body":"This is issue body"}'
|
这种方式适用于参数比较少的情况
下面我们模拟登录 APIPOST /login
Name |
Type |
In |
Description |
username |
string |
query |
username |
password |
string |
query |
password |
- 这种方式获取的是
application/x-www-form-urlencoded
类型的参数,或者说表单类型数据都可以用这种方式获取
1
2
3
4
5
6
7
8
|
@PostMapping("/login")
public boolean login(
@RequestParam("username") String username,
@RequestParam("password") String password) {
boolean result = "root".equals(username) && "toor".equals(password);
System.out.println(result);
return result;
}
|
发送测试
1
2
3
4
|
curl \
-X POST \
-H "Content-Type: application/x-www-form-urlencoded" \
http://localhost:8080/login\?username\="root"\&password\="toor"
|
1
2
3
4
|
@GetMapping("/")
public String index() {
return "<h1>Greetings from Spring Boot!</h1>";
}
|
我们在生产中是不会用这个的,因为它太原始了,也比较麻烦,Spring为我们提供了更好的处理方式
1
2
3
4
5
6
7
8
|
@GetMapping("/demo")
public void demo(HttpServletRequest request, HttpServletResponse response) throws IOException {
// HttpServletRequest 对象表示本次请求的对象
// HttpServletResponse 表示本次请求的响应对象
// 两个对象都有很多方法,可以自己去看看,了解下
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().write("demo");
}
|
- 直接返回对象,它会被Spring自动格式化成JSON
@ResponseBody
1
2
3
4
5
6
7
8
9
|
@GetMapping("/demo")
@ResponseBody
public Object demo() {
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("arrays", Arrays.asList("aaa", "bbb", "ccc"));
hashMap.put("count", 1);
hashMap.put("message", "Hello Response");
return hashMap;
}
|
测试响应 curl http://localhost:8080/demo