尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【Spring】详解Spring IOCDI

【Spring】详解Spring IOCDI --------------------------------------------------------------------------目录------?前言IoC和DI的使用IoC详解???Bean存储???Controller控制器存储???获取Bean???根据Bean类型获取Bean???根据Bean名称获取Bean???根据Bean的名称和Bean类型获取Bean???Service(业务逻辑)???Repository仓库存储???Component组件存储???Configuration配置存储???额外小话题???为什么要有这么多类注解????方法注解???同一个类创建多个对象???扫描路径DI详解???属性注入???构造方法注入???Setter方法注入???三种注入方式的优缺点Autowired存在的问题???Primary???Qualifier???Resource???Autowired自动装配流程图???Autowired和Resouce的区别总结???Spring、Spring Boot和Spring MVC的关系及差别???区别???关系????Bean的命名???五大注解存储Bean???Bean注解存储Bean???Spring中常见注解* *前言–在前面中我们讲解了Spring MVC是什么以及如何使用。那么本篇我们就来讲Spirng Framework我们一般讲的Spring就指的是Spring Framework。在前面中我们已经讲解了什么是SpringIOC和AOP是Spring框架的核心思想。本篇我们主要讲IOC容器的使用。想了解什么是Spirng可以看这篇【Spring】什么是Spring-CSDN博客回顾一下IOC和DI。IOC控制反转是Spring框架中的核心思想。 控制反转Inversion of ControlIoC是一种设计思想其核心是将对象的创建和管理的控制权从使用方客户端转移到一个专门的容器如Spring容器 在软件开发中传统设计都是将对象的创建和管理交给使用方来进行创建而Ioc则是将对象的创建和管理交给IOC在需要使用的时候我们只需要告诉Ioc容器所需的对象那么它就可以为我们创建好。 Ioc很好的让每个模块进行了解耦实现了高内聚低耦合。DIDependency Injection依赖注入是Ioc的一种具体方式将对象的创建和管理权交给外部容器。 依赖注入将对象的依赖关系通过外部容器注入的方式来传递给对象而不是让对象自己去创建或查找依赖。IoC和DI的使用---------通过前面的学习相信对IoC和DI有初步的认识那么接下来我们就来具体学习IoC和DI的代码实现。既然Spring是一个IoC(控制反转容器作为容器那么就具备两个功能存和取。 Sring容器管理的主要是对象这些对象我们称为“Bean”把这些对象交给Spring管理由Spring来负责对象的创建和销毁我们只需要告诉Spring哪些要存以及如何从Spring中取出对象。 那么在Spring中如何存取Bean 1. 将类存储进Spring Ioc容器我们需要通过Component注解不止这个。 2. 取出对象需要使用注解Autowired。示例 package com.example.demo.model; import org.springframework.stereotype.Component; Component public class UserInfo { private String userName; public void hi(){ System.out.println(“hi”); } } package com.example.demo.component; import com.example.demo.model.UserInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; RequestMapping(“/user”) RestController public class UserComponent { Autowired private UserInfo userInfo; RequestMapping(“/hi”) public String hi(){ userInfo.hi(); return “调用成功”; } }可以看到确实能显现出我们想要的结果说明这两个注解能够实现堆Bean的存取。 注意我们在使用Autowired 注解时需要使用该注解的类有 Controller 注解修饰要使用Spring的IoC容器就要保证这个类是由Spring管理的。以上展示的就是Ioc和DI的基本使用接下来我们来深入学习IoC和DI。IoC详解-----### Bean存储在前面的案例中我们要把某个对象交给 IoC 容器管理那么就需要在类上添加一个注解Component。而Spring框架为了更好的服务Wen应用程序提供了丰富的注解。共有两类注解类型可以实现 *类注解Controller、Service、Repository、Component、Configuration *方法注解Bean我们先来讲下类注解。### Controller控制器存储使用 Controller 存储Bean package com.example.demo.controller; import org.springframework.stereotype.Controller; Controller public class UserController { public void say(){ System.out.println(“UserController …”); } } 既然我们已经将Bean存储到IoC容器中了那么如何获取Bean### 获取Bean先上代码 SpringBootApplication public class BeantextApplication { public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); // 从应用上下文中获取UserController bean的实例 UserController bean context.getBean(UserController.class); // 调用UserController实例的say方法执行相关逻辑 bean.say(); } }代码是由 SpringBootApplication注解修饰的类中写的并且我们用ApplicationContext接口来接收run()方法的返回值。 当我们运行一个带有 SpringBootApplication注解修饰的类会启动Spring项目并且SpringApplication.run()是有返回值的这个返回值可以接收也可以不接收。那为什么能用 ApplicationContext 类来接收为什么要用这个类 我们可以先查看一下run()方法的返回值是什么 我们可以看到run()的返回值类型是一个ConfigurableApplicationContext接口那这个接口内部是什么样 可以看到ConfigurableApplicationContext 接口是ApplicationContext 接口的一个子接口根据java的多态性子接口的实例可以被父接口类型接收因此这里可以用ApplicationContext来接收。 至于为什么要使用该接口 ApplicationContext是Spring框架中管理Bean的核心接口提供了访问Spring容器中所有Bean的基本功能几乎所有与Spring 容器的交互都可以通过ApplicationContext完成。 而在此处我们是要获取Bean那就需要使用ApplicationContext 。 在BeanFactory在我们可以看到getBean() 方法顾名思义这个方法就是用来获取Bean的。在这里我们只介绍1、2、4种。#### 根据Bean类型获取Bean在上面中我们就是根据Bean的类型来获取到Bean。 这种方式是Sprinig利用反射机制实现的。Spring能通过类型找到对应的Bean是因为在容器初始化的时候Spring会通过反射机制扫描并注册所有Bean的定义信息。 反射是Spring实现依赖注入和Bean管理的核心技术允许Spring在运行时动态地创建对象、访问和修改对象的字段、调用对象的方法。 SpringBootApplication public class BeantextApplication { public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); // 从应用上下文中获取UserController bean的实例 UserController bean context.getBean(UserController.class); // 调用UserController实例的say方法执行相关逻辑 bean.say(); } }#### 根据Bean名称获取BeanBean名称应该如何写我们可以看下官方回答 简单来说就是如果类名前两个字母中大写字母个数小于2也就是只有首字母大写那么该类在交给Ioc后就会以第一个字母小写的小驼峰形式命名当类名前两个字母都大写那么该类型就作Bean名称。 public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); // 从应用上下文中获取UserController bean的实例 UserController bean (UserController) context.getBean(“userController”); // 调用UserController实例的say方法执行相关逻辑 bean.say(); }#### 根据Bean的名称和Bean类型获取Bean public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); // 从应用上下文中获取UserController bean的实例 UserController bean (UserController) context.getBean(“userController”, UserController.class); // 调用UserController实例的say方法执行相关逻辑 bean.say(); }### Service(业务逻辑)这个注解也可以存储Bean。 package com.example.demo.service; import org.springframework.stereotype.Service; Service public class UserService { public void say(){ System.out.println(“UserService say”); } } 读取 public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); UserService userService (UserService) context.getBean(“userService”); userService.say(); }### Repository仓库存储 package com.example.demo.controller; import org.springframework.stereotype.Repository; Repository public class UserRepository { public void say() { System.out.println(“UserRepository say”); } } public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); UserRepository userRepository (UserRepository)context.getBean(“userRepository”); userRepository.say(); }### Component组件存储 package com.example.demo.component; import org.springframework.stereotype.Component; Component public class UserComponents { public void say() { System.out.println(“UserComponents say”); } } public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); UserComponents bean context.getBean(UserComponents.class); bean.say(); }### Configuration配置存储 package com.example.demo.configuration; import org.springframework.context.annotation.Configuration; Configuration public class UserConfiguration { public void say() { System.out.println(“UserConfiguration”); } } public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); UserConfiguration bean context.getBean(UserConfiguration.class); bean.say(); }### 额外小话题下面的代码在最后判断对象是否相等那会不会相等呢 public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); UserConfiguration bean context.getBean(UserConfiguration.class); bean.say(); UserConfiguration bean1 (UserConfiguration) context.getBean(“userConfiguration”); bean1.say(); UserConfiguration bean2 context.getBean(“userConfiguration”, UserConfiguration.class); bean2.say(); System.out.println(bean); System.out.println(bean1); System.out.println(bean2); System.out.println(bean bean1); System.out.println(bean bean2); }我们来看下结果我们可以看到利用三种方法获取Bean但最后返回的都是同一个实例这是因为Spring中默认情况下Bean的作用域是单例。### 为什么要有这么多类注解这个和前面讲的应用分层相呼应的当开发者看到类注解的时候就能直接了解当前类的用途。 * Controller控制层,接收请求,对请求进处理,并进响应 * Servie业务逻辑层,处理具体的业务逻辑 * Repository数据访问层也称为持久层.负责数据访问操作 * Configuration配置层.处理项中的些配置信息 *我们可以看到Component都有在其他四个注解中说明Component 是其他注解的“父类”,即其他注解是Component注解的衍生注解。不过其他四个注解用于更具体的用例如三层结构Controller对应控制层、Service对于业务逻辑层、Repository对应持久层。### 方法注解类注解是加上类上的但是存在两个问题1. 使用外部包里的类没有办法添加类注解2. 一个类需要多个对象比如多个数据源。对于这种场景我们就需要用方法注解Bean。我们假设User类是一个外包中的类不能加类注解所以这就需要使用方法注解。 package com.example.demo.model; import lombok.Data; Data public class User { private String name; private int age; } package com.example.demo.controller; import com.example.demo.model.User; import org.springframework.context.annotation.Bean; public class UserController { Bean public User user(){ User user new User(); user.setName(“张三”); user.setAge(18); return user; } } 测试一下代码可以看到报了异常说没有找到Bean这是因为UserController没有加类注解Spring没有扫描到。所以要加上类注解 package com.example.demo.controller; import com.example.demo.model.User; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; Controller public class UserController { Bean public User user(){ User user new User(); user.setName(“张三”); user.setAge(18); return user; } }#### 同一个类创建多个对象在前面中我们已经知道了在Spring容器中Bean默认是单例那么如果想要获取多个Bean应该如何做在使用Bean时指定Bean名称这里的名称是方法名称。 package com.example.demo.controller; import com.example.demo.model.User; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; Controller public class UserController { Bean public User user(){ User user new User(); user.setName(“张三”); user.setAge(18); return user; } Bean(“user1”) public User user1(){ User user new User(); user.setName(“李四”); user.setAge(20); return user; } }此外我们观察Bean注解的属性可以看到name字段是一个字符串数组说明一个Bean可以有多个名称。例如 Bean(name{“user1”,“lisi”}) 需要注意有多个名称的情况下需要用大括号括起来并指定字段。当然类注解也可以指定名称但只能指定一个。### 扫描路径在项目中不是所有的文件都会被扫描的只有在 SpringBootApplication注解所在的包中才会被扫描。假如我们把 SpringBootApplication的类移动一下。运行一下该错误是由于Spring容器无法找到类型为com.example.demo.model.UserInfo的Bean导致依赖注入失败。具体来说UserComponent类中的userInfo字段需要一个UserInfo类型的Bean但Spring上下文中没有定义该Bean。这是因为UserInfo类没有被扫描的因为UserInfo类不在SpringBoot启动类所在的包中。所以无法注入。默认扫描的范围是SpringBoot启动类所在包以及其子包推荐把启动类放在我们希望扫描的包的路径下这样我们定义的Bean就都能被扫描到。DI详解----依赖注入是一个过程指IoC容器在创建Bean时去提供运行时所依赖的资源而资源指的就是对象。在前面IoC学习中我们使用了 Autowired 注解来完成注入。对于依赖注入Spring给我们提供了三种方式 *属性注入(Field Injection) *构造方法注入(Constructor Injection) *Setter 注入Setter Injection### 属性注入属性注入是用 Autowired 实现的直接在属性上加上该注解。 package com.example.demo.service; import org.springframework.stereotype.Service; Service public class UserService { public void say(){ System.out.println(“UserService say”); } } package com.example.demo.controller; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; Controller public class DiController { //属性注入 Autowired private UserService userService; public void say(){ System.out.println(“DiController say”); userService.say(); } } 测试一下; public static void main(String[] args) { // 启动Spring Boot应用并将应用上下文注入到变量context中 ApplicationContext contextSpringApplication.run(BeantextApplication.class, args); DiController bean context.getBean(DiController.class); bean.say(); }### 构造方法注入 package com.example.demo.controller; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; Controller public class DiController { //属性注入 // Autowired private UserService userService; //构造方法注入 public DiController(UserService userService){ this.userService userService; } public void say(){ System.out.println(“DiController say”); userService.say(); } }注意如果类中只有一个构造方法那么 Autowired 注解就可以省略但如果类中有多个构造方法那么就需要用 Autowired 来明确要使用哪个构造方法。package com.example.demo.controller; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; Controller public class DiController { //属性注入 // Autowired private UserService userService; public DiController(){ System.out.println(“DiController 构造函数”); } Autowired public DiController(UserService userService){ this.userService userService; } public void say(){ System.out.println(“DiController say”); userService.say(); } } ### Setter方法注入 Controller public class DiController { //属性注入 // Autowired private UserService userService; //set注入 public void setUserService(UserService userService) { this.userService userService; } public void say(){ System.out.println(“DiController say”); userService.say(); } }这里报错的原因是由于set方法没有被调用到所以我们需要用 Autowired注解修饰。 package com.example.demo.controller; import com.example.demo.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; Controller public class DiController { //属性注入 // Autowired private UserService userService; //set注入 Autowired public void setUserService(UserService userService) { this.userService userService; } public void say(){ System.out.println(“DiController say”); userService.say(); } }### 三种注入方式的优缺点 1.属性注入 1. 优点简单使用方便 2. 缺点 1. 只能用于IoC容器如果是非IoC容器不可用并且只有在使用的时候才会出现NPE空指针异常 2. 不能注入一个Final修饰的属性 2.构造方法注入Spring 4.X推荐: 1. 优点 1. 可以注入final修饰的属性 2. 注入的对象不会被修改 3. 依赖对象在使用前一定会被完全初始化因为依赖是在类的构造方法中执行的而构造方法是在类加载阶段就会执行的方法。 4. 通用性好构造方法是JDK支持的所以更换任何框架都适用 2. 缺点注入多个对象时代码会比较繁琐 3.Setter注入(Spring 3.X推荐 1. 优点方便在类实例后重新对该对象进行配置或者注入 2. 缺点 1. 不能注入一个Final修饰的属性 2. 注入对象可能会被改变因为setter方法可能会被多次调用就有被修改的风险示例为什么有final修饰的属性不能直接注入如果一个属性被final修饰那么这个属性就变成了一个常量不可被修改而依赖注入的属性需要动态地修改属性的值所以不能对final修饰的属性进行依赖注入。但在构造方法中final修饰的属性是可以被赋值的这是因为构造方法是在对象创建时执行此时final属性还没有被赋值所以在构造方法中可以对final属性进行赋值。总的来说*final修饰的属性不能直接通过依赖注入赋值是因为其不可变性和依赖注入的动态赋值机制相冲突**但构造方法注入是一种合法且符合final语义的方案。Autowired存在的问题--------------当同一个类类型存在多个Bean使用 Autowired 会存在问题。 package com.example.demo.controller; import com.example.demo.model.User; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Controller; Controller public class UserController { Bean public User user1(){ User user new User(); user.setName(“张三”); user.setAge(18); return user; } Bean(“u2”) public User user2(){ User user new User(); user.setName(“李四”); user.setAge(20); return user; } }那么应该如何解决上面这种情况呢在下面有给出了建议在Spring中提供了以下几种方法 * Primary * Qualifier * Resource### Primary使用该注解当存在多个相同类型的Bean注入时加上Primary注解来确定默认的实现。 package com.example.demo.controller; import com.example.demo.model.User; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Controller; Controller public class UserController { Bean public User user1(){ User user new User(); user.setName(“张三”); user.setAge(18); return user; } Bean(“u2”) Primary public User user2(){ User user new User(); user.setName(“李四”); user.setAge(20); return user; } }### QualifierQualifier注解指定当前要注入的Bean对象在Qualifier的value属性中指定要注入Bean的名称。注意Qualifier注解不能单独使用必须配合Autowired使用。### ResourceResource按照Bean的名称来进行注入。通过name属性来指定要注入的bean名称。 Resource(name “user1”) private User user;### Autowired自动装配流程图### Autowired和Resouce的区别 *Autowired 是spring框架提供的注解Resource是JDK提供的注解 * **Autowired 默认是按照类型注Resource是按照名称注。相于 Autowired 来说Resource 支持更多的参数设置例如 name 设置根据名称获取 Bean。*总结–### Spring、Spring Boot和Spring MVC的关系及差别#### 区别SpringSpring是一个轻量级、一站式、模块化的开发应用框架其目的是用于简化企业应用程序开发。 Spring MVCSpring MVC是Spring的一个子框架Spring诞生之后那要如何使用所以就需要提供接口于是按照MVC设计模式设计了一个MVC框架(一些用Spring解耦的组件主要用于Web开发和网络接口简单来说Spring MVC是一个Web框架。 Spring BootSpring Boot是对Spring的一个封装为了简化Spring应用开发而出现的使用Spring Boot能更加快速的搭建框架降低成本让开发者更加专注Spring应用开发而无需过多关注XML的配置和一些底层实现。#### 关系 Spring的主要功能是管理对象以及对象之间的依赖关系AOP、数据库事务管理、数据访问、Wen框架等。 而Spring MVC是基于Spring开发的与Spring框架集成可以让我们更加简洁的进行Wen开发。 而Spring相当于一个脚手架插拔式搭建项目可以快速集成其他框架进来。 比如我们想要使用Spring Boot开发Web项目那么只需要引入 Spring MVC即可Wen开发的工作是Spring MVC完成的而不是Spring Boot想要完成数据访问就只需要引入MyBatis框架。 用一句话总结Spring MVC和Spring Boot都属于SprIngSpring MVC是基于Spring的一个Web MVC框架而Spring Boot是基于Spring的一套快速开发整合包。### Bean的命名#### 五大注解存储Bean *前两位字母均为大写则Bean名称为类型 *其他的为类名首字母小写 *通过value属性设置如Controllervalue“user”。 #### Bean注解存储Bean *bean名称为方法名 *通过name属性设置### Spring中常见注解 *web url映射RequestMapping *参数接收和接口响应RequestParam、RequestBody、ResponseBody *bean的存储Controller、Service、Repository、Component、Configuration、Bean *bean的获取Autowired、Qualifier、Resource* *以上就是有关IoC和DI的相关知识。若有不足欢迎指正~
返回列表