TABLE OF CONTENTS

  1. 前言
  2. Spring IOC容器构建过程
    1. invokeBeanFactoryPostProcessors
    2. registerBeanPostProcessors
    3. finishBeanFactoryInitialization
    4. Bean存储在beanDefinitionMap中
    5. Bean如何加载到Spring容器中?
    6. 几个BeanFactory子列
    7. Bean的生命周期
  3. 最佳实践
  4. 代码工程
  5. Spring基本概念
  6. IoC容器如何管理对象间的依赖关系
  7. IoC容器
  8. Bean的生命周期
  9. ApplicationContext容器
  10. Spring注解
  11. Spring AOP切面

前言

这篇文章主要从Spring IoC容器的角度进行切入,了解Spring是如何工作的。

Spring IOC容器构建过程

org.springframework.context.support.AbstractApplicationContext#refresh核心代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}

invokeBeanFactoryPostProcessors

invokeBeanFactoryPostProcessors方法主要是获取BeanFactoryPostProcessor接口的子类,并执行它的postProcessBeanFactory方法:

org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory

1
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;

registerBeanPostProcessors

registerBeanPostProcessors方法可以获取用户定义的BeanPostProcessor的子类,并把他们注册到BeanFactory对象中的beanPostProcessors变量中。org.springframework.beans.factory.config.BeanPostProcessor接口声明了2个方法:postProcessBeforeInitializationpostProcessAfterInitialization,在Bean初始化时执行用户自定义的操作。

finishBeanFactoryInitialization

Bean的实例化代码逻辑,里面涉及一个FactoryBean,一个特殊的工厂bean,用于产生Bean。通常地,通过继承FactoryBean,实现其getObject方法,用户可以产生该类实例对象的方法。Spring获取FactoryBean本身的对象是通过在前面加上&来完成。经常用来整合第三方框架。

Bean的实例创建流程比较复杂,可以查看流程图

Bean存储在beanDefinitionMap中

其中BeanDefinition接口是Bean的定义对象,Bean实例化之后存放在Spring容器中,维护在DefaultListableBeanFactory#beanDefinitionMap属性中,key默认是首字母小写的类名,value是一个BeanDefinition子类对象。

org.springframework.beans.factory.support.DefaultListableBeanFactory#beanDefinitionMap

1
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, BeanDefinition>(64);

Bean如何加载到Spring容器中?

通过org.springframework.beans.factory.support.BeanDefinitionReader的子类来实现,XML配置文件,注解的方式标识需要Spring容器管理的Bean,有对应的BeanDefinitionReader进行加载。

几个BeanFactory子列

简介
ClassPathXmlApplicationContext 通过读取类路径下的XMl配置文件创建IOC容器对象
FileSystemXmlApplicationContext 通过读取文件系统路径下的XMl配置文件创建IOC容器对象
ConfigurableApplicationContext ApplicationContext的子接口,包含一些扩展方法refresh()和close(),让ApplicationContext具有启动、关闭和刷新上下文的能力
WebApplicationContext 基于Web环境创建IOC容器,将对象存入ServletContext中

Bean的生命周期

最佳实践

代码工程


Spring基本概念

1、IOC注入的三种方式

  • 构造方法
  • setter方法
  • 接口注入

注入有2层含义:
(1)将依赖的对象通过Spring容器注入进来;
(2)生成当前对象实例后注入到容器中(非必须)

注入是指,如果当前对象有依赖于其他对象实例,不再通过new的方式创建,而是直接从Spring容器中获取。

而从容器中获取的实例是否是同一个,涉及到对象的scope

2、对象的scope有几种
有3种:singleton单例,prototype多实例,session一次会话是相同的实例,不同的会话是不同的实例。

3、什么是控制反转IoC(Inversion of controll)
将原来通过new关键字创建对象实例的方式,改成直接从Spring容器中获取对象实例。

4、IoC的好处是什么?
好处是不用再关心依赖类的实例化,而是直接从容器中获取,从而直接使用被依赖对象提供的服务。

IoC容器如何管理对象间的依赖关系

