十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring IOC与DI:控制反转与依赖注入详解

Spring IOC与DI:控制反转与依赖注入详解 文章目录1. IOC介绍2. DI介绍3. IOC详解3.1 Bean的存储3.1.1 ControllerBean命名3.1.2 Service(服务存储)3.1.3 Repository(仓库存储)3.1.4 Component(组件存储)3.1.5 Configuration(配置存储)3.1.6 为什么要这么多类注解3.2 方法注解Bean3.2.1 定义多个对象3.2.2 重命名Bean3.3 扫描路径4. DI详解4.1.1 属性注入4.1.2 构造方法注入4.1.3 Setter注入4.2 三种注入优缺点分析4.3 Autowired存在问题(同类多Bean情况)5. Spring, SpringMVC, SpringBoot1. IOC介绍Spring 是包含了众多工具方法的 IoC 容器容器 容器是用来容纳某种物品的基本装置IoC 是Spring的核心思想, 在类上面添加 RestController, Controller 注解, 就是把这个对象交给Spring管理, Spring 框架启动时就会加载该类. 把对象交给Spring管理, 就是IOC思想IOC: Inversion of Control (控制反转), 获得依赖对象的过程被反转了也就是说, 当需要某个对象时, 传统开发模式中需要自己通过 new 创建对象, 现在不需要再进行创建, 把创建对象的任务交给容器, 程序中只需要依赖注入 (Dependency Injection,DI)就可以了. 这个容器称为IoC容器. Spring是一个IoC容器, 所以有时Spring 也称为Spring 容器我们可以尝试不在每个类中自己创建下级类如果自己创建下级类就会出现当下级类发生改变操作自己也要跟着修改.此时我们只需要将原来由自己创建的下级类改为传递的方式也就是注入的方式因为我们不需要在当前类中创建下级类了所以下级类即使发生变化创建或减少参数当前类本身也无需修改任何代码这样就完成了程序的解耦资源不由使用资源的双方管理而由不使用资源的第三方管理这可以带来很多好处。第一资源集中管理实现资源的可配置和易管理。第二降低了使用资源双方的依赖程度也就是我们说的耦合度。IoC容器具备以下优点:资源集中管理: IoC容器会帮我们管理一些资源(对象等), 我们需要使用时, 只需要从IoC容器中去取就可以了我们在创建实例的时候不需要了解其中的细节, 降低了使用资源双方的依赖程度, 也就是耦合度2. DI介绍DI: Dependency Injection(依赖注入), 容器在运行期间, 动态的为应用程序提供运行时所依赖的资源称之为依赖注入.IoC 是一种思想也是目标, 而思想只是一种指导原则最终还是要有可行的落地方案而 DI 就属于具体的实现。所以也可以说, DI 是IoC的一种实现.IOCDI的简单使用Service层及Dao层的实现类交给Spring管理: 使用注解: Component在Controller层 和Service层 注入运行时依赖的对象: 使用注解 Autowired3. IOC详解我们已经知道了Spring IoC 和DI的基本操作, 接下来我们来系统的学习Spring IoC和DI的操作。前面我们提到IoC控制反转就是将对象的控制权交给Spring的IOC容器由IOC容器创建及管理对象 也就是bean的存储3.1 Bean的存储类注解Controller、Service、Repository、Component、Configuration.方法注解Bean3.1.1 Controller使用 Controller 存储 beanControllerpublicclassUserController{publicvoidsayHi(){System.out.println(Hello);}}根据类型获取对象SpringBootApplicationpublicclassTestApplication{publicstaticvoidmain(String[]args){ApplicationContextcontextSpringApplication.run(TestApplication.class,args);// 从上下文中获取对象UserControlleruserControllercontext.getBean(UserController.class);userController.sayHi();}}Spring容器中, 同一个类型存在多个bean的话, 怎么来获取呢?ApplicationContext 也提供了其他获取bean的方式, ApplicationContext 获取bean对象的功能, 是父类BeanFactory提供的功能// 1. 根据bean名称获取beanObjectgetBean(Stringvar1)throwsBeansException;// 2. 根据bean名称和类型获取beanTTgetBean(Stringvar1,ClassTvar2)throwsBeansException;// 3. 按bean名称和构造函数参数动态创建bean,只适用于具有原型(prototype)作用域的beanObjectgetBean(Stringvar1,Object...var2)throwsBeansException;// 4. 根据类型获取beanTTgetBean(ClassTvar1)throwsBeansException;// 5. 按bean类型和构造函数参数动态创建bean, 只适用于具有原型(prototype)作用域的beanTTgetBean(ClassTvar1,Object...var2)throwsBeansException;常用的是上述1,2,4种, 这三种方式,获取到的bean是一样的Spring bean是Spring框架在运行时管理的对象, Spring会给管理的对象起一个名字, 根据Bean的名称(BeanId)就可以获取到对应的对象.Bean命名程序开发人员不需要为bean指定名称(BeanId), 如果没有显式的提供名称(BeanId)Spring容器将为该bean生成唯一的名称.命名约定使用Java标准约定作为实例字段名. 也就是说bean名称以小写字母开头然后使用驼峰式大小写.也有一些特殊情况, 当有多个字符并且第一个和第二个字符都是大写时, 将保留原始的大小写. 这些规则与java.beans.Introspector.decapitalize (Spring在这里使用的)定义的规则相同.类名: UserController, Bean的名称为: userController类名: UController, Bean的名称为: UControllerpublicstaticvoidmain(String[]args){ApplicationContextcontextSpringApplication.run(TestApplication.class,args);// 根据bean类型, 从上下文中获取对象UserControlleruserController1context.getBean(UserController.class);// 根据bean名称, 从上下文中获取对象UserControlleruserController2(UserController)context.getBean(userController);// 根据bean名称类型, 从上下文中获取对象UserControlleruserController3context.getBean(userController,UserController.class);System.out.println(userController1);System.out.println(userController2);System.out.println(userController3);}地址一样, 说明对象是一个ApplicationContext VS BeanFactory• 继承关系和功能方面来说Spring 容器有两个顶级的接口BeanFactory 和ApplicationContext。其中 BeanFactory 提供了基础的访问容器的能力而ApplicationContext 属于 BeanFactory 的子类它除了继承了 BeanFactory 的所有功能之外它还拥有独特的特性还添加了对国际化支持、资源访问支持、以及事件传播等方面的支持.• 从性能方面来说ApplicationContext 是一次性加载并初始化所有的 Bean 对象而BeanFactory 是需要那个才去加载那个因此更加轻量. (空间换时间)3.1.2 Service(服务存储)ServicepublicclassUserService{publicvoidsayHi(){System.out.println(Hello);}}3.1.3 Repository(仓库存储)RepositorypublicclassUserRepository{publicvoidsayHi(){System.out.println(Hi, UserRepository~);}}3.1.4 Component(组件存储)ComponentpublicclassUserComponent{publicvoidsayHi(){System.out.println(Hi, UserComponent~);}}3.1.5 Configuration(配置存储)ConfigurationpublicclassUserConfiguration{publicvoidsayHi(){System.out.println(Hi,UserConfiguration~);}}3.1.6 为什么要这么多类注解程序员看到类注解之后就能直接了解当前类的用途• Controller控制层, 接收请求, 对请求进行处理, 并进行响应.• Servie业务逻辑层, 处理具体的业务逻辑.• Repository数据访问层也称为持久层. 负责数据访问操作• Configuration配置层. 处理项目中的一些配置信息.查看Controller / Service / Repository / Configuration 等注解的源码发现:其实这些注解里面都有一个注解 Component 说明它们本身就是属于 Component 的子类Component 是一个元注解也就是说可以注解其他类注解, Controller , Service 和 Repository 用于更具体的用例(分别在控制层, 业务逻辑层, 持久化层), 在开发过程中, 如果你要在业务逻辑层使用 Component 或Service显然Service是更好的选择类注解是添加到某个类上的 但是存在两个问题使用外部包里的类, 没办法添加类注解一个类, 需要多个对象, 比如多个数据源3.2 方法注解Bean这种场景, 我们就需要使用方法注解 Bean在 Spring 框架的设计中方法注解 Bean 要配合类注解才能将对象正常的存储到 Spring 容器中ComponentpublicclassBeanConfig{BeanpublicPersonperson(){PersonpersonnewPerson();person.setAge(20);returnperson;}}publicstaticvoidmain(String[]args){ApplicationContextcontextSpringApplication.run(TestApplication.class,args);Personpersoncontext.getBean(Person.class);System.out.println(person.getAge());}3.2.1 定义多个对象对于同一个类, 如何定义多个对象呢?ComponentpublicclassBeanConfig{BeanpublicPersonperson1(){PersonpersonnewPerson();person.setAge(20);returnperson;}BeanpublicPersonperson2(){PersonpersonnewPerson();person.setAge(21);returnperson;}}定义了同一个类的对象后, 如果再根据Bean的类型获取对象, 就会发生报错, 期望只有一个匹配, 结果发现了两个.Bean 注解的bean, bean的名称就是它的方法名, 接下来我们根据名称来获取bean对象publicstaticvoidmain(String[]args){ApplicationContextcontextSpringApplication.run(TestApplication.class,args);Personperson1(Person)context.getBean(person1);Personperson2(Person)context.getBean(person2);System.out.println(person1,person2);}3.2.2 重命名Bean可以通过设置 name 属性给 Bean 对象进行重命名操作Bean(name{p1,person1})publicPersonperson1(){PersonpersonnewPerson();person.setAge(20);returnperson;}publicstaticvoidmain(String[]args){ApplicationContextcontextSpringApplication.run(TestApplication.class,args);Personperson1(Person)context.getBean(p1);Personperson2(Person)context.getBean(person1);System.out.println(person1,person2);}name可以省略, 写成Bean({“p1”, “person1”})只有一个名称时候, {}也可以省略, Bean(“p1”)3.3 扫描路径使用前面学习的四个注解声明的bean一定会生效吗?不一定原因bean想要生效还需要被Spring扫描)使用五大注解声明的bean要想生效, 还需要配置扫描路径, 让Spring扫描到这些注解也就是通过 ComponentScan 来配置扫描路径SpringBootApplicationComponentScan({com.example.test.demos})那为什么前面没有配置 ComponentScan注解也可以呢?ComponentScan 注解虽然没有显式配置但是实际上已经包含在了启动类声明注解SpringBootApplication 中了?默认扫描的范围是SpringBoot启动类所在包及其子包, 在配置类上添加 ComponentScan 注解, 该注解默认会扫描该类所在的包下所有的配置类, 把启动类放在我们希望扫描的包的路径下, 这样我们定义的bean就都可以被扫描到修改META-INF 自动配置文件也能做到加载Bean的效果, 一般开发公共组件会用上, 可自行简单了解4. DI详解依赖注入是一个过程是指IoC容器在创建Bean时, 去提供运行时所依赖的资源而资源指的就是对象.DI简单来说, 就是把对象取出来放到某个类的属性中.关于依赖注入, Spring也给我们提供了三种方式4.1.1 属性注入属性注入是使用 AutowiredControllerpublicclassUserController{// 注入方法1: 属性注入AutowiredprivateUserServiceuserService;publicvoidsayHi(){userService.sayHi();}}4.1.2 构造方法注入构造方法注入是在类的构造方法中实现注入注意事项如果类只有一个构造方法那么 Autowired 注解可以省略如果类中有多个构造方法那么需要添加上 Autowired 来明确指定到底使用哪个构造方法ControllerpublicclassUserController{//注入方法2: 构造方法privateUserServiceuserService;AutowiredpublicUserController(UserServiceuserService){this.userServiceuserService;}publicvoidsayHi(){userService.sayHi();}}4.1.3 Setter注入Setter 注入和属性的 Setter 方法实现类似只不过在设置 set 方法的时候需要加上 Autowired 注解ControllerpublicclassUserController{// 注入方法3: Setter方法注入privateUserServiceuserService;AutowiredpublicvoidsetUserService(UserServiceuserService){this.userServiceuserService;}publicvoidsayHi(){userService.sayHi();}}4.2 三种注入优缺点分析属性注入:优点:简洁使用方便缺点:只能用于 IoC 容器, 并且只有在使用的时候才会出现 NPE空指针异常);不能注入一个Final修饰的属性构造函数注入优点:可以注入final修饰的属性, 注入的对象不会被修改;依赖对象在使用前一定会被完全初始化因为依赖是在类的构造方法中执行的而构造方法是在类加载阶段就会执行的方法;通用性好, 构造方法是JDK支持的, 所以更换任何框架,他都是适用的缺点:注入多个对象时, 代码会比较繁琐Setter注入优点:方便在类实例之后, 重新对该对象进行配置或者注入缺点:不能注入一个Final修饰的属性注入对象可能会被改变, 因为setter方法可能会被多次调用, 就有被修改的风险4.3 Autowired存在问题(同类多Bean情况)当同一类型存在多个bean时, 使用Autowired会存在问题Bean(namep1)publicPersonperson1(){PersonpersonnewPerson();person.setAge(20);returnperson;}BeanpublicPersonperson2(){PersonpersonnewPerson();person.setAge(21);returnperson;}Spring提供了以下几种解决方案:PrimaryQualifierResource使用Primary注解当存在多个相同类型的Bean注入时加上Primary注解来确定默认的实现PrimaryBean(namep1)publicPersonperson1(){PersonpersonnewPerson();person.setAge(20);returnperson;}BeanpublicPersonperson2(){PersonpersonnewPerson();person.setAge(21);returnperson;}使用Qualifier注解指定当前要注入的bean对象。 在Qualifier的value属性中指定注入的bean的名称; Qualifier注解不能单独使用必须配合Autowired使用AutowiredQualifier(person2)privatePersonperson;使用Resource注解是按照bean的名称进行注入。通过name属性指定要注入的bean的名称Resource(namep1)privatePersonperson;Autowird 与 Resource的区别Autowired 是spring框架提供的注解而Resource是JDK提供的注解Autowired 默认是按照类型注入而Resource是按照名称注入. 相比于 Autowired 来说Resource 支持更多的参数设置例如 name 设置根据名称获取 Bean5. Spring, SpringMVC, SpringBoot学到这里对Spring, Spring Boot 和Spring MVC应该有了一定的了解, 简单总结下三者关系以及区别Spring: 简单来说, Spring 是一个开发应用框架 其目的是用于简化企业级应用程序开发Spring的主要功能: 管理对象以及对象之间的依赖关系, 面向切面编程, 数据库事务管理, 数据访问, web框架支持等但是Spring具备高度可开放性, 并不强制依赖Spring, 开发者可以自由选择Spring的部分或者全部, Spring可以无缝继承第三方框架Spring MVC Spring MVC: Spring MVC是Spring的一个子框架, Spring诞生之后, 大家觉得很好用, 于是按照MVC模式设计了一个 MVC框架, 主要用于开发WEB应用和网络接口所以,Spring MVC 是一个Web框架Spring MVC基于Spring进行开发的, 天生的与Spring框架集成. 可以让我们更简洁的进行Web层开发, 支持灵活的 URL 到页面控制器的映射, 提供了强大的约定大于配置的契约式编程支持Spring Boot: Spring Boot是对Spring的一个封装, 为了简化Spring应用的开发而出现的中小型企业没有成本研究自己的框架, 使用Spring Boot 可以更加快速的搭建框架, 降级开发成本, 让开发人员更加专注于Spring应用的开发而无需过多关注XML的配置和一些底层的实现。Spring Boot是个脚手架, 插拔式搭建项目, 可以快速的集成其他框架进来.比如想使用SpringBoot开发Web项目, 只需要引入Spring MVC框架即可, Web开发的工作是SpringMVC完成的, 而不是SpringBoot, 想完成数据访问, 只需要引入Mybatis框架即可最后一句话总结: Spring MVC和Spring Boot都属于SpringSpring MVC 是基于Spring的一个MVC 框架而Spring Boot 是基于Spring的一套快速开发整合包
返回列表