JAVA26-Spring&Sprintboot

Spring

一个可以自动管理对象之间依赖关系的容器

  • Spring 一个IoC容器,它是Java世界应用的事实标准(什么是事实标准? 到处都是文档,所有人都在用)
  • SpringMVC 一个基于Spring和Servlet的Web应用框架
  • SpringBoot也是一个框架,只不过它的集成度和自动化程度更高

Spring容器的核心概念

  • Bean 容器中的最小工作单元,通常为一个Java对象
  • BeanFactory/ApplicationContext 容器本身对应的Java对象
  • 依赖注入(DI) 由容器负责注入所有依赖
  • 控制反转(IoC)用户将控制权交给了容器

依赖注入(DI) 与 控制反转(IoC)?

这个例子将向你演示什么是依赖注入

正常情况下手动管理依赖

  • Dao
1
2
3
4
5
6
public class UserDao {
    // 模拟数据库查询对应id的用户
    public String selectUserById(int id) {
        return "User: {id=" + id + "}";
    }
}
  • Service
 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 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));
    }
}
  • Main
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)会自动帮你进行装配的过程,这个概念就叫做控制反转

手写一个简单的IoC容器

  • 有如下配置文件
1
2
userDao=com.github.wjinlei.UserDao
userService=com.github.wjinlei.UserService
  • IoC容器实现
 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);
    }
}
  • Main
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);
    }
}

Spring Boot

Getting Started

创建一个简单的Web应用

Create a Simple Web Application

  1. 新建项目,直接通过官网的Spring Initializr. 创建一个项目
  2. 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);
	}

}
  1. 添加一个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!";
	}

}
  1. 启动SpringBootApplication,访问 http://localhost:8080

获取Get请求参数

我们修改一下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;
    }
}

获取多个Get请求参数

从这里开始,我将只贴关键代码,不再把一些无关代码贴上来了

  • 两个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
  1. 新建一个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;
    }
}
  1. 发送测试请求curl -X DELETE http://localhost/golang/go

RESTful API

你可能注意到了上面我们使用了@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}")是一样的

获取请求Body

这种情况适用于参数比较多的情况

下面我们模拟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;
}
  • Issue Bean
 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"}'

获取Post请求参数

这种方式适用于参数比较少的情况

下面我们模拟登录 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"

生成响应

  • 最基本的响应,直接返回HTML文本
1
2
3
4
@GetMapping("/")
public String index() {
    return "<h1>Greetings from Spring Boot!</h1>";
}
  • 操作最原始的Servlet

我们在生产中是不会用这个的,因为它太原始了,也比较麻烦,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

updatedupdated2025-03-012025-03-01