1、有哪些管理对象间依赖的方式?

  • 直接编码,进行对象绑定 –> 不符合IoC的理念,因此Spring主要是通过下面2种方式进行对象间依赖关系的管理
  • 配置文件,通常用xml
  • 元数据方式

IoC容器

1、Spring有哪些IoC容器?

  • BeanFactory
  • ApplicationContext

BeanFactory容器特点:
默认延迟初始化策略(懒加载lazy-load),启动初期所需资源少,速度快

ApplicationContext容器特点:
在BeanFactory的基础上构建,还提供了更高级的特性,如事件发布、国际化信息处理等
容器启动后,默认全部初始化并完成绑定,前期所需系统资源多,启动速度较慢

这2个容器都是接口

2、ApplicationContext与BeanFactory的关系是怎样的?ApplicationContext容器如何实现比BeanFactory更丰富的功能? — 同样的问题

ApplicationContext间接继承BeanFactory,自然拥有BeanFactory的功能,此外还继承了其他三个接口:ApplicationEventPublisher,ResourceLoader,MessageSource,因此多出来的功能是通过三个接口抽象定义的。

3、BeanFactory与ApplicationContext分别如何进行构建?

1
2
3
4
5
BeanFactory bf = new XmlBeanFactory("配置文件路径");

ApplicationContext ac = new ClassPathXmlApplicationContext("配置文件路径");
// 或者根据下面的方式构建
ApplicationContext ac = new FileSystemXmlApplicatiionContext("配置文件路径")

4、是不是所有的对象实例都应该通过Bean容器进行管理?

当然不是,通常来说只有需要对外提供服务的对象实例要通过Spring容器管理起来

5、Bean如何被注入Spring容器?—阅读源代码

todo

6、BeanDefinitionRegistry和BeanDefinitionReader类之间的关系?或者说Spring如何实现同一个的外部配置文件加载?

BeanDefinitionReader负责将相应的配置文件内容读取并映射到BeanDefinition,然后将映射完成的BeanDefinition注册到一个BeanDefinitionRegistry中,然后BeanDefinitionRegistry即完成Bean的注册和加载。大部分的工作,如括解析文件格式、装配BeanDefinition之类的工作,都是由BeanDefinitionReader的实现类来完成,BeanDefinitionRegistry只负责保管。

BeanDefinitionReader有3个实现类,一个抽象类AbstractBeanDefinitionReader,2个具体子类:PropertiesBeanDefinitionReader和XmlBeanDefinitionReader,说明Spring默认支持2种配置文件:xml和properties

因此,如果想要实现自定义配置文件的加载,可以通过继承AbstractBeanDefinitionReader来实现。

7、@Component和@Autowired注解的作用

@Component在类和接口上标注,开启自动扫包后,对应扫描路径下如果有该注解的类实例会注册到Spring容器中,通过Spring容器进行管理

自动扫包配置

1
<context:component-scan base-package="cn.spring21.project.base.package"/> 

@Autowired是从Spring容器中获取依赖的对象实例并注入到注解标注的地方

8、xml配置文件有哪些关键的配置?

某个场景下如果要对大部分的<bean>进行设置某些行为的话,可以利用<beans>统一设置。

一些常用的属性
default-lazy-init
default-autowire
default-dependency-check
default-init-method
default-destroy-method

添加别名

1
<alias name="dataSourceForMasterDatabase" alias="masterDataSource"/>

在xml配置文件进行配置时,如果通过属性注入要求类提供setter方法,通过构造参数注入要求类提供对应参数列表的构造方法

了解如何注入复杂类型的对象,如集合、空元素null、内部bean

depends-on显示指定bean之间的依赖关系,可以通过逗号分隔指定多个依赖的bean

1
2
3
<bean id="classAInstance" class="...ClassA" depends-on="configSetup,configSetup2,..."/> 
<bean id="configSetup" class="SystemConfigurationSetup"/>
<bean id="configSetup2" class="SystemConfigurationSetup2"/>

9、Spring提供了哪些自动绑定方式?
5种:no(默认), byName, byType, constructor, autodetect

