Spring-IOC专题

IOC专题

1、IOC是什么?

IoC也称为依赖项注入(DI)。这是一个对象仅通过构造函数参数、工厂方法的参数或对象实例构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即使用的其他对象)的过程。然后容器在创建bean时注入这些依赖项。这个过程基本上是bean本身的逆过程(因此称为控制反转),通过使用类的直接构造或服务定位器模式等机制来控制其依赖项的实例化或位置。

2、IOC解决什么问题?

通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦,降低程序间的耦合关系

3、IOC如何实现?

参考链接:https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/core.html#beans

码云代码库:

https://gitee.com/chenscript/spring_ioc_aop_mvc_learning.git

注解一:helloworld(@Configuration,@Bean)

1.先看main方法

public static void main(String[] args) {
		AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
		context.register(MyConfiguration.class);
		context.refresh();
		MyService bean = context.getBean(MyService.class);
		String item = bean.getItem();
		System.out.println(item);

使用AnnotationConfigApplicationContext()获取上下文,在注入配置,后再刷新配置,相当于重启IOC容器。
2.看看配置类是怎么实现的:
在这里插入图片描述
@Configuration表明这是个配置类,@Bean则表示该方法会返回一个bean实例,并会注入到spring容器中,通常方法名就是这个bean的名字。 但是如果new MyService(name)使用了构造函数,并且参数是name,则该bean的实例化id就是这个name参数。

3.maven依赖
在这里插入图片描述
spring依赖的引入(版本5.2.1.RELEASE),maven会帮我们下载好spring需要的几个包
在这里插入图片描述
以上编写好后,直接运行main()就能实现注解版的spring了。

注解二: 增加注解扫描包的范围(@ComponentScan)

@ComponentScan(basePackages = “com.spring.annotation_config_demo.pkg1”)

在basePackages 这个包下面的类都会被进行注解扫描

注解三: 组件识别(@Component)

@Service、@Component和@Repository 的基类注解

注解四: 同一个类不同实例间区分注入(@Qualifier)

一般配合@Autowired使用,区分同一个类不同的实例

注解五: 属性注入*.properties的属性配置(@PropertySource) 以及属性读取(@Value)

如果你要自定义添加一个属性文件,那么可以使用@PropertySource注解。

@Configuration
@PropertySource("classpath:com.spring.annotation_demo/service.properties")
public class PropertiesConfig {
}

那么,如何获取这个属性文件的值呢? 可以配合@Value进行获取使用,也可以获取属性类型对象PropertySourcesPlaceholderConfigurer (请看下个注解)

public MyService01(@Value("${service.myname}") String name) {
		this.name = name;
	}

代码示例:码云目录(com.spring.annotation_demo)

注解六: XML方法注入(PropertySourcesPlaceholderConfigurer)–(该对象代码示例:com.spring.collectiondemo)
  <bean id="properties"
          class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="properties">
            <value>
                datasource.url=www.baidu.com
                datasource.username=chen3
                datasource.password=123456
            </value>
        </property>
    </bean>

代码获取属性:

PropertySourcesPlaceholderConfigurer myDataSource = (PropertySourcesPlaceholderConfigurer) context.getBean("myDataSource");
		PropertySources sources = myDataSource.getAppliedPropertySources();
		PropertySource<?> localProperties = sources.get("localProperties");
		String propertySource = (String) localProperties.getProperty("jdbc.driver.className");
		System.out.println(propertySource);
注解七: 引入容器中某一实例(@Import)

该注解的作用:

Indicates one or more component classes to import — typically @Configuration classes.
Provides functionality equivalent to the element in Spring XML

引入一个或多个@Configuration组件,等同于xml中的标签。
也就是能引入多个配置,从而形成隔离分类但还能生效的作用。(代码示例:com.spring.annotation_import_demo)

注解八: 备份生产和开发环境属性,如何手动切换(@Profile) --代码示例:com.spring.annotation_profile_demo

在每个配置类中加上注解

@Configuration
@Profile("dev")
public class DevConfig {

然后在启动时,显示的setActiveProfiles(“dev”)就可以把配置切换成开发环境了。

	AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
		ctx.getEnvironment().setActiveProfiles("dev");
		ctx.register(DevConfig.class, ProConfig.class);
		ctx.refresh();
注解九: spring 事件驱动(@EvenListener) (示例目录:com.spring.applicationEven_annotation_demo)

事件驱动,可以让我们进行更有效的解耦操作。也是为一些没有布上mq啥的做一些前期准备吧。
同步和异步的都在代码上,这里贴一下实现截图

@Configuration
@EnableAsync
public class Config {

被监听的事件:

public class BlackListEvent extends ApplicationEvent
注解九一: 同步
	@EventListener
	public void processBlackListEvent(BlackListEvent event) {
注解九二: 异步

异步这里我有点话~~
异步需要线程池的支持,所以这里需要注入一个线程池TaskExecutor()

@Bean(name="processExecutor")
	public TaskExecutor workExecutor() {
		ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
		threadPoolTaskExecutor.setThreadNamePrefix("Async-");
		threadPoolTaskExecutor.setCorePoolSize(10);
		threadPoolTaskExecutor.setMaxPoolSize(20);
		threadPoolTaskExecutor.setQueueCapacity(600);
		threadPoolTaskExecutor.afterPropertiesSet();
		// 自定义拒绝策略
		threadPoolTaskExecutor.setRejectedExecutionHandler((r, executor) -> {
			// .....
		});
		// 使用预设的拒绝策略
		threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		return threadPoolTaskExecutor;
	}

然后再在监听的方法上加上就能实现异步了

@EventListener
	@Async//异步
	public void processSecondEvent(SecondEvent event) {
注解十: @Primary 的使用-这儿有个场景吧,就是多数据源的时候,可能会创建两个相同的数据源接口实现,但是调用的时候,是基于接口调用的,此时,容器就不知道默认要注入哪一个数据源给你,这个时候就需要@Primary注解进行区分了。 在示例中,我是以调用IService接口的某个方法,而Service001和Service002都是IService的实现,如果没有加@Primary注解,就会被报错

No qualifying bean of type ‘com.spring.primary_demo.IMyService’ available: expected single matching bean but found 2: myService001,myService002

示例代码:com.spring.primary_demo

@Component
@Primary
public class MyService001 implements IMyService{
注解十一: @Bean的depends-on属性

该注解的作用可以让被修饰的beanA的初始化后于depends-on中的bean初始化,若beanA初始化时未发现depends-on中的bean初始化,则会强制初始化其中的bean,然后再初始化beanA。
示例:com.spring.depends_on_demo

<bean id="beanOne" class="com.spring.depends_on_demo.ExampleBean" depends-on="manager"/>
注解十二: 自己实现一个工厂方法创建bean(FactoryBean)

示例代码:com.spring.factorybean_demo
主要实现了FactoryBean方法,可以控制单例多例的实现

public class MyFactoryBean implements FactoryBean<CustomService>{
	@Override
	public CustomService getObject(){
		return new CustomService(1);
	}
	@Override
	public Class<?> getObjectType() {
		return CustomService.class;
	}
	@Override
	public boolean isSingleton() {
		return true;
	}
}
注解十三: 如何手动关闭spring容器(tomcat热部署中的一步:registerShutdownHook)

这儿可以看tomcat源码上有个hook。
另外,如果自己手动实现一个web的话,可以设置一个按钮,关闭整个服务器,这时候就可以用这东西了(应该很少用,但是之前公司里使用的dubbo泛化调用就这么玩的)

	public static void main(String[] args) throws InterruptedException {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
		ctx.registerShutdownHook();
		int n = 0;
		while (n<5){
			Thread.sleep(1000);
			System.out.println("hehe");
			n++;
		}
	}
注解十四: JSR330的两个注解(@Inject和@Name)

@Inject相当于@Autowired ; @Name相当于@Component ,前提要引入依赖:
官网链接:

https://docs.spring.io/spring/docs/5.2.1.RELEASE/spring-framework-reference/core.html#beans-standard-annotations

<!--JSR330-->
    <dependency>
      <groupId>javax.inject</groupId>
      <artifactId>javax.inject</artifactId>
      <version>1</version>
    </dependency>
注解十五: spring i18n实现----国际化还是有些网站要用上的。

示例代码:com.spring.i18n_demo
玩这个,其实就是key-value形式的属性文件上的不同语言版本的内容,然后就在获取时传参数

1.spring.xml

    <beans>
        <!-- this MessageSource is being used in a web application -->
        <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
            <property name="basename" value="i18n.exceptions"/>
            <property name="defaultEncoding" value="UTF-8"/>
        </bean>
    </beans>

在这里插入图片描述

public class App {
	public static void main(String[] args) {
		MessageSource resources = new ClassPathXmlApplicationContext("spring.xml");
		String message1 = resources.getMessage("argument.required",
				new Object [] {"userDao"}, "Required", Locale.SIMPLIFIED_CHINESE);
		System.out.println(message1);
	}
}
注解十六: spring 单例如何调用多例

没想好怎么写。

4、IOC原理是什么?(Spring容器是如何创建的)(基本原理:反射)

关于注解版的常用注解已经差不多解释完了,虽然也掺杂了一些xml形式,但不难转换成注解版的。(是我偷懒了~~)下面该讲原理了,就是IOC主要的构建流程。

1)核心流程 (refresh())

不管是xml形式还是注解形式的spring,构建ioc容器核心都是refresh()方法,所以这是重点对象。坐标:AbstractApplicationContext#refresh(),整个方法分成了10个步骤。下面一一介绍:

(1)刷新前处理— prepareRefresh();
  • initPropertySources(); -----------初始化一些属性设置;子类自定义个性化的属性设置方法
  • getEnvironment().validateRequiredProperties(); 检验属性的合法等
  • this.applicationListeners.addAll(this.earlyApplicationListeners); 重置本地容器监听器
(2) ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

Specify an id for serialization purposes, allowing this BeanFactory to be
deserialized from this id back into the BeanFactory object, if needed.

  • 告诉子类刷新内部的beanFactory类,返回刷新后的beanFacotry的子类ConfigurableListableBeanFactory的一个实现类DefaultListableBeanFactory 实例 (很绕对吧? 那就对了。。。。)
  • 怎么识别DefaultListableBeanFactory 呢? 用的是serializationId。
  • 这一步主要是构建一个serializationId唯一识别的DefaultListableBeanFactory
(3) prepareBeanFactory(beanFactory);

上一步只是创建DefaultListableBeanFactory 对象而已,这一步就相当于为它装配属性吧,装配标准BeanFactory该有的属性。

  • classloader
  • 容器的回调的类,其中重要的操作有:

1.将DefaultListableBeanFactory 注入application属性中(新构建ApplicationContextAwareProcessor)
2.将ApplicationContextAwareProcessor作为处理器注入AbstractBeanFactory的beanPostProcessors 中,为第(5)步做准备

  • 能够被@Autowired自动分配的类,比如:ApplicationContext.class
  • 监听器
  • LoadTimeWeaver (类加载期织入器,AOP的一种)
  • 注册默认的环境使用的bean(“environment”,“systemProperties”,“systemEnvironment”),可以根据这些id进行注入获取的哈。
(4) postProcessBeanFactory(beanFactory);

注册一个BootstrapContextAwareProcessor类,我理解是用来启动各种*Aware类的

(5) invokeBeanFactoryPostProcessors(beanFactory);

目标对象:生成Bean的工厂—BeanFactory
这里做了两件事:

1.registryProcessor.postProcessBeanDefinitionRegistry(registry); —注册postProcessBean
----> 1.1 区分出实现PriorityOrdered,Ordered 和不实现Ordered的注册器,因为有些注册器存在依赖关系(就是依赖上一个注册器的结果),所有要实现Ordered接口。
-----> 1.2 先注册PriorityOrdered,后注册ordered接口的注册器,再注册没有实现ordered的接口的。

2.invokeBeanFactoryPostProcessors —调用工厂的后置处理器
-----> 后置处理器处理的逻辑和注册器一样。
-----> 1.区分出实现PriorityOrdered,Ordered 和不实现Ordered的后置处理器
-----> 2.先调用PriorityOrdered,后调用ordered接口的注册器,再调用没有实现ordered的接口的。

这两步完成之后,算是BeanFactory创建完成了,接下来的步骤就是要注册我们应用程序中使用到的Bean了

(6) registerBeanPostProcessors(beanFactory);

多说一句,其实(5)中的beanFactory也算是JavaBean,所以Bean的创建步骤和(5)差不多。

差在哪呢?
1.这个internalPostProcessors这里,

AutowiredAnnotationBeanPostProcessor — 看类名可以推出是和@Autowired注解有关的后置处理器
CommonAnnotationBeanPostProcessor — 这也是跟注解有关的后置处理器。Common,那就是共同的注解吧。而AutowiredAnnotationBeanPostProcessor 则是注解后置处理器中的特例。。

2.最后会重新注册一个Bean后置处理器:ApplicationListenerDetector

也是为了在以后步骤中遍历处理完Bean的后置处理器,进行的一些监听器的操作。

(7) initMessageSource();

初始化系统显示的信息类型,可以参考i18n国际化示例

(8) initApplicationEventMulticaster();

广播ApplicationEvent 的操作,启动各种观察者对相对应的行为
这一步就主要是初始化ApplicationEventMulticaster实例而已,真正的操作也就是
setFactoryBean给这个实例。

(9) onRefresh();

这是初始化一些特殊的bean,据我所知,在springboot中的这个方法里,被重写了,用于热重启tomcat的。

(10) registerListeners();

1.为广播器装上监听器

获取所有监听器并添加到广播器中

for (ApplicationListener<?> listener : getApplicationListeners()) {
			getApplicationEventMulticaster().addApplicationListener(listener);
		}
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
		for (String listenerBeanName : listenerBeanNames) {
			getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
		}

发布早期的事件,并开启相关监听器

// Publish early application events now that we finally have a multicaster...
		Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
		this.earlyApplicationEvents = null;
		if (earlyEventsToProcess != null) {
			for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
				getApplicationEventMulticaster().multicastEvent(earlyEvent);
			}
		}
(11) finishBeanFactoryInitialization(beanFactory)

Instantiate all remaining (non-lazy-init) singletons. – 初始化剩下的不是懒加载的单例

这代码片段就是证据

 if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {

从定义好的bean中挑选上面这个条件的单例。
关键步骤:

beanFactory.preInstantiateSingletons();
1.RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName); --获取bean信息

1判断是否是工厂方法bean,如果是,在判断是否是热初始化
2获取bean实例 getBean(benaName)中的GetBeanName();

1 若缓存中存在已经单例的,直接获取返回。bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
2 获取工厂中的bean,如果有,则直接返回
3 先实例化depend-on的类,再进行判断该生成的bean是单例还是多例实现

1如果是单例实现,直接创建bean , createBean(beanName, mbd, args);

1 获取RootBeanDefinition mbdToUse = mbd;
2 覆盖重写的方法。mbdToUse.prepareMethodOverrides();
3 进行初始化前的解析 Object bean = resolveBeforeInstantiation(beanName, mbdToUse); 主要是处理bean后置处理器
4 然后就是创建bean Object beanInstance = doCreateBean(beanName, mbdToUse, args);

1 instanceWrapper = createBeanInstance(beanName, mbd, args); --这一步很多地方使用上了反射api
2 final Object bean = instanceWrapper.getWrappedInstance(); 获取包装后的bean
3 执行 合并bean定义 applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);
4 部署bean的属性 populateBean(beanName, mbd, instanceWrapper);
5 exposedObject = initializeBean(beanName, exposedObject, mbd);

1 创建前处理 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
2 如果bean 继承InitializingBean 实现它的重写方法 ((InitializingBean) bean).afterPropertiesSet();,如果存在init()自定义方法,则也在这个点执行。invokeCustomInitMethod(beanName, bean, mbd);
3 创建后处理wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

2 根据SmartInitializingSingleton 类,在初始化单例后,智能处理相关事件。

典型的synchronized锁的加法:

if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
			synchronized (this.singletonObjects) {
				singletonObject = this.earlySingletonObjects.get(beanName);
				if (singletonObject == null && allowEarlyReference) {
					ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
					if (singletonFactory != null) {
						singletonObject = singletonFactory.getObject();
						this.earlySingletonObjects.put(beanName, singletonObject);
						this.singletonFactories.remove(beanName);
					}
				}
			}
		}

至此,(10)步骤已经结束。

(11) finishRefresh()

主要工作还是推行事件发布

publishEvent(new ContextRefreshedEvent(this));

监听器是广播发布的。

public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
		ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
		Executor executor = getTaskExecutor();
		for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
			if (executor != null) {
				executor.execute(() -> invokeListener(listener, event));
			}
			else {
				invokeListener(listener, event);
			}
		}
	}

摘自网络
createBean()过程。
在这里插入图片描述

2)核心组件理解

ApplicationContext
BeanFactory:
FactoryBean
ApplicationListener
ApplicationAware
BeanPostProcessor
MessageSource:

3)spring中的主要事件

Spring 提供了以下五种标准的事件:
上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext 被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的 #refresh() 方法时被触发。
上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext 的 #start() 方法开始/重新开始容器时触发该事件。
上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext 的 #stop() 方法停止容器时触发该事件。
上下文关闭事件(ContextClosedEvent):当ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都被销毁。
请求处理事件(RequestHandledEvent):在 We b应用中,当一个HTTP 请求(request)结束触发该事件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值