Springboot入门——初学者对Spring Ioc技术的理解与运用,包含Bean生命周期

Spring Ioc

Spring所依赖的两个核心理念:控制反转(Ioc)、面向切面编程(AOP)
初学者可能不理解什么叫控制反转,那么我们来进一步描述一下Ioc。
Ioc是一种通过描述来生成或获取对象的技术,这里的对象当然是指java对象。
在Java中我们更多的是通过new关键字来创建对象,在Spring中,则是通过描述来创建对象。
所以我们知道了,Ioc就是用来获取java对象的东西,“控制反转”这个词先往后放放。

对象有了,我们就需要一个东西对这些对象进行存放、管理,用什么呢,没错,一个容器。
Spring把需要管理的对象叫做Spring Bean(简称Bean),管理这些Bean的容器叫做Ioc容器。
所以 Ioc容器的职责为:管理Bean的发布和获取以及Bean之间的依赖关系

Bean的装配与获取

Ioc容器需实现BeanFactory接口,这个接口里包含里许多getBean的方法以及其他的一些方法(比如isSingleton单例获取[只有一个],还是prototype原生获取[每次获取都生成一个新的])等。ApplicationContext接口通过继承上级接口进而继承了BeanFactory接口,它是我们熟知的上下文环境,没错,它的实现也是一个Ioc容器(注意spring中有好多容器呢)。

1. 通过@Bean注释

我们再来说一个基于注解的Ioc容器:AnnotationConfigApplicationContext,之所以讲他是因为Springboot装配/获取Bean的方法与它如出一辙。

public class User{
	private Long id;
	...
}

//other file
@Configuration
public class AppConfig{
	@Bean(name = "user")
	public User initUser(){
		return new User();
	}
}

Spring容器会根据@Configuration这个注解来生成Ioc容器,然后以这个容器去装配Bean。而@Bean这个注解则会将其标注的方法返回的对象装配到Ioc容器中,name则是可以自定义装配的这个对象叫什么名字,我们可以通过以下代码来查看是否装配进去了

public static void main(String[] avgs){
	//参数AppConfig.class就是我们自己写的那个容器类。
	ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
	User user = ctx.getBean(User.class);
}
2.通过@Component等注解

通过以上的代码我们了解到了,Spring通过@Bean这个注解来装配Bean对象。
当然如果都用这个来装配,实在是很麻烦,所以Spring还以此为基础设置了Bean的扫描装配
@Component、@Controller、@Service、@Repository等,这些注解都是用来定义扫描类的,后面那几个其实都是基于@Component而建立的注解,所以他们也有@Component的作用。@ComponentScan则是用来告诉Spring要扫描的路径(包)的,它还有些过滤器参数,这里不做详谈。

	@Component
	public class User{
		...
	}
	//Other file
	@Configuration
	@ComponentScan(backPackages={ "你要装配的Bean的包路径" })
	public class AppConfig{
		...
	}

所以这个过程为:Spring容器根据@Configuration生成IoC容器,然后通过@ComponentScan的描述去对应的包路径下寻找被@Component注释的类装配相应的Bean对象

3.通过DI依赖注入

所谓依赖注入:当一个Bean(a)内部包含另一个Bean(b)时,Ioc容器就需要把a所依赖的b也装配进去。@Autowired注解就是做这个用的,它是按类型找到对应的Bean然后注入的,对应这BeanFactory接口中的按类型获取Bean的方法。

@Component
public class User{
	@Autowired
	private Animal animal;
	....
}

这里有个小细节,既然他是按类型查找的,那如果Animal接口下有两个实现类Cat和Dog,并且这两个子类都已经装配到Ioc容器中了,那么Ioc容器会把哪个类注入到User中呢?对于这种注入歧义问题有两种方法可以消除:
i.@Primary 被这个注解修饰,当前类会获取优先权,也就是比如Cat被它修饰,而Dog没有,则注入时会自动选择Cat。
ii.@Quelifier 它会明确定义被注入的是哪个Bean。每个Bean都是有名字的,默认是类名且其首字母变成小写。

	@Autowired
	@Qualifier("dog")
	private Animal animal;