手动指定的绑定关系,会覆盖自动绑定的关系,一般来说会使用手动绑定,明确依赖关系,便于管理,避免一些不可预知的行为发生。比如,使用byName可能会导致同名的bean定义被替换,造成问题。

10、lazy-init属性了解
默认会进行初始化,除非设置了lazy-init="true"。假如一个对象A依赖了延迟初始化的bean对象B,那么A实例化时会先实例化对象B,表现为lazy-init属性设置失效。

11、如果父类和子类都要放到bean容器进行管理,需要如何配置呢?

子类会继承父类的非私有属性,假如父类和子类的属性指向同一个对象,可以使用parent属性简化配置,当然建议是在代码属性上加上@Autowired注解,或者在xml中引用同一个bean对象即可。

Bean的生命周期

1.bean的scope

标记为singleton的bean是由容器来保证这种类型的bean在同一个容器中只存在一个共享实例;
而Singleton模式则是保证在同一个Classloader中只存在一个这种类型的实例

2、自定义scope的步骤

  • 实现Scope接口,参考org.springframework.context.support.SimpleThreadScope实现
  • Spring容器(例如ApplicationContext)中注册
    • 直接编码:beanFactory.registerScope("thread",threadScope);
    • xml配置:
1
2
3
4
5
6
7
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> 
<property name="scopes">
<map>
<entry key="thread" value="com.foo.ThreadScope"/>
</map>
</property>
</bean>

3、工厂bean类FactoryBean的作用是什么?使用方法?

Java强调面向接口编程,依赖的属性通常为接口,但类实例化时通常会选择具体的接口实现,FactoryBean就是将声明依赖接口的对象与接口实现类关联起来的一种方式。

FactoryBean这里有一个知识点,用到了工厂方法模式。

工厂方法模式:
定义一个用于创建对象的接口,让子类决定实例化哪一个类,使一个类的实例化延迟到其子类。或者更加本质的概括说:延迟到子类来选择实现

4、Spring容器有哪些地方使用了FactoryBean?

  • JndiObjectFactoryBean
  • LocalSessionFactoryBean
  • SqlMapClientFactoryBean
  • ProxyFactoryBean
  • TransactionProxyFactoryBean

4、依赖注入DI与控制反转IoC是同一个意思吗?区别是什么?

两者是对同一件事情的不同描述
依赖注入:应用程序依赖于容器并注入它所需要的资源
控制反转:由容器反向地向应用程序注入其所需要的外部资源

5、类B是类A的属性成员,假如类B定义为prototype,如果要确保每次实例化A时获取一个新的实例化对象B,需要通过lookup-method标签指定获取依赖对象B的方式,Spring会通过Cglib动态生成一个子类并覆写方法getNewsBean,把新生成的子类对象注入到对象A的newsBean属性中。

1
2
3
4
5
<bean id="newsBean" class="..domain.FXNewsBean" scope="prototype"> 
</bean>
<bean id="mockPersister" class="..impl.MockNewsPersister">
<lookup-method name="getNewsBean" bean="newsBean"/>
</bean>

BeanFactoryAware接口:
容器会自己本身注入到BeanFactoryAware接口的子类中,通过BeanFactoryAware接口方法setBeanFactory实现,可以定义一个BeanFactory beanFactory属性,通过覆写setBeanFactory方法设置到beanFactory属性中,后续再次获取scope为prototype的对象时,则每次都通过beanFactory.getBean(“newsBean”)来获取。这种方式与上面的效果一样。

ObjectFactoryCreatingFactoryBean是一个FactoryBean实现类,它的父类还实现了BeanFactoryAware接口,但是通常认为直接操作BeanFactory可能会导致一些安全问题。ObjectFactoryCreatingFactoryBean会返回一个ObjectFactory实例,用于与Spring容器进行交互,隔离了客户端对BeanFactory的直接引用。

与方法注入只是通过相应方法为主体对象注入依赖对象不同,方法替换更多体现在方法的实现层
面上,它可以灵活替换或者说以新的方法实现覆盖掉原来某个方法的实现逻辑

