IoC容器
IoC和IoC容器不是同一个东西哟。
IoC是面向对象编程里的一个重要原则,目的是从程序里移出原有的控制权,把控制权交给了容器。IoC容器是一个中心化的地方,负责管理对象,也就是Bean的创建、销毁、依赖注入等操作,让程序变得更加灵活、可扩展、易于维护。
在使用IoC容器时,我们需要先配置容器,包括注册需要管理的对象、配置对象之间的依赖关系以及对象的生命周期等。然后,IoC容器会根据这些配置来动态地创建对象,并把它们注入到需要它们的位置上。当我们使用IoC容器时,需要将对象的配置信息告诉IoC容器,这个过程叫做依赖注入(DI),而IoC容器就是实现依赖注入的工具。因此,理解IoC容器就是理解它是如何管理对象,如何实现DI的过程。
举个例子来说,我们有一个程序需要使用A对象,这个A对象依赖于一个B对象。我们可以把A对象和B对象的创建、配置工作都交给IoC容器来处理。这样,当程序需要使用A对象的时候,IoC容器会自动创建A对象,并将依赖的B对象注入到A对象中,最后返回给程序使用。
IoC容器的意思是使用Bean容器管理一个个的Bean,最简单的Bean就是一个Java的业务对象。在Java中,创建一个对象最简单的方法就是使用 new 关键字。IoC容器,也就是BeanFactory,存在的意义就是将创建对象与使用对象的业务代码解耦,让业务开发人员无需关注底层对象(Bean)的构建和生命周期管理,专注于业务开发。
怎么实现IoC容器呢
我们需要通过特定的规则在外部配置好Bean的声明。然乎通过发射或者其他手段创建这个Bean的实例。创建实例之后,我们用一个Map来保存Bean的实例;最后我们提供一个getBean() 方法供外部使用。这就是IoC容器的简单实现过程
在IoC容器中,对象的创建和依赖关系的管理大体分为两个过程。
- 对象实例化:IoC容器负责创建需要使用的对象实例。这意味着如果一个对象需要使用其他对象,IoC容器会自动处理这些对象的创建,并且将它们注入到需要它们的对象中。
- 依赖注入:IoC容器负责把其他对象注入到需要使用这些依赖对象的对象中。这意味着我们不需要显式地在代码中声明依赖关系,IoC容器会自动将依赖注入到对象中,从而解耦了对象之间的关系。
这些过程大大简化了对象创建和依赖关系的管理,使代码更加易于维护和扩展。
一个Demo
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>tech.pdai</groupId>
<artifactId>spring-framework-demo-01</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring.version>5.3.23</spring.version>
<aspectjweaver.version>1.9.6</aspectjweaver.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectjweaver.version}</version>
</dependency>
</dependencies>
</project>
package com.evan.dao;
import com.evan.entity.User;
import java.util.Collections;
import java.util.List;
public class UserDaoImpl {
public List<User> findUserList() {
return Collections.singletonList(new User("evan", 18));
}
}
package com.evan.service;
import com.evan.dao.UserDaoImpl;
import com.evan.entity.User;
import java.util.List;
public class UserServiceImpl {
private UserDaoImpl userDao;
public UserServiceImpl() {
}
public void setUserDao(UserDaoImpl userDao) {
this.userDao = userDao;
}
public List<User> findUserList() {
return this.userDao.findUserList();
}
}
spring.xml
<?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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
">
<bean id="userDao" class="com.evan.dao.UserDaoImpl"/>
<bean id="userService" class="com.evan.service.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="logAspect" class="com.evan.aspect.LogAspect"/>
</beans>
package com.evan;
import com.evan.entity.User;
import com.evan.service.UserServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
public class AppTest {
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
UserServiceImpl service = context.getBean("userService", UserServiceImpl.class);
List<User> userList = service.findUserList();
userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
}
}
结果
evan,18
站在Spring的角度如何理解IoC
Spring Bean是什么
IoC Container管理的是Spring Bean, 那么Spring Bean是什么呢?
Spring里面的bean就类似是定义的一个组件,而这个组件的作用就是实现某个功能的,这里所定义的bean就相当于给了你一个更为简便的方法来调用这个组件去实现你要完成的功能。
IoC是什么
Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。
- 如果没有Spring框架,我们需要自己创建User/Dao/Service等,比如:
public static void main(String[] args) {
UserDaoImpl userDao = new UserDaoImpl();
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(userDao);
List<User> userList = userService.findUserList();
userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
}
- 有了Spring框架,可以将原有Bean的创建工作转给框架, 需要用时从Bean的容器中获取即可,这样便简化了开发工作(分离关注点)
ApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
UserServiceImpl service = context.getBean("userService", UserServiceImpl.class);
List<User> userList = service.findUserList();
userList.forEach(a -> System.out.println(a.getName() + "," + a.getAge()));
- 谁控制谁,控制什么?
传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
- 为何是反转,哪些方面反转了?
传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。
其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。
Sping IoC 配置的三种方式
xml 配置
顾名思义,就是将bean的信息配置.xml文件里,通过Spring加载文件为我们创建bean。
- 优点: 可以使用于任何场景,结构清晰,通俗易懂
- 缺点: 配置繁琐,不易维护,枯燥无味,扩展性差
Java 配置
将类的创建交给我们配置的JavcConfig类来完成,Spring只负责维护和管理,采用纯Java创建方式。其本质上就是把在XML上的配置声明转移到Java配置类中
- 优点:适用于任何场景,配置方便,因为是纯Java代码,扩展性高,十分灵活
- 缺点:由于是采用Java类的方式,声明不明显,如果大量配置,可读性比较差
举例:
- 创建一个配置类, 添加@Configuration注解声明为配置类
- 创建方法,方法上加上@bean,该方法用于创建实例并返回,该实例创建后会交给spring管理,方法名建议与实例名相同(首字母小写)。注:实例类不需要加任何注解
@Configuration
public class BeansConfig {
@Bean("userDao")
public UserDaoImpl userDao() {
return new UserDaoImpl();
}
@Bean("userService")
public UserServiceImpl userService() {
UserServiceImpl userService = new UserServiceImpl();
userService.setUserDao(userDao());
return userService;
}
}
注解配置
通过在类上加注解的方式,来声明一个类交给Spring管理,Spring会自动扫描带有@Component,@Controller,@Service,@Repository这四个注解的类,然后帮我们创建并管理,前提是需要先配置Spring的注解扫描器。
- 优点:开发便捷,通俗易懂,方便维护。
- 缺点:具有局限性,对于一些第三方资源,无法添加注解。只能采用XML或JavaConfig的方式配置
举例:
- 对类添加@Component相关的注解,比如@Controller,@Service,@Repository
- 设置ComponentScan的basePackage, 比如<context:component-scan base-package='tech.pdai.springframework'>, 或者@ComponentScan("tech.pdai.springframework")注解,或者 new AnnotationConfigApplicationContext("tech.pdai.springframework")指定扫描的basePackage.
@Service
public class UserServiceImpl {
@Autowired
private UserDaoImpl userDao;
public List<User> findUserList() {
return userDao.findUserList();
}
}
依赖注入的三种方式
这里只是常用的注入方法,其实spring IoC提供的注入方式还有很多种。
常用的注入方式主要有三种:
- 基于 field 注入(属性注入)
- 基于 setter 注入
- 基于 constructor 注入(构造器注入)
基于 field 注入(属性注入)
public class UserServiceImpl {
@Autowired
private UserDaoImpl userDao;
public UserServiceImpl() {
}
public List<User> findUserList() {
return this.userDao.findUserList();
}
public void setUserDao(UserDaoImpl userDao) {
this.userDao = userDao;
}
}
优点
属性注入最大的优点就是实现简单、使用简单,只需要给变量上添加一个注解(@Autowired),就可以在不 new 对象的情况下,直接获得注入的对象了,所以它的优点就是使用简单。
缺点
然而,属性注入虽然使用简单,但也存在着很多问题,甚至编译器 Idea 都会提醒你“不建议使用此注入方式”。
属性注入的缺点主要包含以下 3 个:
-
功能性问题:无法注入一个被final修饰的不可变的对象(在 Java 中 final 对象(不可变)要么直接赋值,要么在构造方法中赋值,所以当使用属性注入 final 对象时,它不符合 Java 中 final 的使用规范,所以就不能注入成功了)
-
依赖注入与容器本身耦合(依赖注入框架的核心思想之一就是受容器管理的类不应该去依赖容器所使用的依赖。换句话说,这个类应该是一个简单的POJO(Plain Ordinary Java Object)能够被单独实例化并且你也能为它提供它所需的依赖。)这个问题具体可以表现在:
- 你的类和依赖容器强耦合,不能在容器外使用
- 你的类不能绕过反射(例如单元测试的时候)进行实例化,必须通过依赖容器才能实例化,这更像是集成测试
-
设计原则问题:更容易违背单一设计原则。
基于 Setter 注入
public class UserServiceImpl {
private UserDaoImpl userDao;
public UserServiceImpl() {
}
public List<User> findUserList() {
return this.userDao.findUserList();
}
@Autowired
public void setUserDao(UserDaoImpl userDao) {
this.userDao = userDao;
}
}
优点
完全符合单一职责的设计原则,因为每一个 Setter 只针对一个对象
缺点
- 不能注入不可变对象(final 修饰的对象)
- 注入的对象可被修改(对象是可变的)
基于 constructor 注入(构造器注入)
构造方法注入是 Spring 官方从 4.x 之后推荐的注入方式,它的实现代码如下:
- 在XML配置方式中,是通过构造函数参数注入,比如下面的xml:
public class UserServiceImpl {
private final UserDaoImpl userDao;
@Autowired // 这里@Autowired也可以省略
public UserServiceImpl(UserDaoImpl userDaoImpl) {
this.userDao = userDaoImpl;
}
public List<User> findUserList() {
return this.userDao.findUserList();
}
}
在 Spring 4.3 及以后的版本中,如果这个类只有一个构造方法,那么这个构造方法上面也可以不写 @Autowired 注解。
在Spring4.x版本中推荐的注入方式就是这种,相较于上面的field注入方式而言,就显得有点难看,特别是当注入的依赖很多(5个以上)的时候,就会明显的发现代码显得很臃肿。
优点
构造方法注入相比于前两种注入方法,它可以注入不可变对象,并且它只会执行一次,也不存在像 Setter 注入那样,被注入的对象随时被修改的情况,它的优点有以下 4 个:
- 可注入不可变对象;
- 注入对象不会被修改;
- 注入对象会被完全初始化;
- 通用性更好。
为每个依赖属性定义对应的setter方法,需要使用大量的setter方法,这可能会让代码显得几余。对象状态不一致,由工Setter注入是通过调用一系列方法来完成依赖注入的,依赖对象可能被注入了.部分但未完成全部注入的情况,从而导致对象状态不一致。可能会增加耦合度,通过Setter注入,类的实例可能需要访问依赖类的属性或方法,增加依赖对象之间的耦合度。
Spring 开发团队的建议
Since you can mix constructor-based and setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies.
简单来说,就是
- 强制依赖就用构造器方式
- 可选、可变的依赖就用setter 注入
当然你可以在同一个类中使用这两种方法。构造器注入更适合强制性的注入旨在不变性,Setter注入更适合可变性的注入。
让我们看看Spring 这样推荐的理由,首先是基于构造方法注入,
The Spring team generally advocates constructor injection as it enables one to implement application components as immutable objects and to ensure that required dependencies are not null. Furthermore constructor-injected components are always returned to client (calling) code in a fully initialized state. As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.
Spring 团队提倡使用基于构造方法的注入,因为这样一方面可以将依赖注入到一个不可变的变量中 (注:final 修饰的变量),另一方面也可以保证这些变量的值不会是 null。此外,经过构造方法完成依赖注入的组件 (注:比如各个 service),在被调用时可以保证它们都完全准备好了。与此同时,从代码质量的角度来看,一个巨大的构造方法通常代表着出现了代码异味,这个类可能承担了过多的责任。
而对于基于 setter 的注入,他们是这么说的:
Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency. One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.
基于 setter 的注入,则只应该被用于注入非必需的依赖,同时在类中应该对这个依赖提供一个合理的默认值。如果使用 setter 注入必需的依赖,那么将会有过多的 null 检查充斥在代码中。使用 setter 注入的一个优点是,这个依赖可以很方便的被改变或者重新注入。
Spring IoC 容器依赖查找
- 根据 Bean 名称查找
- 实时查找
- 延迟查找
- 根据 Bean 类型查找
- 单个 Bean 对象
- 集合 Bean 对象
- 根据 Bean 名称 + 类型查找
- 根据 Java 注解查找
- 单个 Bean 对象
- 集合 Bean 对象
package com.evan;
import com.evan.annotation.Super;
import com.evan.entity.User;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Map;
/**
* 依赖查找示例
* ● 根据 Bean 名称查找
* ○ 实时查找
* ○ 延迟查找
* ● 根据 Bean 类型查找
* ○ 单个 Bean 对象
* ○ 集合 Bean 对象
* ● 根据 Bean 名称 + 类型查找
* ● 根据 Java 注解查找
* ○ 单个 Bean 对象
* ○ 集合 Bean 对象
*/
public class DependencyLookupDemo {
public static void main(String[] args) {
// 配置 XML 配置文件
// 启动 Spring 应用上下文
BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring.xml");
// 按照类型查找
lookupByType(beanFactory);
// 按照类型查找集合对象
lookupCollectionByType(beanFactory);
// 通过注解查找对象
lookupByAnnotationType(beanFactory);
//实时查找
lookupInRealTime(beanFactory);
//延迟查找
lookupInLazy(beanFactory);
}
private static void lookupByAnnotationType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = (Map) listableBeanFactory.getBeansWithAnnotation(Super.class);
System.out.println("查找标注 @Super 所有的 User 集合对象:" + users);
}
}
private static void lookupCollectionByType(BeanFactory beanFactory) {
if (beanFactory instanceof ListableBeanFactory) {
ListableBeanFactory listableBeanFactory = (ListableBeanFactory) beanFactory;
Map<String, User> users = listableBeanFactory.getBeansOfType(User.class);
System.out.println("查找到的所有的 User 集合对象:" + users);
}
}
private static void lookupByType(BeanFactory beanFactory) {
User user = beanFactory.getBean(User.class);
System.out.println("实时查找:" + user);
}
private static void lookupInLazy(BeanFactory beanFactory) {
ObjectFactory<User> objectFactory = (ObjectFactory<User>) beanFactory.getBean("objectFactory");
User user = objectFactory.getObject();
System.out.println("延迟查找:" + user);
}
private static void lookupInRealTime(BeanFactory beanFactory) {
User user = (User) beanFactory.getBean("user");
System.out.println("实时查找:" + user);
}
}
Spring IoC容器 依赖注入
- 根据 Bean 名称注入
- 根据 Bean 类型注入
- 单个 Bean 对象
- 集合 Bean 对象
- 注入容器內建 Bean 对象
- 注入非 Bean 对象
- 注入类型
- 实时注入
- 延迟注入
<?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:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
<bean id="user" class="com.evan.entity.User">
<constructor-arg name="name" value="evan"/>
<constructor-arg name="age" value="18"/>
</bean>
<bean id="supperUser" class="com.evan.entity.SuperUser" parent="user" primary="true">
<constructor-arg name="address" value="北京"/>
</bean>
<bean id="objectFactory" class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName" value="user"/>
</bean>
<!-- bean definitions here -->
<bean id="userRepository" class="com.evan.dao.UserRepository" autowire="byType">
<!-- 第一种手动,自动 autowire="byType" -->
<property name="users">
<util:list>
<ref bean="supperUser"/>
<ref bean="user"/>
</util:list>
</property>
</bean>
</beans>
package com.evan;
import com.evan.dao.UserRepository;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.Environment;
/**
* 依赖注入示例
* 可以发现BeanFactory不是一个普通的Bean 通过依赖注入可以获得但是又不和通过上下文获取的beanFactory相等 通过依赖查找无法获得
* 此处的beanFactory和applicationContext相等
*
* ● 根据 Bean 名称注入
* ● 根据 Bean 类型注入
* ○ 单个 Bean 对象
* ○ 集合 Bean 对象
* ● 注入容器內建 Bean 对象
* ● 注入非 Bean 对象
* ● 注入类型
* ○ 实时注入
* ○ 延迟注入
*/
public class DependencyInjectionDemo {
public static void main(String[] args) {
// 配置 XML 配置文件
// 启动 Spring 应用上下文
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("dependency-injection-context.xml");
// 依赖来源一:自定义 Bean
UserRepository userRepository = applicationContext.getBean("userRepository", UserRepository.class);
System.out.println(userRepository.getUsers());
// 依赖来源二:依赖注入(內建依赖)
System.out.println(userRepository.getBeanFactory());
//延时注入
ObjectFactory userFactory = userRepository.getObjectFactory();
System.out.println(userFactory.getObject() == applicationContext);
// 依赖来源三:容器內建 Bean
Environment environment = applicationContext.getBean(Environment.class);
System.out.println("获取 Environment 类型的 Bean:" + environment);
}
}