一、Spring IoC 和 DI
IoC(Inversion of Control):控制反转,是一种设计思想,即将对象的控制权交由Spring进行管理,在以往程序中,当我们需要使用某一个对象时,是由我们自己在程序中通过 new 进行创建的,而在 Spring 中,Spring 提供了 IoC 容器来对对象的创建进行管理。
DI(Dependency Injection):提及 IoC,就必须提到另一个概念依赖注入,在以往的程序中,对象的属性以及对象之间的依赖是由我们自己进行控制和注入的,而 IoC 容器则会帮我们查找,并注入属性以及依赖对象。
IoC 容器:Spring既然要对对象进行统一管理,那么就必须要有一个地方去存储这些对象,而这个地方就被称之为 Spring 的 IoC 容器。
- BeanFactory:IOC 容器基本实现,是 Spring 内部的使用接口,不提供开发人员进行使用。
- ApplicationContext:BeanFactory 接口的子接口,提供更多更强大的功能,一般由开发人员进行使用。
二、通过 XML 配置文件注入 Bean
pom文件
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
创建实体类 Person
package org.example.pojo;
public class Person {
private String name;
private String age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public Person() {
}
public Person(String name, String age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
}
Spring 配置文件:
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="person" class="org.example.pojo.Person">
<property name="name" value="zhangsan"></property>
<property name="age" value="20"></property>
</bean>
</beans>
测试方法:
package org.example.test;
import org.example.pojo.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class XmlMainTest {
public static void main(String[] args) {
ApplicationContext ac = new ClassPathXmlApplicationContext("spring-beans.xml");
Person person = (Person) ac.getBean("person");
System.out.println(person);
}
}
测试结果如下:
三、通过注解方式注入 Bean
定义配置类
package org.example.config;
import org.example.pojo.Person;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MainConfig {
@Bean
public Person person(){
return new Person("lis", "25");
}
}
测试方法
package org.example.test;
import org.example.config.MainConfig;
import org.example.pojo.Person;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class AnnotationMainTest {
public static void main(String[] args) {
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
Person person = (Person) ac.getBean("person");
System.out.println(person);
}
}
测试结果:
四、@Scope 注解设置组件作用域
4.1 @Scope 注解介绍
@Scope 注解需与 @Bean 注解配合使用,其作用与 xml 配置文件中 bean 标签的属性 scope 一样。
<bean id="person" class="org.example.pojo.Person" scope="singleton"></bean>
@Scope 注解:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
// 与 scopeName 属性相同,作用域类型
@AliasFor("scopeName")
String value() default "";
// 作用域名,同 value,共四种类型
@AliasFor("value")
String scopeName() default "";
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
}
@Scope 注解的 value 属性共有四种类型,分别为:
- ConfigurableBeanFactory.SCOPE_PROTOTYPE, 多实例
- ConfigurableBeanFactory.SCOPE_SINGLETON, 单实例(默认值)
- org.springframework.web.context.WebApplicationContext.SCOPE_REQUEST, 同一次请求创建一个实例
- org.springframework.web.context.WebApplicationContext.SCOPE_SESSIO, 同一个session创建一个实例
4.2 具体使用
配置类:
package org.example.config;
import org.example.pojo.Person;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Controller;
@Configuration
@ComponentScan(value = "org.example")
public class MainConfig {
@Scope
@Bean("person")
public Person person(){
System.out.println("向容器中添加Person.......");
return new Person("lis", "25");
}
}
测试方法:
@Test
public void testComponentScan(){
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);
}
测试结果:
这说明单实例 bean,在 IoC 容器启动时就会调用方法创建对象并将对象注入到 IoC 容器中。
如果我们多次从 IoC 容器中获取单实例 bean,那么获取到的 bean 实际上是同一个,测试方法如下:
@Test
public void testScope(){
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig2.class);
Object person1 = ac.getBean("person");
Object person2 = ac.getBean("person");
System.out.println(person1 == person2);
}
测试结果如下,可以看出两次获取到的 Person 对象,实际上是同一个,由于 IoC 容器启动时就会调用方法创建对象并将对象注入到 IoC 容器中,所以第二次获取 Person 的时候,其实是直接从 IoC 容器中获取的,所以控制台只输出了一次”向容器中添加Person…“,比较结果也返回了true。
我们来看下多实例情况下,bean 的获取情况,将注解 @Scope 的 value 属性改为 ConfigurableBeanFactory.SCOPE_PROTOTYPE,配置类代码如下:
package org.example.config;
import org.example.pojo.Person;
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan(value = "org.example")
public class MainConfig2 {
@Scope("prototype")
@Bean("person")
public Person person(){
System.out.println("向容器中添加Person.......");
return new Person("lis", "25");
}
}
测试代码:
@Test
public void testScope(){
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("容器创建完成。。。。。。。。。。。。");
Object person1 = ac.getBean("person");
Object person2 = ac.getBean("person");
System.out.println(person1 == person2);
}
测试结果:
分析:
可以看出,IoC 容器启动后并不会去调用方法创建对象放在容器中,而是每次在获取的时候才回去创建。
五、@Lazy 懒加载注解
先来看下懒加载 @Lazy 注解的详细定义:
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Lazy {
// 是否开启懒加载功能
boolean value() default true;
}
该注解可以加载 类、方法、构造器、参数以及属性上。只有一个 value 属性,表示是否开启懒加载,默认为 true,即默认是开启懒加载的。
验证懒加载,配置类如下:
package org.example.config;
import org.example.pojo.Person;
import org.springframework.context.annotation.*;
@Configuration
@ComponentScan(value = "org.example")
public class MainConfig2 {
@Lazy
@Bean("person")
public Person person(){
System.out.println("向容器中添加Person.......");
return new Person("lis", "25");
}
}
测试方法:
@Test
public void testScope(){
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig2.class);
}
测试结果:
可以看出,此时懒加载已经起到了效果,IoC 容器启动时,并未创建 Person 实例,如果创建成功,则会在控制台输出”向容器中添加Person…“
修改下测试方法,如下:
@Test
public void testScope(){
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig2.class);
System.out.println("容器创建完成。。。。。。。。。。。。");
Object person1 = ac.getBean("person");
Object person2 = ac.getBean("person");
System.out.println(person1 == person2);
}
测试结果:
当我们第一次获取 Bean 时,容器创建了对象,并将该对象存放到了容器中。
总结
- @Configuration:该注解用于类上,表示该类为一个配置类,对应 Spring 的 xml 配置文件。
- @Bean:可用在方法上,表示向容器中注册一个 Bean,类型为返回值类型,Bean 名默认为方法名。对应 xml 配置文件中的 <bean></bean>
<bean id="person" class="org.example.pojo.Person"></bean>
- 通过注解 @Bean 注册实例时,在测试代码中,如果要获取容器,可以通过构造方法
public AnnotationConfigApplicationContext(Class<?>... componentClasses)
,将配置类传入即可,可传入多个配置类。
ApplicationContext ac = new AnnotationConfigApplicationContext(MainConfig.class);