系列文章目录
文章目录
前言
上一篇介绍了spring的整体框架和主要功能,其中IOC容器是spring的核心功能,我们平时主要也是使用的IOC容器创建和管理各种bean,所以我们先从IOC容器的使用开始,逐渐展开学习
一、IOC容器的使用
1.定义bean
spring定义bean,可以通过有参、无参、工厂类来创建bean,还可以定义bean的scope,是否懒加载等等,有XML的方式和注解的方式
1.1使用XML的方式
在spring.xml文件中定义bean的信息
a.无参构造
通过Car的无参构造方法创建对象,如下:
<bean id="car" class="com.zcl.ioc.entity.Car"/>
b.有参构造
通过Car的有参构造方法创建对象,可以通过构造方法的参数名或者参数顺序来定义,如下:
<!--根据参数名-->
<bean id="car" class="com.zcl.ioc.entity.Car">
<constructor-arg name="color" value="red"/>
<constructor-arg name="brand" value="吉利"/>
</bean>
<!--根据参数顺序-->
<bean id="car" class="com.zcl.ioc.entity.Car">
<constructor-arg index=0 value="red"/>
<constructor-arg index=1 value="red"/>
</bean>
c.静态工厂方法
public class CarFactory {
public static Car createCar(){
return new Car();
}
}
<bean id="car" class="com.zcl.ioc.entity.CarFactory" factory-method="createCar"/>
d.非静态工厂方法
public class CarFactory {
public Car createCar(){
return new Car();
}
}
<!-- 创建工厂对象 -->
<bean id="carFactory" class="com.zcl.ioc.entity.CarFactory"></bean>
<!-- 调用工厂对象中的方法 -->
<bean id="car1" factory-bean="carFactory" factory-method="createCar"></bean>
e.配置bean的作用域
<bean id="car" class="com.zcl.ioc.entity.Car" scope="prototype"/>
f.使用懒加载
<bean id="car" class="com.zcl.ioc.entity.Car" lazy-init="true"/>
1.2使用注解的方式
这是基于读取配置类的形式定义Bean信息,配置类就相当于XML配置中的spring.xml文件
a.使用有参和无参构造方法:
@Configuration
public class ConstructConfig {
//无参构造
@Bean
public Person person() {
return new Person();
}
//有参构造
@Bean
public House house(Person person) {
return new House(person);
}
}
public class TestConstructBean {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(ConstructConfig.class);
House house = context.getBean(House.class);
System.out.println(house.getOwner());
}
}
打印结果为:com.zcl.ioc.bean.entity.Person@71623278
@Bean注解的方法里 return new Person()并不是真的new了一个对象,而是生成了BeanDefinition,这个BeanDefinition的factoryBeanName为配置类ConstructConfig,factoryMethodName为person,也就是说ConstructConfig这个配置类作为工厂bean,person方法为工厂方法,最终生成了Person的实例。可以理解成与xml文件里的<bean id=“” factory-bean=“” factory-method=“”/ >一样
注意: 通过@Bean的形式是使用的话, bean的默认名称是方法名,若@Bean(value=“bean的名称”),那么bean的名称是指定的。
b.配置bean的作用域
1)在不指定@Scope的情况下,所有的bean都是单实例的bean,而且是饿汉加载(容器启动实例就创建好了)
@Bean
public Person person() {
return new Person();
}
2)指定@Scope为 prototype 表示为多实例的,而且还是懒汉模式加载(IOC容器启动的时候,并不会创建对象,而是在使用的时候才会创建)
@Bean
@Scope(value = "prototype")
public Person person() {
return new Person();
}
四种scope类型
a) singleton 单实例的(默认)
b) prototype 多实例的
c) request 同一次请求
d) session 同一个会话级别
c.配置懒加载
@Bean
@Lazy
public Person person() {
return new Person();
}
2.通过@CompentScan注解来添加bean**
@CompentScan注解是通过扫描包路径下的指定类来添加Bean的
首先,创建MyController, MyService, MyDao,分别注解上@Controller, @Serivce和@Repository
@Controller
public class MyController {
}
@Configuration
@ComponentScan(basePackages = "com.xda.ioc.component")
public class BaseTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseTest.class);
Arrays.stream(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}
}
最终打印出MyController, MyService, MyDao这三个类
这里,@Controller, @Serivce和@Repository注解的类所生成的BeanDefinition与xml中<bean id=“” class=“”/ >一样,BeanDefinition的beanClass属性为@Controller注解的类的全限定类名。这样生成的BeanDefinition会通过全限定类名来生成Bean(后续介绍推断构造方法的时候会详细介绍)。这里只是与前面工厂方法产生的BeanDefinition进行区别。
另外,@ComponentScan还有几个重要的属性,excludeFilters和includeFilters
1)排除用法 excludeFilters
例如排除@Controller注解的类,和MyService.class这个类
@ComponentScan(basePackages = "com.xda.ioc.component", excludeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class) ,
@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = MyService.class)
})
@Configuration
public class ExcludeTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ExcludeTest.class);
Arrays.stream(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}
}
结果,就不扫@Controller注解和MyService
2)包含用法 includeFilters
注意,若使用包含的用法,需要useDefaultFilters属性设置为false(true表示扫描全部的)
@ComponentScan(basePackages = "com.xda.ioc.component", includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Controller.class),
@ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Service.class)
}, useDefaultFilters = false)
@Component
public class IncludeTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(IncludeTest.class);
Arrays.stream(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}
}
结果只扫描@Controller和@Service
@ComponentScan.Filter type的类型
1.注解形式的FilterType.ANNOTATION @Controller @Service @Repository @Compent
2.指定类型的 FilterType.ASSIGNABLE_TYPE @ComponentScan.Filter(type =
FilterType.ASSIGNABLE_TYPE, value = {MyService.class})
3.aspectj类型的FilterType.ASPECTJ(不常用)
4.正则表达式的FilterType.REGEX(不常用)
5.自定义的 FilterType.CUSTOM
3.通过@Import来导入bean
a.直接import类名注入
@Configuration
@Import({Car.class, Person.class})
public class BaseTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(BaseTest.class);
Arrays.stream(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}
}
**b.通过@Import 的ImportSeletor类实现组件的导入 **
导入的是全类名路径,spring会通过全类名路径来加载bean
public class MyImportSelector implements ImportSelector {
//importingClassMetadata: 可以获取导入类的注解信息
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[] {"com.xda.ioc.entity.Car"};
}
}
@Import(MyImportSelector.class)
@Configuration
public class ImportSelectorTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ImportSelectorTest.class);
Arrays.stream(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}
}
c.通过@Import的 ImportBeanDefinitionRegistrar 导入组件
直接注册BeanDefinition,而后在实例化bean阶段生成bean
public class MyImportBeanDefinitionRegistry implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
// 创建bean定义对象
RootBeanDefinition definition = new RootBeanDefinition(Car.class);
// 注册bean定义
registry.registerBeanDefinition("car", definition);
}
}
@Import(MyImportBeanDefinitionRegistry.class)
@Configuration
public class ImportBeanDefinitionRegistrarTest {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx
= new AnnotationConfigApplicationContext(ImportBeanDefinitionRegistrarTest.class);
Arrays.stream(ctx.getBeanDefinitionNames()).forEach(System.out::println);
}
}
4.使用FactoryBean来生成Bean
public class Housefactory implements FactoryBean {
@Override
public Object getObject() throws Exception {
return new House(new Person());
}
@Override
public Class<?> getObjectType() {
return null;
}
@Override
public boolean isSingleton() {
return false;
}
}
@Configuration
public class FactoryConfig {
@Bean
public Housefactory house(){
return new Housefactory();
}
}
public class TestFactoryBean {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(FactoryConfig.class);
House house = (House)context.getBean("house");
System.out.println(house.getOwner());
}
}
打印结果为:com.zcl.ioc.bean.entity.Person@4c762604
根据前面的介绍,@Bean注解的house方法会生成一个BeanDefinition,这个BeanDefinition的factoryBeanName为配置类FactoryConfig,factoryMethodName为house,从house方法返回的Housefactory对象来看,context.getBean(“house”)最后返回的应该Housefactory对象才对啊。但是因为Housefactory实现了FactoryBean,所以spring容器在判断要获取的对象实现了FactoryBean时,不会直接返回要获取的对象,而是返回该对象的getObject方法返回的对象,也就是House对象。如果想要获取Housefactory对象,需要通过context.getBean(“&house”)来获取。
这里spring容器为啥要提供这么一种特殊的方式来获取Bean对象,网上解释最多的是可以通过这种方式配置复杂的对象,但是明明通过@Bean注解就能完成啊,在@Bean注解的方法里,你想要生成多复杂的Bean都可以,比如datasource就可以用@Bean去配置。
5.Bean的依赖管理
5.1通过@Value +@PropertySource来给组件赋值
public class Person {
//通过普通的方式
@Value("司马")
private String firstName;
//spel方式来赋值
@Value("#{28-8}")
private Integer age;
//通过读取外部配置文件的值
@Value("${person.lastName}")
private String lastName;
}
@Configuration
@PropertySource(value = {"classpath:person.properties"}) //指定外部文件的位置
public class MainConfig {
@Bean
public Person person() {
return new Person();
}
}
5.2自动装配
1)@AutoWired自动注入
@Repository
public class MyDao {
}
@Service
public class MyService {
@Autowired
private MyDao myDao;
}
@AutoWired自动装配首先时按照类型进行装配,若在IOC容器中发现了多个相同类型的组件,那么就按照属性名称来进行装配。
如果我们需要指定特定的组件来进行装配,我们可以通过使用@Qualifier(“myDao”)来指定装配的组件。或者在配置类上的@Bean加上@Primary注解
2)@Resource(JSR250规范)
@Service
public class MyService {
@Resource
private MyDao myDao;
}
根据属性名称来装配
不支持@Primary 和@Qualifier
5.3@AutoWired用在构造方法和set方法上
@Component注解让Car被识别为Bean,生成BeanDefinition,而构造方法上的@Autowired和set方法上的@Autowired会形成meteData元数据,在推断构造方法时(后面的章节会讲推断构造方法),告诉spring使用这个构造方法来实例化对象,在属性注入阶段(后面章节会讲)注入Person对象
@Component
public class Car {
private Person owner;
private Brand brand;
public Car() {
}
//用在构造方法上
@Autowired
public Car(Brand brand) {
this.brand = brand;
}
//用在set方法上
@Autowired
public void setOwner(Person owner) {
this.owner = owner;
}
public Person getOwner() {
return owner;
}
public Brand getBrand() {
return brand;
}
public void setBrand(Brand brand) {
this.brand = brand;
}
}
@ComponentScan("com.zcl.ioc.bean")
public class DiConfig {
@Bean
public Person person() {
return new Person();
}
@Bean
public Brand brand() {
return new Brand();
}
}
public class TestDiBean {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(DiConfig.class);
Car car = context.getBean(Car.class);
System.out.println(car.getOwner());
System.out.println(car.getBrand());
}
}
结果打印出:
com.zcl.ioc.bean.entity.Person@80ec1f8
com.zcl.ioc.bean.entity.Brand@1445d7f
小结
不论是@Bean定义、@Component扫描,还是@Import导入,都会先生成BeanDefinition,再根据Bean的生命周期,完成实例化前、实例化、实例化后、属性注入、初始化前、初始化、初始化后,最后销毁方法。
5.Bean的生命周期管理
a.通过@Bean注解指定bean的初始化方法和bean的销毁方法
@Configuration
public class MainConfig {
//指定了bean的生命周期的初始化方法和销毁方法.
@Bean(initMethod = "init",destroyMethod = "destroy")
public Car car() {
return new Car();
}
}
针对多实例bean的话,容器启动的时候,bean是不会被创建的而是在获取bean的时候被创建,而且bean的销毁不受IOC容器的管理。
b.通过 InitializingBean和DisposableBean的二个接口实现bean的初始化以及销毁方法
@Component
public class Person implements InitializingBean, DisposableBean {
public Person() {
System.out.println("Person的构造方法");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean的destroy()方法 ");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean的 afterPropertiesSet方法");
}
}
c.通过JSR250规范 提供的注解@PostConstruct 和@PreDestory标注的方法
@Component
public class Book {
public Book() {
System.out.println("book 的构造方法");
}
@PostConstruct
public void init() {
System.out.println("book 的PostConstruct标志的方法");
}
@PreDestroy
public void destory() {
System.out.println("book 的PreDestory标注的方法");
}
}
注意:
当initMethod和destroyMethod、实现InitializingBean和DisposableBean、注解@PostConstruct 和@PreDestory这三种方式都存在,且方法名都不同时,执行顺序为:
1.注解@PostConstruct 和@PreDestory
2.实现InitializingBean和DisposableBean
3.initMethod和destroyMethod
d.通过Spring的BeanPostProcessor的 bean的后置处理器
BeanPostProcessor的执行时机:
postProcessBeforeInitialization 在init方法之前调用
postProcessAfterInitialization 在init方法之后调用
populateBean(beanName, mbd, instanceWrapper)
initializeBean {
applyBeanPostProcessorsBeforeInitialization()
invokeInitMethods {
isInitializingBean.afterPropertiesSet
自定义的init方法
}
applyBeanPostProcessorsAfterInitialization()方法
}
所以,通过实现BeanPostProcessor的postProcessBeforeInitialization和postProcessAfterInitialization方法,可以在bean执行init方法的前后对bean进行处理
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor...postProcessBeforeInitialization:"+beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor...postProcessAfterInitialization:"+beanName);
return bean;
}
}