方法替换MethodReplacer

ObjectFactoryCreatingFactoryBean源代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<beans>

<!-- Prototype bean since we have state -->
<bean id="myService" class="a.b.c.MyService" scope="prototype"/>

<bean id="myServiceFactory"
class="org.springframework.beans.factory.config.ObjectFactoryCreatingFactoryBean">
<property name="targetBeanName"><idref local="myService"/></property>
</bean>

<bean id="clientBean" class="a.b.c.MyClientBean">
<property name="myServiceFactory" ref="myServiceFactory"/>
</bean>

</beans>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyClientBean {

private ObjectFactory<MyService> myServiceFactory;

public void setMyServiceFactory(ObjectFactory<MyService> myServiceFactory) {
this.myServiceFactory = myServiceFactory;
}

public void someBusinessMethod() {
// get a 'fresh', brand new MyService instance
MyService service = this.myServiceFactory.getObject();
// use the service object to effect the business logic...
}
}

6、BeanFactoryPostProcessor容器扩展机制,可以实现什么功能?

本身就有很多实现

BeanFactoryPostProcessor一些常见的实现:

PropertyPlaceholderConfigurer
(1)PropertyPlaceholderConfigurer允许我们在xml配置文件中使用>${jdbc.url}之类的占位符,加载配置文件的方式对其进行填充,因此可以将这些配置信息单独配置到一个个的properties,不需要全部写到xml中。直接改properties的KV结构要比改xml配置文件更不如容易出错。
(2)PropertyPlaceholderConfigurer不单可以从properties文件中加载配置项,也可以从Java的System类中的Properties加载配置项,默认是先从properties文件中查找,再从System的Properties中查找。

PropertyOverrideConfigurer
(1)对任何的bean定义进行更改,指定一些properties配置文件,里面的kv可以替换掉其他地方的键值对定义,还可以修改bean定义。比如使用beanName.propertyName=value来修改。
(2)配置多个PropertyOverrideConfigurer时,以最后一个为准。

PropertyPlaceholderConfigurer和PropertyOverrideConfigurer都继承自PropertyResourceConfigurer,里面有一个protected类型的方法convertPropertyValue,子类可以覆盖这个方法对相应的配置项进行转换,比如有些敏感配置项是通过密文存储,加载的时候需要先解密在覆盖到bean定义中。

CustomEditorConfigurer
对对BeanDefinition没有做任何变动,只是将后期会用到的信息注册到容器。

PropertyEditor
xml文件中配置的都是String类型的字符串,解析bean定义时需要转换为实际的类型,比如

  • StringArrayPropertyEditor:将逗号分隔的字符串转换为String[]格式
  • ClassEditor:根据String类型的class名称,直接将其转换成相应的Class对象,相当于Class.forName(String)完成的功效
  • FileEditor等等

PropertyEditor也可以自定义,可以通过实现PropertyEditor接口,也可以继承PropertyEditorSupport,如果仅仅涉及格式转换,可以重写setAsText()和getAsText()方法

7、bean的实例化过程?或者说bean的生命周期
第一次调用beanFactory.getBean会进行bean的实例化,第二次调用会直接返回容器缓存的对象实例(prototype类型的bean除外)

实例化的过程如下:

  • 实例化bean对象
  • 设置对象属性
  • 检查Aware相关接口并设置相关依赖
  • BeanPostProcessor前置处理postProcessBeforeInitialization
  • 检查是否是InitializingBean的实现,决定是否调用afterPropertiesSet方法
  • 如果配置有init-method方法,则调用
  • BeanPostProcessor后置处理postProcessAfterInitialization
  • 注册必要的Destruction相关回调接口
  • 使用中
  • 如果实现了DisposableBean接口,则调用destroy方法
  • 如果配置有destroy-method方法,则调用

可以AbstractAutowireCapableBeanFactory的createBean()方法查看完整实现

8、容器内部可以通过哪些方式来实例化bean?

