Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,IOC意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。如何理解好IOC呢?理解好IOC的关键是要明确“谁控制谁,控制什么,为何是反转(有反转就应该有正转了),哪些方面反转了”,那我们来深入分析一下:
●谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IOC是有专门一个容器来创建这些对象,即由IOC容器来控制对象的创建;谁控制谁?当然是IOC容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
●为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。
2、DI:依赖注入IOD的另一种表述方式:即组件以一些预先定义好的方式(入setter方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
3、IOC容器在Spring中的实现3.1、在通过IOC容器读取Bean的实例之前,需要将IOC容器本身实例化。
Spring提供了IOC容器的两种实现方式
3.1.1、BeanFactory :IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是给开发人员使用的。
3.1.2、ApplicationContext:BeanFactory 的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是BeanFactory 。
ApplicationContext高级特性:
1、与 Spring 的 AOP 功能轻松集成
2、消息资源处理(用于国际化)
3、Event publication
4、特定于应用程序层的上下文,例如用于 Web 应用程序的WebApplicationContext。
ApplicationContext的主要实现类
ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。ConfigurableApplicationContext 是ApplicationContext的子接口,包含一些扩展方法refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。WebApplicationContext 专为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作 4、IOC配置的三种方式 xml 配置
顾名思义,就是将bean的信息配置.xml文件里,通过Spring加载文件为我们创建bean。这种方式出现很多早前的SSM项目中,将第三方类库或者一些配置工具类都以这种方式进行配置,主要原因是由于第三方类不支持Spring注解。
优点: 可以使用于任何场景,结构清晰,通俗易懂
缺点: 配置繁琐,不易维护,枯燥无味,扩展性差
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, 比如
@Service
public class UserServiceImpl {
@Autowired
private UserDaoImpl userDao;
public List findUserList() {
return userDao.findUserList();
}
}
5、依赖注入的三种方式
setter方式
在XML配置方式中,property都是setter方式注入,比如下面的xml:
本质上包含两步: 第一步,需要new UserServiceImpl()创建对象, 所以需要默认构造函数 第二步,调用setUserDao()函数注入userDao的值, 所以需要setUserDao()函数 所以对应的service类是这样的:
public class UserServiceImpl {
private UserDaoImpl userDao;
public UserServiceImpl() {
}
public List findUserList() {
return this.userDao.findUserList();
}
public void setUserDao(UserDaoImpl userDao) {
this.userDao = userDao;
}
}
在注解和Java配置方式下
public class UserServiceImpl {
private UserDaoImpl userDao;
public List findUserList() {
return this.userDao.findUserList();
}
@Autowired
public void setUserDao(UserDaoImpl userDao) {
this.userDao = userDao;
}
}
构造函数
在XML配置方式中,
本质上是new UserServiceImpl(userDao)创建对象, 所以对应的service类是这样的:
public class UserServiceImpl {
private final UserDaoImpl userDao;
public UserServiceImpl(UserDaoImpl userDaoImpl) {
this.userDao = userDaoImpl;
}
public List findUserList() {
return this.userDao.findUserList();
}
}
在注解和Java配置方式下
@Service
public class UserServiceImpl {
private final UserDaoImpl userDao;
@Autowired // 这里@Autowired也可以省略
public UserServiceImpl(final UserDaoImpl userDaoImpl) {
this.userDao = userDaoImpl;
}
public List findUserList() {
return this.userDao.findUserList();
}
}
注解注入
以@Autowired(自动注入)注解注入为例,修饰符有三个属性:Constructor,byType,byName。默认按照byType注入。
constructor:通过构造方法进行自动注入,spring会匹配与构造方法参数类型一致的bean进行注入,如果有一个多参数的构造方法,一个只有一个参数的构造方法,在容器中查找到多个匹配多参数构造方法的bean,那么spring会优先将bean注入到多参数的构造方法中。
byName:被注入bean的id名必须与set方法后半截匹配,并且id名称的第一个单词首字母必须小写,这一点与手动set注入有点不同。
byType:查找所有的set方法,将符合符合参数类型的bean注入。
@Service
public class UserServiceImpl {
@Autowired
private UserDaoImpl userDao;
public List findUserList() {
return userDao.findUserList();
}
}
6、为什么推荐构造器注入方式
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.
简单翻译:构造器注入的方式能够保证注入的组件不可变,并且确保需要的依赖不为空。此外,构造器注入的依赖总是能够在返回客户端(组件)代码的时候保证完全初始化的状态。
依赖不可变:其实说的就是final关键字依赖不为空:省去了我们对其检查,当要实例化实例的时候,由于自己实现了有参数的构造函数,所以不会调用默认的构造函数,那么就需要Spring容器传入所需要的参数,所以就两种情况:1.有该类型的参数,传入,OK。2.无该类型的参数,报错。完全初始化的状态:向构造器传参之前,要确保注入的内容不为空,那么肯定要调用依赖组件的构造方法完成实例化。而在Java类加载实例化的过程中,构造方法是最后的一步(之前如果有父类先初始化父类,然后自己的成员变量,最后才是构造方法),所以返回来的都是初始化之后的状态。
所以通常是这样的
@Service
public class UserServiceImpl {
private final UserDaoImpl userDao;
public UserServiceImpl(final UserDaoImpl userDaoImpl) {
this.userDao = userDaoImpl;
}
}
如果使用setter注入,缺点显而易见,对于IOC容器以外的环境,除了使用反射来提供它需要的依赖之外,无法复用该实现类。而且将一直是个潜在的隐患,因为你不调用将一直无法发现NPE的存在。
// 这里只是模拟一下,正常来说我们只会暴露接口给客户端,不会暴露实现。 UserServiceImpl userService = new UserServiceImpl(); userService.findUserList(); // -> NullPointerException, 潜在的隐患
循环依赖的问题:使用field注入可能会导致循环依赖,即A里面注入B,B里面又注入A:
public class A {
@Autowired
private B b;
}
public class B {
@Autowired
private A a;
}
如果使用构造器注入,在spring项目启动的时候,就会抛出:BeanCurrentlyInCreationException:Requested bean is currently in creation: Is there an unresolvable circular reference?从而提醒你避免循环依赖,如果是field注入的话,启动的时候不会报错,在使用那个bean的时候才会报错。
7、使用构造器注入方式时注入了太多的类导致Bad Smell怎么办?比如当你一个Controller中注入了太多的Service类,会给你提示相关告警
对于这个问题,说明你的类当中有太多的责任,那么你要好好想一想是不是自己违反了类的单一性职责原则,从而导致有这么多的依赖要注入。
@Autowired
1、@Autowired是Spring自带的注解,通过AutowiredAnnotationBeanPostProcessor 类实现的依赖注入
2、@Autowired可以作用在CONSTRUCTOR、METHOD、PARAMETER、FIELD、ANNOTATION_TYPE
3、@Autowired默认是根据类型(byType )进行自动装配的
4、如果有多个类型一样的Bean候选者,需要指定按照名称(byName )进行装配,则需要配合@Qualifier。 指定名称后,如果Spring IOC容器中没有对应的组件bean抛出NoSuchBeanDefinitionException。也可以将@Autowired中required配置为false,如果配置为false之后,当没有找到相应bean的时候,系统不会抛异常
@Resource
1、@Resource是JSR250规范的实现,在javax.annotation包下
2、@Resource可以作用TYPE、FIELD、METHOD上
3、@Resource是默认根据属性名称进行自动装配的,如果有多个类型一样的Bean候选者,则可以通过name进行指定进行注入
@Inject
1、@Inject是JSR330 (Dependency Injection for Java)中的规范,需要导入javax.inject.Inject jar包 ,才能实现注入
2、@Inject可以作用CONSTRUCTOR、METHOD、FIELD上
3、@Inject是根据类型进行自动装配的,如果需要按名称进行装配,则需要配合@Named;
@Autowired、@Resource和@Inject注解的区别(最详细)
IOC体系结构 BeanDefinitionIOC容器管理了我们定义的各种Bean对象及其相互的关系,Bean对象在Spring实现中是以BeanDefinition来描述的。



