黑马程序员java web学习笔记--后端基础(二)基础知识

目录

1 SpringBoot 入门

2 HTTP 协议

2.1 HTTP 请求协议

2.2 获取请求数据

2.3 HTTP 相应协议

2.4 响应数据设置

3 SpringBoot 案例

3.1 需求说明

3.2 代码实现

3.3 @ResponseBody

4 分层解耦

4.1 三层架构

4.2 分层解耦

4.2.1 解耦思路

4.2.2 解耦问题

4.3 IOC & DI 入门

4.4 IOC 使用细节

4.5 DI 使用细节


1 SpringBoot 入门

Spring发展到今天已经形成了一种开发生态圈,Spring提供了若干个子项目,每个项目用于完成特定的功能。我们在项目开发时,一般会偏向于选择这一套spring家族的技术,来解决对应领域的问题,那我们称这一套技术为spring全家桶

官网:Spring

第1步:创建SpringBoot工程,并勾选Web开发相关依赖

第2步:定义HelloController类,添加方法hello,并添加注解

@RestController//表示当前类是一个请求处理类
public class HelloController {

    @RequestMapping("/hello")//标识请求路径
    public String hello(String name) {
        System.out.println("name : " + name);
        return "Hello " + name + "~";
    }
}

第3步:以debug方式运行SpringBoot自动生成的引导类 (标识有 @SpringBootApplication 注解的类)

/**
 * 启动类
 */
@SpringBootApplication
public class SpringbootWebQuickstartApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebQuickstartApplication.class, args);
    }

}

第4步:打开浏览器,输入 http://localhost:8080/hello?name=heima

为什么一个main方法就可以将Web应用启动了?

因为我们在创建springboot项目的时候,选择了web开发的起步依赖 spring-boot-starter-web。而spring-boot-starter-web依赖,又依赖了spring-boot-starter-tomcat,由于maven的依赖传递特性,那么在我们创建的springboot项目中也就已经有了tomcat的依赖,这个其实就是springboot中内嵌的tomcat。

而我们运行引导类中的main方法,其实启动的就是springboot中内嵌的Tomcat服务器。 而我们所开发的项目,也会自动的部署在该tomcat服务器中,并占用8080端口号 。

2 HTTP 协议

HTTP:Hyper Text Transfer Protocol(超文本传输协议),规定了浏览器与服务器之间数据传输的规则。

  • 基于TCP协议: 面向连接,安全。TCP是一种面向连接的(建立连接之前是需要经过三次握手)、可靠的、基于字节流的传输层通信协议,在数据传输方面更安全。

  • 基于请求-响应模型: 一次请求对应一次响应(先请求后响应)。

  • HTTP协议是无状态协议: 对于数据没有记忆能力,每次请求-响应都是独立的。

请求之间无法共享数据会引发的问题:

如:京东购物。加入购物车和去购物车结算是两次请求。由于HTTP协议的无状态特性,加入购物车请求响应结束后,并未记录加入购物车是何商品。发起去购物车结算的请求后,因为无法获取哪些商品加入了购物车,会导致此次请求无法正确展示数据。

具体使用的时候,我们发现京东是可以正常展示数据的,原因是Java早已考虑到这个问题,并提出了使用会话技术(Cookie、Session)来解决这个问题。具体如何来做,我们后面课程中会讲到。

2.1 HTTP 请求协议

请求协议浏览器将数据以请求格式发送到服务器。包括:请求行、请求头 、请求体

GET方式的请求协议:

POST方式的请求协议:

  • 请求行HTTP请求中的第一行数据。由:请求方式、资源路径、协议/版本组成。
  • 请求头第二行开始,上图黄色部分内容就是请求头,格式为key: value形式。http是个无状态的协议,所以在请求头设置浏览器的一些自身信息和想要响应的形式。这样服务器在收到信息后,就可以知道是谁,想干什么了。
  • 请求体 存储请求参数,GET请求的请求参数在请求行中,故不需要设置请求体。

2.2 获取请求数据

Web服务器(Tomcat)对HTTP协议的请求数据进行解析,并进行了封装(HttpServletRequest),并在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷。