这种DI注入也是可以放到构造方法中的

	public User(@Autowired Animal animal){
		...
	}
4.引入xml的Bean

对于一些老旧项目,都是以xml配置的bean,那么我们怎么通过注释引入这些xml类型的bean呢?可以用注解@ImportResource,它可以引入xml文件来加载bean。
比如一个xml名为spring-other.xml
那么相应的装配代码为:

	@Configuration
	@ImportResource(value={"classpath:spring-other.xml"})
	public class AppConfig{
		...
	}
5.条件装配

在装配一些bean时,有可能会引用properties文件中的属性,如果相应的属性没有,就有可能造成bean的装配错误,应对这样的场景,使用@Conditional限制装配的条件,如果不符合条件,则不装配,这样就不会报错了。(@Value注解是用来获取application.properties中的属性值的)

@Bean
@Conditional(MyConditional.class)
public DataSource getDataSource( 
	@Value("${database.driverName}") String driver,
	....
){ ... }

//other file
public class MyConditional implements Condition{
	@Override
	public boolean matches(ConditionContext ctx,AnnotatedTypeMetadata metadata){
		Environment env = ctx.getEnvironment();
		return env.containsProperty("database.driverName");
	}
}

Bean的生命周期

Ioc容器初始化和销毁Bean的过程,也可以说是Bean被Ioc容器管理,从生到死的过程。
大致分为四个阶段:Bean定义、Bean初始化、Bean生存期、Bean销毁。

Bean定义: Spring通过@Component等注解实现资源定位,然后解析资源,将类定义的信息保存起来(注意此时仅仅是Bean的定义,并没有实例化),然后把定义信息发布到Ioc容器中,仍旧没有实例化!更没有依赖注入。

Bean的初始化:
Spring针对Bean的初始化有两种情况:

  • 默认的,接着上面的定义信息,完成后续的实例化和依赖注入
  • 仅仅在外部拿取这个Bean的时候,才去实例化,如果没有拿,那么它在Ioc容器中仅仅只是存在定义信息。@ComponentScan中有个lazyInit(默认值为false)属性,它的含义是延迟初始化,我们可以通过此属性实现该目的。

以下顺次执行

过程讲解
初始化也就是实例化
依赖注入DI
setBeanName对应于BeanNameAware接口,设置对象名
setBeanFactory对应于BeanFactoryAware接口,让Bean拥有访问Spring容器的能力
setApplicationContext需要容器实现ApplicationContext接口才会调用,它实现的是ApplicationContextAware接口,目的是传入上下文
postProcessBeforeInitializationBeanPostProcessor的预初始化方法,它是针对所有Bean的,是对所有的bean进行一个初始化之前和之后的代理
自定义的初始化之后方法被@PostConstruct标注的方法
afterPropertiesSet实现接口InitializingBean,它是针对单个Bean的初始化完成之后的事件
postProcessAfterInitializationBeanPostProcessor的后处事话方法,它是针对所有Bean的,是对所有的bean进行一个初始化之前和之后的代理
生存期
自定义的销毁方法@PreDestroy标注的方法
destroy实现接口DisposableBean

表中,只有BeanPostProcessor是针对所有Bean的,其余都是针对单个Bean的。
这样看来,Bean的声明周期就很好记了:
在bean初始化并依赖注入后,对bean进行命名并使其拥有对spring容器及上下文的访问能力。然后由于BeanPostProcessor对Bean的代理,执行了一个所有bean初始化前都要执行的方法,然后针对单个bean执行了一个被@PostConstruct修饰自定义的方法,这样初始化成功了,紧接着要执行一个在初始化成功之后的方法,最后再执行一个针对所有bean初始化之后都要执行的方法,ok,bean下生了,最后在bean死之前,可以执行一个被@PreDestroy修饰的自定义的方法,再执行一个destroy的方法,bean就死透了
(有点类似于小孩从娘胎里出来后,给他冠名,给他能用的东西,然后在他长大前做一些一般小孩子经历的事情,比如打疫苗,然后再给他安排一些个性化只有他自己独有的东西,长大后,做一些个性化独有的事情,再做一些通用的事,比如上学,然后,小孩就正式成人了,好好生存呗,最后,变老了,要准备后事了,我们可以自定义个性化的做一些生前最后的事儿,最后,下葬destroy,死透透的了…)
我在文末摘抄了一个关于bean生命周期应用实例。

