【spring】spring学习系列之二:spring IOC容器的基本使用

系列文章目录


前言

上一篇介绍了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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值