1.快速入门-IOC-DL(依赖查找)
1.1 引入依赖
对于快速入门来说,我们只需要引入一个依赖即可:spring-context
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
1.2 创建配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 申明bean的id,和bean的class-->
<bean id="person" class="com.linkedbear.spring.basic_dl.a_quickstart_byname.bean.Person"></bean>
</beans>
1.3 申明一个普通类
创建Person ``
无需填充任何内容, 在1.2中配置,指向此类的声明
1.4 创建启动类
有了配置文件,就需要创建启动类,读取配置文件,加载配置文件刀spring的IOC中
public class QuickstartByNameApplication {
public static void main(String[] args) {
// 采用ClassPathXmlApplicationContext来加载配置文件刀到BeanFactory 中
// BeanFactory 是一个接口,采用借口接收,满足多台思想
BeanFactory factory = new ClassPathXmlApplicationContext("basic_dl/quickstart-byname.xml");
// 从factory中获取Bean 通过Id名称获取
Person person = (Person) factory.getBean("person");
// 打印类的全限定名+内存地址
System.out.println(person);
}
}
运行main方法后悔打印person的内存地址, 可以发现,我们使用spring创建了Person的实例,不再是使用new的方式创建类的实例,这就是依赖查找
2.依赖查找
1.1 byName
1.2 byType
配置文件修改
<bean class="com.linkedbear.spring.basic_dl.b_bytype.bean.Person"></bean>
启动类修改
public class QuickstartByTypeApplication {
public static void main(String[] args) {
BeanFactory factory = new ClassPathXmlApplicationContext("basic_dl/quickstart-bytype.xml");
Person person = factory.getBean(Person.class);
System.out.println(person);
}
}
1.3 ofType —一次性查找多个
声明一个 DemoDao ,并声明 3 种对应的实现类,分别模拟操作 MySQL 、Oracle 、Postgres 数据库的实现类:
对应的配置类,也把这几个 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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="demoMySQLDao" class="com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoMySQLDao"/>
<bean id="demoOracleDao" class="com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoOracleDao"/>
<bean id="demoPostgreDao" class="com.linkedbear.spring.basic_dl.c_oftype.dao.impl.DemoPostgresDao"/>
</beans>
启动类
public class OfTypeApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-oftype.xml");
Map<String, DemoDao> beans = ctx.getBeansOfType(DemoDao.class);
beans.forEach((beanName, bean) -> {
System.out.println(beanName + " : " + bean.toString());
});
}
}
1.4 通过注解查找
声明Bean+注解+配置文件
新建一个包 d_withanno ,并在里面声明一个注解:@Color 。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Color {
}
之后,创建几个类,以及运行 main 方法的启动类:
启动类
public class WithAnnoApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-withanno.xml");
Map<String, Object> beans = ctx.getBeansWithAnnotation(Color.class);
beans.forEach((beanName, bean) -> {
System.out.println(beanName + " : " + bean.toString());
});
}
}
运行 main 方法,可以发现控制台只打印了 Black 和 Red ,证明成功取出。
black : com.linkedbear.spring.basic_dl.d_withanno.bean.Black@553f17c red : com.linkedbear.spring.basic_dl.d_withanno.bean.Red@6c3708b3
1.5 获取IOC容器中所有的Bean
方法: getBeanDefinitionNames
public class BeannamesApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-withanno.xml");
String[] beanNames = ctx.getBeanDefinitionNames();
// 利用jdk8的Stream快速编写打印方法
Stream.of(beanNames).forEach(System.out::println);
}
}
1.6 延迟查找
对于一些特殊的场景,需要依赖容器中的某些特定的 Bean ,但当它们不存在时也能使用默认 / 缺省策略来处理逻辑。这个时候,使用上面已经学过的方式倒是可以实现,但编码可能会不很优雅。
1.6.1 使用现有方案实现Bean缺失时的缺省加载
当获取不到对应的Bean的时候报错 NoSuchBeanDefinitionException 在cathc中去创建这个Bean
public class ImmediatlyLookupApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-lazylookup.xml");
Cat cat = ctx.getBean(Cat.class);
System.out.println(cat);
Dog dog;
try {
dog = ctx.getBean(Dog.class);
} catch (NoSuchBeanDefinitionException e) {
// 找不到Dog时手动创建
dog = new Dog();
}
System.out.println(dog);
}
}
既然作为一个容器,能获取自然就能有检查,ApplicationContext 中有一个方法就可以专门用来检查容器中是否有指定的 Bean :containsBean
Dog dog = ctx.containsBean("dog") ? (Dog) ctx.getBean("dog") : new Dog();
但注意,这个 containsBean 方法只能传 bean 的 id ,不能查类型,所以虽然可以改良前面的方案,但还是有问题:如果 Bean 的名不叫 dog ,叫 wangwang ,那这个方法岂不是废了?所以这个方案还是不够好,需要改良。
1.6.2 延迟查找
如果能有一种机制,我想获取一个 Bean 的时候,你可以先不给我报错,先给我一个包装让我拿着,回头我自己用的时候再拆开决定里面有还是没有,这样是不是就省去了 IOC 容器报错的麻烦事了呢?在 SpringFramework 4.3 中引入了一个新的 API :ObjectProvider ,它可以实现延迟查找。
public class LazyLookupApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("basic_dl/quickstart-lazylookup.xml");
Cat cat = ctx.getBean(Cat.class);
System.out.println(cat);
// 下面的代码会报Bean没有定义 NoSuchBeanDefinitionException
// Dog dog = ctx.getBean(Dog.class);
// 这一行代码不会报错
ObjectProvider<Dog> dogProvider = ctx.getBeanProvider(Dog.class);
}
}
可以发现,ApplicationContext 中有一个方法叫 getBeanProvider ,它就是返回上面说的那个“包装”。如果直接 getBean ,那如果容器中没有对应的 Bean ,就会报 NoSuchBeanDefinitionException;如果使用这种方式,运行 main 方法后发现并没有报错,只有调用 dogProvider 的 getObject ,真正要取包装里面的 Bean 时,才会报异常。所以总结下来,ObjectProvider 相当于延后了 Bean 的获取时机,也延后了异常可能出现的时机。
但是,上面的问题还没有被解决呀,调用 getObject 方法还是会报异常,那下面咱就继续研究 ObjectProvider 的其他一些方法。
ObjectProvider 中还有一个方法:getIfAvailable ,它可以在找不到 Bean 时返回 null 而不抛出异常。使用这个方法,就可以避免上面的问题了。改良之后的代码如下:
有三种写法:
Dog dog = dogProvider.getIfAvailable();
if (dog == null) {
dog = new Dog();
}
Dog dog = dogProvider.getIfAvailable(() -> new Dog());
Dog dog = dogProvider.getIfAvailable(Dog::new);
3.依赖注入
通过上面创建的Bean实例都是不携带任何属性的,如果我们创建的Bean需要预设一些属性,就需要采用IOC的依赖注入
1.1配置文件实现简单的属性注入
声明两个类:Person 与 Cat ,形成“猫需要依赖人”的场景:
public class Person {
private String name;
private Integer age;
// getter and setter ......
}
public class Cat {
private String name;
private Person master;
// getter and setter ......
}
之后,咱在 resources 目录下新建 basic_di 文件夹,并声明配置文件 inject-set.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Person">
<property name="name" value="test-person-byset"/>
<property name="age" value="18"/>
</bean>
<bean id="cat" class="com.linkedbear.spring.basic_di.a_quickstart_set.bean.Cat">
<property name="name" value="test-cat"/>
<!--ref 标签可以关联 bean-->
<property name="master" ref="person"/>
</bean>
</beans>
编写启动类
public class QuickstartInjectBySetXmlApplication {
public static void main(String[] args) throws Exception {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("basic_di/inject-set.xml");
Person person = beanFactory.getBean(Person.class);
System.out.println(person);
Cat cat = beanFactory.getBean(Cat.class);
System.out.println(cat);
}
}
1.2 setter属性注入
@Bean
public Person person() {
Person person = new Person();
person.setName("test-person-anno-byset");
person.setAge(18);
return person;
}
1.3 构造器注入
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
1.4 通过@Value 注解从配置文件读取
通过@Value 或者@ConfigurationProperties 从配置文件读取参数
1.5 在一个bean中注入另一个bean
1.直接在属性上使用@Autowired
@Component
public class Dog {
// ......
@Autowired
private Person person;
2.在构造方法上使用
@Component
public class Dog {
// ......
private Person person;
@Autowired
public Dog(Person person) {
this.person = person;
}
3.在setter上使用
@Component
public class Dog {
// ......
private Person person;
@Autowired
public void setPerson(Person person) {
this.person = person;
}
4.使用@Autowired 注入不存在的Bean
简单概括这个异常,就是说本来想找一个类型为 Person 的 Bean ,但一个也没找到!那必然没找到啊,@Component 注解被注释掉了,自然就不会注册了。如果出现这种情况下又不想让程序抛异常,就需要在 @Autowired 注解上加一个属性:required = false 。
@Autowired(required = false)
private Person person;
- 使用@Component 向容器中注入Bean的时候指定名称
@Component(“administrator”)
- 使用 @Autowired 注入 指定名称的Bean
7.@Primary:默认Bean@Autowired
@Qualifier("administrator")
private Person person;
@Primary 注解的使用目标是被注入的 Bean ,在一个应用中,一个类型的 Bean 注册只能有一个,它配合 @Bean 使用,可以指定默认注入的 Bean :
重新运行测试类,发现 Cat 中注入的 Person 是 master ,Dog 中注入的还是 administrator ,可见 @Qualifier 不受 @Primary 的干扰。@Bean
@Primary
public Person master() {
Person master = new Person();
master.setName("master");
return master;
}
同样的,在 xml 中可以指定标签中的 primary 属性为 true ,跟上面标注 @Primary 注解是一样的。 1.6@Autowired注入的原理逻辑
先拿属性对应的类型,去 IOC 容器中找 Bean ,如果找到了一个,直接返回;如果找到多个类型一样的 Bean , 把属性名拿过去,跟这些 Bean 的 id 逐个对比,如果有一个相同的,直接返回;如果没有任何相同的 id 与要注入的属性名相同,则会抛出
4.注册驱动与组建扫描
在使用XML驱动的IOC的容器中,使用的是ClassPathXmlApplicationContext 对应的的类路径下的xml驱动, 还可以使用注解加载驱动的方式,
4.1 注解驱动IOC的依赖查找
@Configuration
public class QuickstartConfiguration {
// 对应配置文件中的bean标签
@Bean(name = "aaa") // 4.3.3之后可以直接写value
public Person person() {
return new Person();
}
}
这种使用方式,可以解释为:向 IOC 容器注册一个类型为 Person ,id 为 person 的 Bean 。方法的返回值代表注册的类型,方法名代表 Bean 的 id 。当然,也可以直接在 @Bean 注解上显式的声明 Bean 的 id ,只不过在注解驱动的范畴里,它不叫 id 而是叫 name :
启动类:
public class AnnotationConfigApplication {
public static void main(String[] args) throws Exception {
ApplicationContext ctx = new AnnotationConfigApplicationContext(QuickstartConfiguration.class);
Person person = ctx.getBean(Person.class);
System.out.println(person);
}
}
4.2注解IOC容器的其他用法
翻看 AnnotationConfigApplicationContext 的构造方法,可以发现它还有一个方法,是传入一组 basePackage ,翻译过来是 “根包” 的意思,它又是什么意思呢?这就涉及到下面的概念了:组件注册与扫描。
4.3组建注册与组建扫描
上面声明的方式,如果需要注册的组件特别多,那编写这些 @Bean 无疑是超多工作量,于是 SpringFramework 中给咱整了几个注解出来,可以帮咱快速注册需要的组件,这些注解被成为模式注解 ( stereotype annotation )。
在类上标注 @Component 注解,即代表该类会被注册到 IOC 容器中作为一个 Bean 。
4.3.1一切组件注册的根源:@Component
@Component
public class Person {
}
如果不指定 Bean 的名称,它的默认规则是 “类名的首字母小写”(例如 Person 的默认名称是 person ,DepartmentServiceImpl 的默认名称是 departmentServiceImpl )。
4.3.2 组建扫描 @ComponentScan
只声明了组件,咱在写配置类时如果还是只写 @Configuration 注解,随后启动 IOC 容器,那它是感知不到有 @Component 存在的,一定会报 NoSuchBeanDefinitionException 。
为了解决这个问题,咱需要引入一个新的注解:@ComponentScan 。
在配置类上额外标注一个 @ComponentScan ,并指定要扫描的路径,它就可以扫描指定路径包及子包下的所有 @Component 组件:
@Configuration
@ComponentScan("com.linkedbear.spring.annotation.c_scan.bean")
public class ComponentScanConfiguration {
}
如果不指定扫描路径,则默认扫描本类所在包及子包下的所有 @Component 组件。
上面的方法等同于如下
ApplicationContext ctx = new AnnotationConfigApplicationContext("com.linkedbear.spring.annotation.c_scan.bean");
1.5 给Bean注入另一个Bean
直接在属性上使用@Autowired
@Component
public class Dog {
// ......
@Autowired
private Person person;
}
在构造方法上使用
@Component
public class Dog {
// ......
private Person person;
@Autowired
public Dog(Person person) {
this.person = person;
}
注: 小结和思考
1.依赖查找与依赖注入的对比
- 作用目标不同
- 依赖注入的作用目标通常是类成员
- 依赖查找的作用目标可以是方法体内,也可以是方法体外
- 实现方式不同
3. BeanFactory与ApplicationContext
BeanFactory 接口提供了一种高级配置机制,能够管理任何类型的对象。ApplicationContext 是 BeanFactory 的子接口。它增加了:
- 与 SpringFramework 的 AOP 功能轻松集成
- 消息资源处理(用于国际化)
- 事件发布
- 应用层特定的上下文,例如 Web 应用程序中使用的 WebApplicationContext
- 对比了 BeanFactory 与 ApplicationContext 的不同指标: | 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 的子接口,它简化了与 AOP 的整合、消息机制、事件机制,以及对 Web 环境的扩展( WebApplicationContext 等),BeanFactory 是没有这些扩展的。
ApplicationContext 主要扩展了以下功能:
- AOP 的支持( AnnotationAwareAspectJAutoProxyCreator 作用于 Bean 的初始化之后 )
- 配置元信息( BeanDefinition 、Environment 、注解等 )
- 资源管理( Resource 抽象 )
- 事件驱动机制( ApplicationEvent 、ApplicationListener )
- 消息与国际化( LocaleResolver )
- Environment 抽象