策略模式来决定采用何种方式初始化bean实例,可以通过反射或者Cglib动态字节码的方式初始化bean或者动态生成其子类。
InstantiationStrategy定义是实例化策略的抽象接口,直接子类SimpleInstantiationStrategy实现了简单的对象实例化功能,可以通过反射来实例化对象实例。
CglibSubclassingInstantiationStrategy继承了SimpleInstantiationStrategy,除了能够通过反射实例化对象,也可以通过Cglib动态字节码技术生成某个类的子类,满足方法注入所需对象的实例化需求。
默认情况下,Spring容器内部采用的是CglibSubclassingInstantiationStrategy

策略模式:
AbstractAutowireCapableBeanFactory相当于context,持有InstantiationStrategy,而上面两种策略可以根据需要进行选择,默认选用CglibSubclassingInstantiationStrategy

BeanWrapper定义继承了org.springframework.beans.PropertyAccessor接口,可以以统一的
方式对对象属性进行访问

9、Spring容器的Aware接口

BeanFactory容器的Aware接口

  • BeanNameAware:将改对象实例的bean定义对应的beanName设置到当前对象实例
  • BeanClassLoaderAware:将对应加载当前bean的ClassLoader注入到当前对象。默认会使用ClassUtils类的ClassLoader
  • :将BeanFactory容器自身注入到当前对象实例,当前对象实例就持有了BeanFactBeanFactoryAwareory容器的引用

ApplicationContext容器的Aware接口

  • ResourceLoaderAware:ApplicationContext实现了ResourceLoaderAware接口,会将当前的ApplicationContext容器注入到当前对象实例,与BeanFactoryAware类似
  • ApplicationEventPublisherAware:将当前的ApplicationContext容器注入到当前对象实例,还具有发布器的作用
  • MessageSourceAware:将当前的ApplicationContext容器注入到当前对象实例,并提供国际化的信息支持

10、BeanFactoryPostProcessor和BeanPostProcessor区别
(1)前者是容器启动阶段,后者是对象实例化阶段。
(2)BeanFactoryPostProcessor会处理容器内所有符合条件的BeanDefinition,BeanPostProcessor会处理所有符合条件的实例化后的对象实例

ApplicationContext容器

1、讲讲ApplicationContext如何实现统一资源加载?

Spring中通过Resource定义了资源的抽象接口,ResourceLoader定义资源的加载,两者结合完成统一资源加载。ApplicationContext继承了ResourcePatternResolver,间接实现了ResourceLoader接口,因此这就是ApplicationContext支持Spring内统一资源加载原因。

涉及的几个Aware接口:

  • ResourceLoaderAware
  • ApplicationContextAware

2、Java和Spring分别使用什么方式进行国际化信息的处理?

(1)Java主要涉及2个类;java.util包里的Locale和ResourceBundle,ResourceBundle用来保存特定某个Locale的信息,ResourceBundle管理一组信息序列,命名格式为:{basename}{language}{COUNTRY}.properties,例如messages_zh_CN.properties,每个文件涉及的key相同,value与国家语言有关。

(2)Spring通过MessageSource接口,传入相应的Locale、资源的key以及相应参数就可以获取相应的信息,不用先根据Locale取得ResourceBundle

1
2
3
4
5
6
7
public interface MessageSource { 
String getMessage(String code, Object[] args, String defaultMessage, Locale locale);

String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException;

String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessage zException;
}

ApplicationContext使用messageSourcec示例

1
2
3
4
5
6
7
8
9
10
<beans> 
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basenames">
<list>
<value>messages</value>
<value>errorcodes</value>
</list>
</property>
</bean>
</beans>
1
2
3
4
5
ApplicationContext ctx = ...; 
String fileMenuName = ctx.getMessage("menu.file", new Object[]{"F"}, Locale.US);
String editMenuName = ctx.getMessage("menu.file", null, Locale.US);
assertEquals("File(F)", fileMenuName);
assertEquals("Edit", editMenuName);

MessageSource的三种实现:

  • StaticMessageSource:一般只用于测试
  • ResourceBundleMessageSource:提供了对多个ResourceBundle的缓存以提高查询速度,常用。
  • ReloadableResourceBundleMessageSource:通过其cacheSeconds属性可以指定时间段,以定期刷新并检查底层的properties资源文件是否有变更。

