SpringFramework
IOC
IOC,控制反转(Inverse of Control,IOC)。在未引入IOC思想之前,在程序中需要使用其他对象时,必须使用关键字 “new” 去实例化对象。但是使用关键字 “new” 实例化的对象会和程序 强依赖/紧耦合。引入 IOC 思想之后,在运行时通过反射去创建对象,对象实例与程序之间变成了 弱依赖/松散耦合。使用关键字 “new” 主动声明的对象实例,只要编译通过,运行就一定没错;而引入了 IOC 思想后,不会指定实现类,而是由 BeanFactory 去帮助查找需要的对象。若没有找到则会抛出强制异常 “ClassCastException”。
IOC 思想就是将获取对象的方式交给了 BeanFactory 。这种控制权交给别人的思想就称为:控制反转,而根据指定的 对象名称 去获取和创建对象的过程,就称为:依赖查找(Dependency Lookuo,DL)
Spring 简述
- IOC & AOP:是 SpringFramework 的两大核心,IOC(Inverse of Control) 控制反转、AOP(Aspect Oriented Programming) 面向切面编程
- 轻量级:它的规模更小、消耗资源更少
- 一站式:覆盖企业级开发中的所有领域
- 第三方整合:SpringFramework 可以很方便的整合进其他第三方技术(如:Mybatis、Shiro等)
- 容器:SpringFramework 的底层有一个管理对象和组件的容器,由它来支撑基于 SpringFramework 构建的应用的运行
如何概述SpringFramework
SpringFramework 是一个开源的、松耦合的、分层的、可配置的一站式企业级 Java 开发框架,它的核心是 IOC 和 AOP,它可以更容易的构建出企业级 Java 应用,并且它可以根据应用开发的组件需求,整合对应的技术。
为什么使用SpringFramework
- IOC:组件之间的解耦
- AOP:切面编程可以将应用业务做统一或特定的功能增强,能实现应用于增强逻辑的解耦
- 容器与事件:管理应用中使用的组件 Bean、托管 Bean 的生命周期、事件与监听器的驱动机制
- Web、事务控制、测试、与其它技术的整合
SpringFramework 包含的模块
- beans、core、context、expression
- aop 【面向切面编程】
- jdbc 【整合 jdbc】
- orm 【整合 ORM 框架】
- tx 【事务控制】
- web 【Web 层技术】
- test 【整合测试】
- … … … …
依赖查找和依赖注入的区别
-
作用目标不同
- 依赖注入的作用目标通常是类成员
- 依赖查找的作用目标可以是方法体内,也可以是方法体外
-
实现方式不同
- 依赖注入通常借助一个上下文被动的接收
- 依赖查找通常主动使用上下文搜索
BeanFactory 和 ApplicationContext
BeanFactory:在 org.springframework.beans 包下
ApplicationContext:在 org.springframework.context 包下
上面这两个包都是 IOC 容器的基础包。BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象、ApplicationContext 是 BeanFactory 的子接口,对 BeanFactory 的功能进行了扩展,新增了:与 AOP 功能的集成、国际化处理、事件发布、应用层特定上下文(WebApplicationContext)
Feature | BeanFactory | ApplicationContext |
---|---|---|
Bean instantiation/wiring —— Bean的实例化和属性注入 | Yes | Yes |
Integrated lifecycle management —— 生命周期管理 | No | Yes |
Automatic BeanPostProcessor registration —— Bean后置处理器的支持 | No | Yes |
Automatic BeanFactoryPostProcessor registration —— BeanFactory后置处理器的支持 | No | Yes |
Convenient MessageSource access (for internalization) —— 消息转换服务(国际化) | No | Yes |
Built-in ApplicationEvent publication mechanism —— 事件发布机制(事件驱动) | No | Yes |
BeanFactory与ApplicationContext的对比
BeanFactory
接口提供了一个抽象的配置和对象的管理机制,ApplicationContext
是BeanFactory的子接口,它简化了与AOP的整合、消息机制、事件机制。以及对Web环境的扩展(WebApplicationContext等),这些在BeanFactory中是没有的
ApplicationContext注入做了以下几个扩展
-
AOP的支持(AnnotationAwareAspectJAutoProxyCreator 用于与Bean初始化只会)
-
配置元信息(BeanDefinition、Environment、注解等)
-
资源管理(Rsource管理)
-
事件驱动机制(ApplicationEvent,ApplicationListen)
-
消息与国际化(LocaleResolver)
-
Environment 抽象
ApplicationContext常用API
getBeansWithAnnotation()
- 根据指定的注解查找对于的 Bean
getBeanDefinitionNames() - 获取当前 IOC 容器中的所有 bean ,获取到的时 Bean Id
延迟查找
对于一些特殊的场景,需要依赖容器中的某些特定的 Bean ,但当它们不存在时也能使用默认 / 缺省策略来处理逻辑
SpringFramework4.3中引入了一个新的Api ObjectProvider ,对应到 ApplicationContext 中的方法就是 getBeanProvider()
getBeanProvider():获取一个 Bean 的时候,先不报错,而是给一个包装类,回头用的时候再拆开看里面有还是没有,当调用getBeanProvider().getObject() 时 bean 不存在才会抛出异常
ObjectProvider 相当于延后了 Bean 的获取时机,也延后了异常可能出现的时机
ObjectProvider 中还有一个方法:getIfAvailable() ,它可以在找不到 Bean 时返回 null 而不抛出异常
随着 SpringFramework 5.0 基于 jdk8 的发布,函数式编程也被大量用于 SpringFramework 中。ObjectProvider 中新加了几个方法,可以使编码更佳优雅。
Dog dog = dogProvider.getIfAvailable(() -> new Dog());
Or
Dog dog = dogProvider.getIfAvailable(Dog::new);
般情况下,取出的 Bean 都会马上或者间歇的用到,ObjectProvider 还提供了一个 ifAvailable 方法,可以在 Bean 存在时执行 Consumer 接口的方法:
dogProvider.ifAvailable(dog -> System.out.println(dog)); // 或者使用方法引用
注解驱动IOC与组件扫描
在 xml 驱动的 IOC 容器中,用的是 ClassPathXmlApplicationContext 来加载类路径下的 xml 驱动。对于注解配置的驱动 AnnotationConfigApplicationContext 来加载对于的注解
对比于 xml 文件作为驱动,注解驱动需要的是配置类。一个配置类就可以类似的理解为一个 xml 。配置类没有特殊的限制,只需要在类上标注一个 @Configuration 注解即可。
@Configuration
public class QuickstartConfiguration {
}
在 xml 中,咱声明 Bean 是通过
@Bean
public Person person() {
return new Person();
}
这种使用方式,可以解释为:向 IOC 容器注册一个类型为 Person ,id 为 person 的 Bean 。方法的返回值代表注册的类型,方法名代表 Bean 的 id 。当然,也可以直接在 @Bean 注解上显式的声明 Bean 的 id ,只不过在注解驱动的范畴里,它不叫 id 而是叫 name :
@Bean(name = "aaa") // 4.3.3之后可以直接写value
public Person person() {
return new Person();
}
组件注册与组件扫描
通过上面声明的方式,如果需要注册的组件特别多,那编写这些 @Bean 无疑是超多工作量,于是 SpringFramework 中给咱整了几个注解出来,可以帮咱快速注册需要的组件,这些注解被成为模式注解 ( stereotype annotation )。
一切组件注册的根源:@Component
一切组件注册的根源:@Component,如果想指定 Bean 的名称,可以直接在 @Component 中声明 value 属性即可:
@Component("aaa")
public class Person { }
如果不指定 Bean 的名称,它的默认规则是 “类名的首字母小写”(例如 Person 的默认名称是 person ,DepartmentServiceImpl 的默认名称是 departmentServiceImpl )。
组件扫描
只声明了组件,咱在写配置类时如果还是只写 @Configuration 注解,随后启动 IOC 容器,那它是感知不到有 @Component 存在的,一定会报 NoSuchBeanDefinitionException 。需要引入一个新的注解:@ComponentScan
。
@Configuration
@ComponentScan("扫描的路径")
public class ComponentScanConfiguration {
}
如果不指定扫描路径,则默认扫描本类所在包及子包下的所有 @Component 组件。
SpringFramework 为了迎合咱在进行 Web 开发时的三层架构,它额外提供了三个注解:@Controller 、@Service 、@Repository ,分别代表表现层、业务层、持久层。这三个注解的作用与 @Component 完全一致,其实它们的底层也就是 @Component :
IOC驱动方式小结
IOC容器中,有两种驱动方式,分别是:
- ClassPathXmlApplicationContext 类路径下xml驱动
- AnnotationConfigApplicationContext 注解配置驱动
-
注解驱动:
- @Configuration 配置类类似xml文件,加了该注解的类也会被视为bean注册
- @Bean 方法返回bean
- @Component 类会被注册为bean
-
组件扫描:
- 注解:@ComponentScan 组件扫描,可以指定扫描路径,默认为本类所在包及子包
-
xml:
- 注解驱动与xml驱动互通:
- xml需要开启注解配置引入注解
- 注解配置引入xml需要在配置类标注@ImportResource
依赖注入-自动注入&复杂类型注入
自动注入
xml 中的 ref 属性可以在一个 Bean 中注入另一个 Bean ,注解同样也可以这样做,它可以使用的注解有很多种
@Autowired
在 Bean 中直接在 属性 / 构造方法 / setter 方法 上标注 @Autowired 注解,IOC 容器会按照属性对应的类型,从容器中找对应类型的 Bean 赋值到对应的属性上,实现自动注入。
当我们注入一个不存在的对象时会抛出异常
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.linkedbear.spring.basic_di.d_autowired.bean.Person' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {}
异常解释:想找一个类型为 Person 的 Bean ,但一个也没找到!
如果出现这种情况下又不想让程序抛异常,就需要在 @Autowired 注解上加一个属性:required = false ,加上之后,程序不会抛出异常,而是将 bean 设置为 null
@Autowired在配置类的使用
@Autowired 不仅可以用在普通 Bean 的属性上,在配置类中,注册 @Bean 时也可以标注:
@Configuration
@ComponentScan("com.linkedbear.spring.basic_di.d_complexfield.bean")
public class InjectComplexFieldConfiguration {
@Bean
@Autowired // 高版本可不标注
public Cat cat(Person person) {
Cat cat = new Cat();
cat.setName("mimi");
cat.setPerson(person);
return cat;
}
}
由于配置类的上下文中没有 Person 的注册了(使用了 @Component 模式注解),自然也就没有 person() 方法给咱调,那就可以使用 @Autowired 注解来进行自动注入了。
多个相同类型Bean的自动注入
在容器中存在两个类型相同的 bean 实例时,程序启动时就会抛出异常
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'com.linkedbear.spring.basic_di.d_complexfield.bean.Person' available: expected single matching bean but found 2: bean名称1,bean名称2
IOC 容器发现有两个类型相同的 Bean ,它也不知道注入哪一个了,索性直接 “我选择死亡” ,就挂了。
@Qualifier:指定注入Bean的名称
@Qualifier 注解的使用目标是要注入的 Bean ,它配合 @Autowired 使用,可以显式的指定要注入哪一个 Bean :
@Autowired
@Qualifier("administrator")
private Person person;
@Primary:默认Bean
@Primary 注解的使用目标是默认被注入的 Bean ,在一个应用中,一个类型的 Bean 注册只能有一个,它配合 @Bean 使用,可以指定默认注入的 Bean :
@Bean
@Primary // 可以保证IOC容器中存在多个类型相同的Bean时,默认使用标记有 @Primary 注解的 Bean
public Person master() {
Person master = new Person();
master.setName("master");
return master;
}
除了 @Qualifier 可以注入指定名称的Bean ,使用 @Autowired 也可以实现
@Autowired
private Person administrator; // 直接在类后面指定对于的 Bean 名称即可
@Autowired注入的原理逻辑
@Autowired 首先根据对象的类型,去IOC容器中查找指定的 Bean ,如果只找到了一个,直接返回;如果找到多个类型一样的 Bean , 把属性名拿过去,跟这些 Bean 的 id 逐个对比,如果有一个相同的,直接返回;如果没有任何相同的 id 与要注入的属性名相同,去查找使用了 @Primary 的注解,找到了就直接返回,没有找到就抛出 NoUniqueBeanDefinitionException
异常。
多个相同类型Bean的全部注入
上面都是注入一个 Bean 的方式,通过两种不同的办法来保证注入的唯一性。但如果需要一下子把所有指定类型的 Bean 都注入进去应该怎么办呢?其实答案也挺简单的,注入一个用单个对象接收,注入一组对象就用集合来接收:
@Component
public class Dog {
// ......
@Autowired
private List<Person> persons;
如上就可以实现一次性把所有的 Person 都注入进来
JSR250-@Resource
@Resource 也是用来属性注入的注解,它与 @Autowired 的不同之处在于:@Autowired 是按照类型注入,@Resource 是直接按照属性名 / Bean的名称注入。
这个 @Resource 注解相当于标注 @Autowired 和 @Qualifier 了!实际开发中,@Resource 注解也是用的很多的,可以根据情况来进行选择。
JSR330-@Inject
JSR330 也提出了跟 @Autowired 一样的策略,它也是按照类型注入。不过想要用 JSR330 的规范,需要额外导入一个依赖:
<!-- jsr330 -->
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
剩下的使用方式就跟 SpringFramework 原生的 @Autowired + @Qualifier 一样了:
@Component
public class Cat {
@Inject // 等同于@Autowired
@Named("admin") // 等同于@Qualifier
private Person master;
}
@Autowired与@Inject对比
如果万一项目中没有 SpringFramework 了,那么 @Autowired 注解将失效,但 @Inject 属于 JSR 规范,不会因为一个框架失效而失去它的意义,只要导入其它支持 JSR330 的 IOC 框架,它依然能起作用。
依赖注入的注入方式
注入方式 | 被注入成员是否可变 | 是否依赖IOC框架的API | 使用场景 |
---|---|---|---|
构造器注入 | 不可变 | 否(xml、编程式注入不依赖) | 不可变的固定注入 |
参数注入 | 不可变 | 否(高版本中注解配置类中的 @Bean 方法参数注入可不标注注解) | 注解配置类中 @Bean 方法注册 bean |
属性注入 | 不可变 | 是(只能通过标注注解来侵入式注入) | 通常用于不可变的固定注入 |
setter注入 | 可变 | 否(xml、编程式注入不依赖) | 可选属性的注入 |
自动注入的注解对比
注解 | 注入方式 | 是否支持@Primary | 来源 Bean不存在时处理 | |
---|---|---|---|---|
@Autowired | 根据类型注入 | 是 | SpringFramework原生注解 | 可指定required=false来避免注入失败 |
@Resource | 根据名称注入 | 是 | JSR250规范 | 容器中不存在指定Bean会抛出异常 |
@Inject | 根据类型注入 | 是 | JSR330规范 ( 需要导jar包 ) | 容器中不存在指定Bean会抛出异常 |
@Qualifier
:如果被标注的成员/方法在根据类型注入时发现有多个相同类型的 Bean ,则会根据该注解声明的 name 寻找特定的 bean
@Primary
:如果有多个相同类型的 Bean 同时注册到 IOC 容器中,使用 “根据类型注入” 的注解时会注入标注 @Primary 注解的 bean