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

Veronica ·
更新时间:2024-09-20
· 928 次阅读

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接口,目的是传入上下文
postProcessBeforeInitialization BeanPostProcessor的预初始化方法,它是针对所有Bean的,是对所有的bean进行一个初始化之前和之后的代理
自定义的初始化之后方法 被@PostConstruct标注的方法
afterPropertiesSet 实现接口InitializingBean,它是针对单个Bean的初始化完成之后的事件
postProcessAfterInitialization BeanPostProcessor的后处事话方法,它是针对所有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

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; } }

执行结果
原创文章 17获赞 2访问量 1232 关注 私信 展开阅读全文
作者:缔曦_deacy



springboot bean ioc

需要 登录 后方可回复, 如果你还没有账号请 注册新账号