上面三种MessageResource可以独立于容器在程序中使用。

MessageSourceAware类

3、容器内事件发布机制

(1)
与观察者模式有些相似,Listener相当于Observer,事件发布器Publisher+ApplicationEvent相当于主题Subject,持有Listner集合并维护集合,当主题状态发生变化时,通知所有Listener
注意:Spring的ApplicationContext容器内的事件发布机制,主要用于单一容器内的简单消息通知和处
理,并不适合分布式、多进程、多容器之间的事件通知。

(2)一旦容器内发布ApplicationEvent及其子类的事件,注册到容器的ApplicationListener就会对事件进行处理。

一些ApplicationEvent的子类:

  • ContextClosedEvent:ApplicationContext容器在即将关闭时发布的事件类型
  • ContextRefreshedEvent:ApplicationContext容器在初始化或者刷新时发布的事件类型
  • RequestHandledEvent:Web请求处理后发布的事件类型,其中一个子类ServletRequestHandled用于Java EE的Servlet事件。

ApplicationListener
ApplicationContext容器内使用的自定义事件监听器接口定义,继承自java.util.EventListener,ApplicationContext容器启动时,会自动识别并加载EventListener类型的bean定义,一旦容器内有事件发布,会将通知这些注册到容器的EventListener

ApplicationContext
继承了ApplicationEventPublisher接口,担当了事件发布者的角色,通过类层次结构可以看出来,它将对应的活委托给了ApplicationEventMulticaster接口来实现,而ApplicationEventMulticaster接口有一个抽象子类AbstractApplicationEventMulticaster,这个抽象子类实现了监听器的管理,将事件发布委托给了它的子类SimpleApplicationEventMulticaster。这个SimpleApplicationEventMulticaster事件发布交给Executor类型去执行,是否同步异步可以自定义。

(3)一些Aware类
ApplicationEventPublisherAware

4、Spring中的Aware接口使用场景有哪些?
Aware接口是Spring框架中提供的一组标记接口,用于在Bean装配的过程中获取Spring容器中提供的一些核心组件或运行时上下文等信息。通过使用Aware接口,我们可以在Bean实例化和初始化过程中获取到Spring容器中其他组件,方便Bean类实现更复杂的业务逻辑。

通俗的说,只要实现了Aware子接口的Bean都能获取到一个Spring底层组件。实现XXXAware接口的bean:通过实现的setXXX方法就可以获取到XXX组件对象,在Aware的实现类就可以对组件进行操作了。具体可以查看源码org.springframework.context.support.ApplicationContextAwareProcessor#invokeAwareInterfaces,本质是通过BeanPostProcessor的子类ApplicationContextAwareProcessor调用postProcessBeforeInitialization方法完成一些操作。

常见的是实现ApplicationContextAware获取ApplicationContext,做成一个工具类AppplicationUtil,提供应用去获取bean。

常见的Aware实现:

  • ApplicationContextAware:web项目中可以获取ServletContext,获取国际化信息、Scheduler等定时任务
  • BeanFactoryAware:获取Spring容器中的Bean实例、手动注册BeanDefinition
  • MessageSourceAware:获取国际化信息
  • ResourceLoaderAware:进行资源加载操作
  • ServletContextAware:让Bean获取到ServletContext对象
  • EnvironmentAware:获取当前的环境配置,获取配置文件中的属性值
  • ServletConfigAware:让Bean获取到ServletConfig对象,可以获取Servlet相关参数
  • ApplicationContextInitializer:在Spring容器初始化之前做一些必要的操作
  • EmbeddedValueResolverAware:替换配置文件中的占位符,与@Value功能类似
  • LoadTimeWeaverAware:让Bean获取到LoadTimeWeaver对象,实现类的动态加载
  • ApplicationEventPublisherAware:让Bean获取到ApplicationEventPublisher对象,发布和监听事件
  • ConversionServiceAware:类型转换,数据校验

Spring注解