@RestController
public class RequestController {
    @RequestMapping("/request")
    public String request(HttpServletRequest request) {
        //获取请求方式
        String method = request.getMethod();
        System.out.println("请求方式: " + method);

        //获取请求的url地址
        String url = request.getRequestURL().toString();
        System.out.println("请求url地址: " + url);
        String uri = request.getRequestURI();
        System.out.println("请求uri地址: " + uri);

        //获取请求协议
        String protocol = request.getProtocol();
        System.out.println("请求协议: " + protocol);

        //获取请求参数 - name, age
        String name = request.getParameter("name");
        String age = request.getParameter("age");
        System.out.println("请求参数name: " + name + ", age: " + age);

        //获取请求头 - Accept
        String accept = request.getHeader("Accept");
        System.out.println("请求头 - Accept: " + accept);

        return "OK";
    }
}

打开浏览器,输入: http://localhost:8080/request?name=heima&age=18

2.3 HTTP 相应协议

响应协议:服务器将数据以响应格式返回给浏览器。包括:响应行 、响应头 、响应体。

  • 响应行:响应数据的第一行。响应行由协议及版本(HTTP/1.1)、响应状态码(200)、状态码描述(OK)组成。
    响应状态码:

    状态码分类说明
    1xx响应中 --- 临时状态码。表示请求已经接受,告诉客户端应该继续请求或者如果已经完成则忽略
    2xx成功 --- 表示请求已经被成功接收,处理已完成
    3xx重定向 --- 重定向到其它地方,让客户端再发起一个请求以完成整个处理
    4xx客户端错误 --- 处理发生错误,责任在客户端,如:客户端的请求一个不存在的资源,客户端未被授权,禁止访问等
    5xx服务器端错误 --- 处理发生错误,责任在服务端,如:服务端抛出异常,路由出错,HTTP版本不支持等
    200 ok 客户端请求成功
    404 Not Found 请求资源不存在

    500 Internal Server Error服务端发生不可预期的错误

    状态码英文描述解释
    200OK客户端请求成功,即处理成功,这是我们最想看到的状态码
    302Found指示所请求的资源已移动到由Location响应头给定的 URL,浏览器会自动重新访问到这个页面
    304Not Modified告诉客户端,你请求的资源至上次取得后,服务端并未更改,你直接用你本地缓存吧。隐式重定向
    400Bad Request客户端请求有语法错误,不能被服务器所理解
    403Forbidden服务器收到请求,但是拒绝提供服务,比如:没有权限访问相关资源
    404Not Found请求资源不存在,一般是URL输入有误,或者网站资源被删除了
    405Method Not Allowed请求方式有误,比如应该用GET请求方式的资源,用了POST
    428Precondition Required服务器要求有条件的请求,告诉客户端要想访问该资源,必须携带特定的请求头
    429Too Many Requests指示用户在给定时间内发送了太多请求(“限速”),配合 Retry-After(多长时间后可以请求)响应头一起使用
    431Request Header Fields Too Large请求头太大,服务器不愿意处理请求,因为它的头部字段太大。请求可以在减少请求头域的大小后重新提交。
    500Internal Server Error服务器发生不可预期的错误。服务器出异常了,赶紧看日志去吧
    503Service Unavailable服务器尚未准备好处理请求,服务器刚刚启动,还未初始化好
    响应状态码官网:https://cloud.tencent.com/developer/chapter/13553
  • 响应头:响应数据的第二行开始,格式为key:value形式。http是个无状态的协议,所以可以在请求头和响应头中设置一些信息和想要执行的动作,这样,对方在收到信息后,就可以知道你是谁,你想干什么。

  • 响应体: 响应数据的最后一部分。存储响应的数据。

    2.4 响应数据设置

    Web服务器对HTTP协议的响应数据进行了封装(HttpServletResponse),并在调用Controller方法的时候传递给了该方法。这样,就使得程序员不必直接对协议进行操作,让Web开发更加便捷。

    @RestController
    public class ResponseController {
        /**
         * 方式一: HttpServletResponse 设置响应数据
         *
         * @param response
         * @throws IOException
         */
        @RequestMapping("/response")
        public void response(HttpServletResponse response) throws IOException {
            //设置响应状态码
            response.setStatus(401);
            //设置响应头
            response.setHeader("name", "itheima");
            //设置响应体
            response.getWriter().write("<h1>hello response</h1>");
        }
    
        /**
         * 方式二: ResponseEntity - Spring 中提供的方式
         *
         * @return
         */
        @RequestMapping("/response2")
        public ResponseEntity<String> response2() {
            return ResponseEntity
                    .status(401)
                    .header("name", "javaweb-ai")
                    .body("<h1>hello response</h1>");
        }
    }

    tips:响应状态码 和 响应头如果没有特殊要求的话,通常不手动设定。服务器会根据请求处理的逻辑,自动设置响应状态码和响应头。

    3 SpringBoot 案例

    3.1 需求说明

    需求:基于SpringBoot开发web程序,完成用户列表的渲染展示

    当在浏览器地址栏,访问前端静态页面(http://localhost:8080/usre.html)后,在前端页面上,会发送ajax请求,请求服务端(http://localhost:8080/list),服务端程序加载 user.txt 文件中的数据,读取出来后最终给前端页面响应json格式的数据,前端页面再将数据渲染展示在表格中。

    3.2 代码实现

    1.创建工程:再创建一个SpringBoot工程module,并勾选web依赖、lombok依赖。

    • Web 依赖:提供开发 Web 应用(包括 RESTful 接口、Spring MVC 等)所需的核心组件,自动集成了 Spring MVC、Tomcat 服务器、JSON 处理等功能。
    • Lombok 依赖:通过注解简化 Java 类的代码编写,自动生成 getter/setter、构造方法、toString() 等重复代码,减少模板代码冗余。

    2.准备工作:引入资料中准备好的数据文件user.txt,以及static下的前端静态页面。

    3.定义实体类:封装用户信息的实体类。在 com.itheima 下再定义一个包 pojo,专门用来存放实体类。 在该包下定义一个实体类User:

    /**
     * 封装用户信息
     */
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private Integer id;
        private String username;
        private String password;
        private String name;
        private Integer age;
        private LocalDateTime updateTime;
    }

    4.开发服务端程序:接收请求,读取文本数据并响应。
    引入一个非常常用的工具包hutool,是一个功能丰富的工具类库,封装了大量常用操作(如字符串处理、日期时间、文件操作、HTTP 请求等),能极大简化开发。

    /**
     * 用户信息的 Controller
     */
    @RestController
    public class UserController {
        @RequestMapping("/list")
        public List<User> list() throws Exception{
            //1.加载并读取 user.txt 文件
            InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
            ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
    
            //2.解析用户信息,封装为User对象 -> list集合
            List<User> userList = lines.stream().map(line -> {
                String[] parts = line.split(",");
                Integer id = Integer.parseInt(parts[0]);
                String username = parts[1];
                String password = parts[2];
                String name = parts[3];
                Integer age = Integer.parseInt(parts[4]);
                LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                return new User(id, username, password, name, age, updateTime);
            }).toList();
    
            //3.返回数据(json)
            return userList;
    
        }
    }

    在 Maven/Gradle 项目中,src/main/resources 目录下的文件编译后会被自动复制到 类路径(classpath)的根目录(例如 target/classes/ 或 build/classes/)。

    【读取类路径(classpath)下资源文件的经典代码】
    InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");

    this.getClass():获取当前类的 Class 对象。
    .getClassLoader():获取该类的类加载器(负责加载类路径下的资源)。
    .getResourceAsStream("user.txt"):通过类加载器查找类路径下的 user.txt 文件,并返回其输入流(InputStream)。
    .toList() / .collect(Collectors.toList()):将流(Stream)中的元素收集并转换为一个 List 集合。

    3.3 @ResponseBody

    • 类型:方法注解、类注解。

    • 位置:书写在Controller方法上或类上,我们在类上加了@RestController注解,而这个注解是由两个注解组合起来的,分别是:@Controller 、@ResponseBody。 那也就意味着,我们在类上已经添加了@ResponseBody注解了。

    • 作用:将方法返回值直接响应给浏览器,如果返回值类型是实体对象/集合,将会转换为JSON格式后在响应给浏览器。

    4 分层解耦

    4.1 三层架构

    单一职责原则:一个类或一个方法,就只做一件事情,只管一块功能。这样就可以让类、接口、方法的复杂度更低,可读性更强,扩展性更好,也更利于后期的维护。

    • Controller:控制层。接收前端发送的请求,对请求进行处理,并响应数据。

    • Service:业务逻辑层。负责业务逻辑处理的代码。

    • Dao:数据访问层(Data Access Object),也称为持久层。负责业务数据的维护操作,包括增、删、改、查等操作。

    三层架构的好处:复用性强、便于维护、利用扩展

    代码拆分:

    • 控制层包名:com.itheima.controller

    • 业务逻辑层包名:com.itheima.service

    • 数据访问层包名:com.itheima.dao

    (1)在 com.itheima.controller 中创建UserController类,代码如下:

    /**
     * 用户信息的 Controller
     */
    @RestController
    public class UserController {
    
        private UserService userService = new UserServiceImpl();
    
        @RequestMapping("/list")
        public List<User> list() throws Exception {
            //1. 调用service,获取数据
            List<User> userList = userService.findAll();
            //3. 返回数据(json)
            return userList;
    
        }
    }
    

    (2)在 com.itheima.service 中创建UserSerivce接口,代码如下:

    public interface UserService {
        /**
         * 查询所有用户信息
         * @return
         */
        public List<User> findAll();
    }
    

    在 com.itheima.service.impl 中创建UserSerivceImpl实现类,代码如下:

    public class UserServiceImpl implements UserService {
    
        //面向接口的实现方式 + 多态
        private UserDao userDao = new UserDaoImpl();
    
        @Override
        public List<User> findAll() {
            //1. 调用dao获取数据
            List<String> lines = userDao.findAll();
            //2. 解析用户信息,封装为User对象 -> list集合
            List<User> userList = lines.stream().map(line -> {
                String[] parts = line.split(",");
                Integer id = Integer.parseInt(parts[0]);
                String username = parts[1];
                String password = parts[2];
                String name = parts[3];
                Integer age = Integer.parseInt(parts[4]);
                LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                return new User(id, username, password, name, age, updateTime);
            }).toList();
            return userList;
        }
    }

    面向接口的实现方式 结合 多态,是面向对象编程中一种重要的设计思想。
    核心是通过接口定义规范,让不同实现类提供具体实现,再利用多态特性灵活调用,从而实现 “规范与实现分离”。
    作用-解耦:调用者只需依赖接口(UserDao),无需关心具体是 UserDaoImpl 还是别的什么实现,就算改变实现:
    private UserDao userDao = new UserDaoImpl2();
    也不用改下方调用部分的代码,降低代码耦合。

    (3)在 com.itheima.dao 中创建UserDao接口,代码如下:

    public interface UserDao {
        /**
         * 加载用户数据
         * @return
         */
        public List<String> findAll();
    }

    在 com.itheima.dao.impl 中创建UserDaoImpl实现类,代码如下:

    public class UserDaoImpl implements UserDao {
    
        @Override
        public List<String> findAll() {
            //1. 加载并读取 user.txt 文件
            InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
            ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
            return lines;
        }
    }

    4.2 分层解耦

    上方代码在改变实现时:
    private UserDao userDao = new UserDaoImpl2();
    我们需要切换为 UserServiceImpl2 这套实现,就需要修改Contorller的代码,但其实换的是Service层的实现,那此处就会发生层与层耦合。

    • 内聚:软件中各个功能模块内部的功能联系。

    • 耦合:衡量软件中各个层/模块之间的依赖、关联的程度。

    软件设计原则:高内聚低耦合。目的是使程序模块的可重用性、移植性大大增强。

    4.2.1 解耦思路

    (1)首先不能在EmpController中使用new对象。代码如下:

    @RestController
    public class UserController {
    
        private UserService userService;
    
        @RequestMapping("/list")
        public List<User> list() throws Exception {
            //1. 调用service,获取数据
            List<User> userList = userService.findAll();
            //3. 返回数据(json)
            return userList;
    
        }
    }
    

    但是,不能new对象,程序运行就报错,怎么办呢? 我们的解决思路是:

    • 提供一个容器,容器中存储一些对象(例:UserService对象)。

    • Controller程序从容器中获取UserService类型的对象。

    (2)将要用到的对象交给一个容器管理。

    (3)应用程序中用到这个对象,就直接从容器中获取。

    4.2.2 解耦问题

    • 我们如何将对象交给容器管理呢?
    • 程序运行时,容器如何为程序提供依赖的对象呢?

    我们想要实现上述解耦操作,就涉及到Spring中的两个核心概念:

    • 控制反转 Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部容器,这个容器称为:IOC容器或Spring容器。

    • 依赖注入 Dependency Injection,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入。例:EmpController程序运行时需要EmpService对象,Spring容器就为其提供并注入EmpService对象。

    • bean对象:IOC容器中创建、管理的对象,称之为:bean对象。

    如此,便可实现层与层的解耦。

    4.3 IOC & DI 入门

    (1)将 Service 及 Dao 层的实现类,交给IOC容器管理。

    在【实现类】(不是接口哦~)加上 @Component 注解,就代表把当前类产生的对象交给IOC容器管理。

    component 组件,指 “组成整体的一个独立部分”。在软件领域,它被用来描述具有独立功能、可复用的模块 —— 这个模块可以独立完成某类职责。

    (2)为 Controller 及 Service 注入运行时所依赖的对象。

    在【声明成员变量的代码处】加上 @Autowired 注解,就代表IOC容器为当前程序注入运行时所依赖的资源。

    autowired 自动连接,Spring 会自动找到当前类需要的依赖组件,并把它们 “连接”(注入)到一起,就像自动完成组件间的 “接线” 工作。

    /**
     * 用户信息的 Controller
     */
    @RestController
    public class UserController {
    
        @Autowired //应用程序运行时,会自动的查询该类型的bean对象,并赋值给该成员变量
        private UserService userService;
    
        @RequestMapping("/list")
        public List<User> list() throws Exception {
            //1. 调用service,获取数据
            List<User> userList = userService.findAll();
            //3. 返回数据(json)
            return userList;
        }
    }
    @Component //将当前类交给IOC容器管理
    public class UserServiceImpl implements UserService {
    
        @Autowired //应用程序运行时,会自动的查询该类型的bean对象,并赋值给该成员变量
        private UserDao userDao;
    
        @Override
        public List<User> findAll() {
            //1. 调用dao获取数据
            List<String> lines = userDao.findAll();
            //2. 解析用户信息,封装为User对象 -> list集合
            List<User> userList = lines.stream().map(line -> {
                String[] parts = line.split(",");
                Integer id = Integer.parseInt(parts[0]);
                String username = parts[1];
                String password = parts[2];
                String name = parts[3];
                Integer age = Integer.parseInt(parts[4]);
                LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                return new User(id, username, password, name, age, updateTime);
            }).toList();
            return userList;
        }
    }
    
    @Component //将当前类交给IOC容器管理
    public class UserDaoImpl implements UserDao {
    
        @Override
        public List<String> findAll() {
            //1. 加载并读取 user.txt 文件
            InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
            ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
            return lines;
        }
    }

    4.4 IOC 使用细节

    Bean的声明:IOC控制反转,就是将对象的控制权交给Spring的IOC容器,由IOC容器创建及管理对象,IOC容器创建的对象称为bean对象。

    而Spring框架为了更好的标识web应用程序开发当中,bean对象到底归属于哪一层,又提供了@Component的衍生注解:

    注解

    说明

    位置

    @Component

    声明bean的基础注解

    不属于以下三类时,用此注解

    @Controller

    @Component的衍生注解

    标注在控制层类上

    @Service

    @Component的衍生注解

    标注在业务层类上

    @Repository

    @Component的衍生注解

    标注在数据访问层类上(由于与mybatis整合,用的少)

    那么此时,我们就可以使用 @Service 注解声明Service层的bean。 使用 @Repository 注解声明Dao层的bean。 代码实现如下:

    @Service //将当前类交给IOC容器管理
    public class UserServiceImpl implements UserService {
    
        @Autowired //应用程序运行时,会自动的查询该类型的bean对象,并赋值给该成员变量
        private UserDao userDao;
    
        @Override
        public List<User> findAll() {
            //1. 调用dao获取数据
            List<String> lines = userDao.findAll();
            //2. 解析用户信息,封装为User对象 -> list集合
            List<User> userList = lines.stream().map(line -> {
                String[] parts = line.split(",");
                Integer id = Integer.parseInt(parts[0]);
                String username = parts[1];
                String password = parts[2];
                String name = parts[3];
                Integer age = Integer.parseInt(parts[4]);
                LocalDateTime updateTime = LocalDateTime.parse(parts[5], DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                return new User(id, username, password, name, age, updateTime);
            }).toList();
            return userList;
        }
    }
    @Repository //将当前类交给IOC容器管理
    public class UserDaoImpl implements UserDao {
    
        @Override
        public List<String> findAll() {
            //1. 加载并读取 user.txt 文件
            InputStream in = this.getClass().getClassLoader().getResourceAsStream("user.txt");
            ArrayList<String> lines = IoUtil.readLines(in, StandardCharsets.UTF_8, new ArrayList<>());
            return lines;
        }
    }

    【注意事项】

    1. Controller层有 @RestController ,所以不用再加@Controller。原因我们之前也提到过,@RestController = @Controller + @ResponseBody
    2. 声明bean的时候,可以通过注解的value属性指定bean的名字,比如:@Repository("userDao"),如果没有指定,默认为类名首字母小写 userDaoImpl。
    3. 前面声明bean的四大注解,要想生效,还需要被组件扫描注解 @ComponentScan 扫描。该注解虽然没有显式配置,但是实际上已经包含在了启动类声明注解 @SpringBootApplication 中,默认扫描的范围是启动类所在包及其子包

    4.5 DI 使用细节

    依赖注入,是指IOC容器要为应用程序去提供运行时所依赖的资源,而资源指的就是对象。

    在入门程序案例中,我们使用了@Autowired这个注解,完成了依赖注入的操作,而这个Autowired翻译过来叫:自动装配。@Autowired 注解,默认是按照类型进行自动装配的(去IOC容器中找某个类型的对象,然后完成注入操作)。

    @Autowired 进行依赖注入,常见的方式,有如下三种:

    (1)属性注入

    @RestController
    public class UserController {
    
        //依赖注入方式一:属性注入
        @Autowired
        private UserService userService;
     
    }
    • 优点:代码简洁、方便快速开发。

    • 缺点:隐藏了类之间的依赖关系、可能会破坏类的封装性。

    (2)构造函数注入

    使用 final 声明 userService:

    1. 强制在构造器中初始化:final 成员变量必须在对象创建时完成初始化(要么直接赋值,要么在构造器中赋值)。这与 “构造器注入” 的场景天然匹配,强制要求必须在构造器中对 userService 赋值,避免了 “忘记注入” 导致的空指针异常。

    2. 保证依赖不可变:final 修饰的变量一旦被赋值(在构造器中),就不能再被修改。这确保了 userService 从对象创建到销毁始终指向同一个实例,避免了在程序运行中被意外替换,保证了依赖的稳定性。

    @RestController
    public class UserController {
    
        //依赖注入方式二:构造器注入
        private final UserService userService;
        @Autowired //只有一个构造函数时,可省略
        public UserController(UserService userService) {
            this.userService = userService;
        }
    
    }
    
    • 优点:能清晰地看到类的依赖关系、提高了代码的安全性。

    • 缺点:代码繁琐、如果构造参数过多,可能会导致构造函数臃肿。

    • 注意:如果只有一个构造函数,@Autowired注解可以省略。(通常来说,也只有一个构造函数)

    (3)setter注入

    @RestController
    public class UserController {
    
        //依赖注入方式三:setter 注入
        private UserService userService;
        @Autowired
        public void setUserService(UserService userService) {
            this.userService = userService;
        }
    
    }
    • 优点:保持了类的封装性,依赖关系更清晰。

    • 缺点:需要额外编写setter方法,增加了代码量。

    综上,在项目开发中,基于@Autowired进行依赖注入时,基本都是第一种和第二种方式。(官方推荐第二种方式,因为会更加规范)但是在企业项目开发中,很多的项目中,也会选择第一种方式因为更加简洁、高效(在规范性方面进行了妥协)。

    那如果在IOC容器中,存在多个相同类型、不同的UserService的实现类的bean对象,会出现什么情况呢?

    会报错,解决方法有以下三种:

    (1)使用 @Primary 注解

    当存在多个相同类型的Bean注入时,加上@Primary注解,来确定默认的实现。

    @Primary
    @Service
    public class UserServiceImpl implements UserService {
    }

    (2)使用 @Qualifier("xxx") 注解

    指定当前要注入的bean对象。 在@Qualifier的value属性中,指定注入的bean的名称。 @Qualifier注解不能单独使用,必须配合@Autowired使用。

    @RestController
    public class UserController {
    
        @Qualifier("userServiceImpl")
        @Autowired
        private UserService userService;

    (3)使用 @Resource(name = "xxx") 注解

    是按照bean的名称进行注入。通过name属性指定要注入的bean的名称。

    @Resource = @Qualifier + @Autowired

    @RestController
    public class UserController {
            
        @Resource(name = "userServiceImpl")
        private UserService userService;

    面试题:@Autowired 与 @Resource的区别

    • @Autowired 默认是按照类型注入,而@Resource是按照名称注入

    • @Autowired 是spring框架提供的注解,而@Resource是JDK提供的注解

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值