Bean的作用域

在容器实现的BeanFactory接口中,我们可以看到单例(Singleton)和原型(Prototype),Spring默认使用的就是单例,我们可以手动改为其他

	@Component
	@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
	public class Test{...}

常用的作用域还包括:session 、 application、request、globalSession
例如request:同一个请求范围内获取这个Bean时,只会共用同一个Bean,第二次请求则会产生新的Bean。

Bean生命周期应用实例

包含xml形式中的init-method,此处代码引用自 https://www.cnblogs.com/grey-wolf/p/6627925.html

<bean class="com.ckl.springbeanlifecycle.DemoController" init-method="init" destroy-method="cleanUp"></bean>
package com.ckl.springbeanlifecycle;

import com.ckl.springbeanlifecycle.service.IDemoService;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

/**
 * desc:
 *
 * @author : caokunliang
 * creat_date: 2019/7/20 0020
 * creat_time: 18:45
 **/
public class DemoController implements InitializingBean,DisposableBean {

    @Autowired
    private IDemoService iDemoService;
    
    public DemoController() {
        System.out.println();
        System.out.println("constructor ");
        System.out.println( "属性:" + iDemoService);
        System.out.println();
    }

    @Override
    public void destroy() throws Exception {
        System.out.println();
        System.out.println("implements DisposableBean interface");
        System.out.println( "属性iDemoService已注入:" + (iDemoService != null));
        System.out.println( "属性iDemoService已注入:" + iDemoService);
        System.out.println();
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println();
        System.out.println("afterPropertiesSet interface");
        System.out.println( "属性iDemoService已注入:" + (iDemoService != null));
        System.out.println( "属性iDemoService已注入:" + iDemoService);
        System.out.println();
    }

    @PostConstruct
    public void  postConstruct(){
        System.out.println();
        System.out.println("@PostConstrut....");
        System.out.println( "属性iDemoService已注入:" + (iDemoService != null));
        System.out.println( "属性iDemoService已注入:" + iDemoService);
        System.out.println();
    }

    @PreDestroy
    public void  preDestroy(){
        System.out.println();
        System.out.println("@PreDestroy.....");
        System.out.println( "属性iDemoService已注入:" + iDemoService);
        System.out.println();
    }

    public void init(){
        System.out.println();
        System.out.println("init-method by xml 配置文件");
        System.out.println( "属性iDemoService已注入:" + (iDemoService != null));
        System.out.println();
    }
    public void  cleanUp(){
        System.out.println();
        System.out.println("destroy-method by xml 配置文件");
        System.out.println( "属性iDemoService已注入:" + iDemoService);
        System.out.println();
    }
}
package com.ckl.springbeanlifecycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

import java.lang.reflect.Field;

/**
 * desc:
 *
 * @author : caokunliang
 * creat_date: 2019/7/20 0020
 * creat_time: 18:52
 **/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof DemoController){
            System.out.println();
            System.out.println("BeanPostProcessor:" + "postProcessBeforeInitialization");
            Field field = null;
            try {
                field = bean.getClass().getDeclaredField("iDemoService");
                field.setAccessible(true);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
            try {
                Object o = field.get(bean);
                System.out.println( "属性iDemoService已注入:" + (o != null));
                System.out.println( "属性iDemoService已注入:" + o);
                System.out.println();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof DemoController){
            System.out.println();
            System.out.println("BeanPostProcessor:" + "postProcessAfterInitialization");
            Field field = null;
            try {
                field = bean.getClass().getDeclaredField("iDemoService");
                field.setAccessible(true);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
            try {
                Object o = field.get(bean);
                System.out.println( "属性iDemoService已注入:" + (o != null));
                System.out.println( "属性iDemoService已注入:" + o);
                System.out.println();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return bean;
    }
}

执行结果
在这里插入图片描述在这里插入图片描述

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

缔曦_deacy

码字不易,请多支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值