1、系统有哪些BeanPostProcessor?功能主要是做什么?

AutowiredAnnotationBeanPostProcessor:往@Autowired标注的对象注入

2、JSR250标准下的注解有哪些?

使用JSR250注解需要在xml配置文件往Spring容器中注入下面的对象

1
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/> 

@Resource:byName的方式注入
@PostConstruct和@PreDestroy:用于标注对象生命周期管理相关方法

同理,使用Spring的注解@Autowired,也需要在xml配置进行配置

1
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/> 

当然,有一种更简便的方法,在xml配置文件中使用下面配置搞定以上所有的BeanPostProcessor配置,作用是把AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor、RequiredAnnotationBeanPostProcessor四个BeanPostProcessor注册到容器。

1
<context:annotation-config/> 

classpath-scanning功能可以从某一顶层包(base package)开始扫描。当扫描到某个类标注了相应的注解之后,就会提取该类的相关信息,构
建对应的BeanDefinition,然后把构建完的BeanDefinition注册到容器。注意,下面配置还将AutowiredAnnotationBeanPostProcessor和
CommonAnnotationBeanPostProcessor一并注册到了容器中,因此上面的<context:annotation-config/>也可以不要。

1
<context:component-scan base-package="org.spring21"/> 

Spring AOP切面

涉及到的设计模式:代理模式

1、什么是Spring AOP?有哪些关键概念?

Joinpoint:需要织入代码的地方,比如方法调用,字段设置、异常处理等

Pointcut:Joinpoint表达式定义
常见的Pointcut

  • Advice:横切逻辑,有5种

  • BeforeAdvice:通常不会中断程序执行流程,除非有必要可以通过抛异常方式中断当前流。实现MethodBeforeAdvice即可
  • AfterAdvice:
    • After returning Advice:当前Joinpoint处执行流程正常完成之后。对应接口AfterReturningAdvice
    • After throwing Advice:抛异常的情况下会执行。对应接口ThrowsAdvice,覆写三个方法,可以对运行时异常、普通异常、应用异常分别处理,可以发送邮件给对应人员。
    • After finally Advice:无论正常终了还是抛出异常都会执行,类似finally块,使用MethodInterceptor接口实现
  • Around Advice:可以在Joinpoint之前和之后指定相应逻辑,甚至于中断或忽略Joinpoint原来程序流程的执行。Spring AOP没有直接定义Around Advice,使用MethodInterceptor接口实现

MethodInterceptor可以干些什么?
什么都能干,应用场景很多,系统安全验证检查、权限控制、性能检测、限流、日志记录等等

Aspect:对系统中横切点逻辑进行模块化封装的AOP实体。

2、什么是动态代理机制?
Java中的动态代理由Proxy和InvocationHandler两个类实现,

3、Cglib动态字节码技术?
借助Cglib动态字节码技术,在系统运行期间,为目标对象生成相应的扩展子类。

4、Spring MVC的拦截器Interceptor如何实现?

实现 org.springframework.web.servlet.HandlerInterceptor接口或继承 org.springframework.web.servlet.handler.HandlerInterceptorAdapter类,重写下面方法进行拦截处理。

一些拦截器的细节:

  • 拦截链条中,假如有A、B、C三个拦截器,假如各个拦截器的preHandle和postHandle方法都返回true,那么afterCompletion方法执行顺序是:C->B->A,并且都在postHandle执行完成后执行
  • 假如preHandle方法返回true,但是controller的方法中抛出异常,那么当前Interceptor的afterCompletion方法也会执行,postHandle方法不执行
  • 假如preHandle方法返回false,那么后续的controller方法和拦截器及其方法都不会执行
  • 拦截链条中,假如当前拦截器的preHandle返回false,当前拦截器的拦截方法不会执行,但是前面拦截器的afterCompletion方法还会执行

使用步骤:

  • 实现HandlerInterceptor接口或者继承HandlerInterceptorAdapter,重写对应拦截方法
  • 配置拦截器,可以用@Configuration注解或者xml配置文件进行配置,主要包括拦截请求类型、拦截的Interceptor信息,注意添加拦截器是有顺序关系的。

xml配置方式

1
2
3
4
<mvc:interceptors>
<!--第一种方式配置某个拦截器,默认是拦截所有请求的-->
<bean class="com.citi.interceptor.ZuluInterceptor"></bean>
</mvc:interceptors>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 返回false,后续的Interceptor不会继续执行,为true时会继续调用后续Interceptor的preHandle方法
*/
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return true;
}

/**
* Controller方法调用之后执行,通常用于设置一些通用的响应信息,如响应字段等,可以对Controller 处理之后的 ModelAndView 对象进行操作
*/
public void postHandle(
HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
}

/**
* 在ModelAndView对象完成渲染之后且在响应返回之前会执行该方法对结果进行后置处理。
*/
public void afterCompletion(
HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
}

应用场景:
(1)性能监控,监控请求处理的时长
通常来说controller请求处理前和后分别记录时间,两者时间差就是整个请求处理所耗费的时间,对于一些时间延迟比较大的,我们可以进行分析和优化。实现步骤如下:
利用ThreadLocal线程私有相关的变量,在实现的Interceptor中持有该变量,preHandle方法记录请求到达时间,在postHandle请求中记录请求处理完成后的时间,然后两个时间的差值就是所耗费的时间。

(2)拦截器通过判断 cookie/session 中是否有该标识,来决定继续流程还是重定向到登录页面
request.setAttribute("requestStartTime", startTime);同样可以实现请求耗时的计算。

5、过滤器Filter与拦截器Interceptor有什么区别?分别在什么场景使用?
Filter和Interceptor都可以用来实现权限校验、日志处理、数据解压/压缩处理、加密/解密处理等问题,但是Filter是Servlet规范,主要在Tomcat等Web容器下使用,Interceptor是Spring MVC规范,主要由Spring容器进行管理。

(1)Filter主要用责任链模式进行设计,在Tomcat中使用org.apache.catalina.core.ApplicationFilterChain来实现上面提到的责任链模式

通常在web.xml配置文件中配置:

1
2
3
4
5
6
7
8
9
10
<filter>
<filter-name>MDCInsertingServletFilter</filter-name>
<filter-class>
ch.qos.logback.classic.helpers.MDCInsertingServletFilter
</filter-class>
</filter>
<filter-mapping>
<filter-name>MDCInsertingServletFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

如果是在Spring中使用Filter,还可以通过注解的方式
在自定义过滤器上使用@WebFilter注解,并在启动类上使用@ServletComponentScan注解;结合@WebFilter注解中的urlPattern字段,Spring能够将过滤器的处理粒度进一步细化,还可以通过@Order来定义过滤器的顺序

(2)Interceptor是Spring原创的,interceptor的执行时机是要晚于filter的前置处理并且早于filter的后置处理的,同样也是采用责任链的设计模式。

详细可以查看org.springframework.web.servlet.DispatcherServlet#doDispatch源码,

Spring中的配置方式

1
2
3
4
5
6
7
8
9
@Configuration
public class WebConfig implements WebMvcConfigurer {

   @Override
   public void addInterceptors(InterceptorRegistry registry) {
       registry.addInterceptor(new DemoInterceptor()).addPathPatterns("/api/*").excludePathPatterns("/api/ok");
  }
}

6、Spring有几种拦截器
Filter过滤器->Interceptor拦截器->@ControllerAdvice->AOP

使用
Filter:通常同时实现Filter, Ordered接口并重写,可以自定义顺序

1
2
3
4
5
6
7
8
9
结果:
Filter 进入
Interceptor preHandle 进入
ControllerAdvice init 进入
Aop 进入
业务:user.name: 宫三公子
ControllerAdvice handleException 进入
Interceptor afterCompletion 进入
Filter 退出

9、Spring的循环依赖如何解决?如何避免循环依赖?

声明:本站所有文章均为原创或翻译,遵循署名 - 非商业性使用 - 禁止演绎 4.0 国际许可协议,如需转载请确保您对该协议有足够了解,并附上作者名 (Tsukasa) 及原文地址