要求
面试题:ApplicationContext refresh 的流程
【ApplicationContext 是spring中的核心容器; refresh 是其中的一个方法】
Spring refresh 概述
refresh
是 AbstractApplicationContext 中的一个方法,负责初始化 ApplicationContext 容器,容器必须调用 refresh 才能正常工作。它的内部主要会调用 12 个方法,我们把它们称为 refresh 的 12 个步骤:
- prepareRefresh
- obtainFreshBeanFactory
- prepareBeanFactory
- postProcessBeanFactory
- invokeBeanFactoryPostProcessors
- registerBeanPostProcessors
- initMessageSource
- initApplicationEventMulticaster
- onRefresh
- registerListeners
- finishBeanFactoryInitialization
- finishRefresh
功能分类
- 1 为准备环境 【为后续的步骤做准备工作】
- 2 3 4 5 6 为准备
BeanFactory
【因为ApplicationContext是一个外部容器,核心还是要交给BeanFactory容器完成】- 7 8 9 10 12 为准备 ApplicationContext
- 11 为初始化 BeanFactory 中非延迟单例 bean
1. prepareRefresh
这一步创建和准备了 Environment 对象,它作为 ApplicationContext 的一个成员变量
Environment
对象的作用之一是为后续 @Value,值注入时提供键值信息
Environment
分成三个主要部分
package day04.refresh;
// 如何获得和解析 @Value 内容
public class TestEnvironment {
public static void main(String[] args) throws NoSuchFieldException, IOException {
// 1) 获得 @Value 的值
System.out.println("=======================> 仅获取 @Value 值");
//resolver是要拿到@value{}中的值
QualifierAnnotationAutowireCandidateResolver resolver = new QualifierAnnotationAutowireCandidateResolver();
//要拿到name的值
Object name = resolver.getSuggestedValue(new DependencyDescriptor(Bean1.class.getDeclaredField("name"), false));
System.out.println(name);
// 2) 解析 @Value 的值
System.out.println("=======================> 获取 @Value 值, 并解析${}");
Object javaHome = resolver.getSuggestedValue(new DependencyDescriptor(Bean1.class.getDeclaredField("javaHome"), false));
System.out.println(javaHome);
//解析${}中的内容
System.out.println(getEnvironment().resolvePlaceholders(javaHome.toString()));
// 3) 解析 SpEL 表达式
System.out.println("=======================> 获取 @Value 值, 并解析#{}");
Object expression = resolver.getSuggestedValue(new DependencyDescriptor(Bean1.class.getDeclaredField("expression"), false));
System.out.println(expression); //#{'class version:' + '${java.class.version}'}
//解析${}
String v1 = getEnvironment().resolvePlaceholders(expression.toString());
System.out.println(v1);
//解析#{}
System.out.println(new StandardBeanExpressionResolver().evaluate(v1, new BeanExpressionContext(new DefaultListableBeanFactory(),null)));
}
private static Environment getEnvironment() throws IOException {
StandardEnvironment env = new StandardEnvironment();
//告诉自定义键值文件的位置
env.getPropertySources().addLast(new ResourcePropertySource("jdbc", new ClassPathResource("jdbc.properties")));
return env;
}
static class Bean1 {
@Value("hello")
private String name;
@Value("${jdbc.username}")
private String javaHome;
@Value("#{'class version:' + '${java.class.version}'}")
private String expression;
}
}
2. obtainFreshBeanFactory
代码演示:
package day04.refresh;
// 演示各种 BeanDefinition 的来源
public class TestBeanDefinition {
public static void main(String[] args) {
System.out.println("========================> 一开始");
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
System.out.println(Arrays.toString(beanFactory.getBeanDefinitionNames()));
System.out.println("========================> 1) 从 xml 获取 ");
XmlBeanDefinitionReader reader1 = new XmlBeanDefinitionReader(beanFactory);
reader1.loadBeanDefinitions(new ClassPathResource("bd.xml"));
System.out.println(Arrays.toString(beanFactory.getBeanDefinitionNames()));
System.out.println("========================> 2) 从配置类获取 ");
//通过编程的方式获得
beanFactory.registerBeanDefinition("config1", BeanDefinitionBuilder.genericBeanDefinition(Config1.class).getBeanDefinition());
//ConfigurationClassPostProcessor是bean工厂的后处理器,对bean工厂做功能增强,识别注解,将注解标注的类作为BeanDefinition添加到bean工厂
//从配置类中获得BeanDefinition
ConfigurationClassPostProcessor postProcessor = new ConfigurationClassPostProcessor();
postProcessor.postProcessBeanDefinitionRegistry(beanFactory);
System.out.println(Arrays.toString(beanFactory.getBeanDefinitionNames()));
System.out.println("========================> 3) 包扫描获取 ");
//定义包扫描器;
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(beanFactory);
scanner.scan("day04.refresh.sub"); //指定包名;对包下加了@component注解的类进行扫描,将类作为BeanDefinition添加到bean工厂;
System.out.println(Arrays.toString(beanFactory.getBeanDefinitionNames())); //打印bean工厂中有哪些bean;
}
static class Bean1 {
}
static class Bean2 {
}
static class Config1 {
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
}
beanExpressionResolver
用来解析 SpEL(解析#{}),常见实现为 StandardBeanExpressionResolverpropertyEditorRegistrars
会注册类型转换器 (spring做值注入时会将字符串类型转换成其他类型)
registerResolvableDependency
来注册 beanFactory 以及 ApplicationContext,让它们也能用于依赖注入beanPostProcessors
是 bean 后处理器集合,会工作在bean的生命周期各个阶段,做功能增强,此处会添加两个:
4. postProcessBeanFactory
BeanFactory的两个扩展点之一
5. invokeBeanFactoryPostProcessors
BeanFactory的两个扩展点之一
beanFactory
后处理器,充当 beanFactory 的扩展点,可以用来补充或修改 BeanDefinition6. registerBeanPostProcessors
AutowiredAnnotationBeanPostProcessor
功能有:解析 @Autowired,@Value 注解CommonAnnotationBeanPostProcessor
功能有:解析 @Resource,@PostConstruct,@PreDestroyAnnotationAwareAspectJAutoProxyCreator
功能有:为符合切点的目标 bean 自动创建代理package day04.refresh;
//识别@Autowired注解
beanFactory.registerBeanDefinition("processor1",
BeanDefinitionBuilder.genericBeanDefinition(AutowiredAnnotationBeanPostProcessor.class).getBeanDefinition());
//识别@Resource注解
beanFactory.registerBeanDefinition("processor2",
BeanDefinitionBuilder.genericBeanDefinition(CommonAnnotationBeanPostProcessor.class).getBeanDefinition());
//识别@Aspect、@Before注解,找到foo()方法,并自动代理;
beanFactory.registerBeanDefinition("processor3",
BeanDefinitionBuilder.genericBeanDefinition(AnnotationAwareAspectJAutoProxyCreator.class).getBeanDefinition());
context.refresh();
beanFactory.getBean(Bean1.class).foo();
}
static class Bean1 {
Bean2 bean2;
Bean3 bean3;
@Autowired
public void setBean2(Bean2 bean2) {
System.out.println("发生了依赖注入..." + bean2);
this.bean2 = bean2;
}
@Resource
public void setBean3(Bean3 bean3) {
System.out.println("发生了依赖注入..." + bean3);
this.bean3 = bean3;
}
public void foo() {
System.out.println("foo");
}
}
static class Bean2 {
}
static class Bean3 {
}
@Aspect
static class Aspect1 {
@Before("execution(* foo())")
public void before() {
System.out.println("before...");
}
}
}
messageSource
成员,实现国际化功能8. initApplicationContextEventMulticaster
9. onRefresh
11. finishBeanFactoryInitialization
conversionService
也是一套转换机制,作为对 PropertyEditor 的补充embeddedValueResolvers
即内嵌值解析器,用来解析 @Value 中的 ${ },借用的是 Environment 的功能singletonObjects
即单例池,缓存所有单例对象
lifecycleProcessor
(生命周期处理器)成员,用来控制容器内需要生命周期管理的 bean面试题: Spring bean 的生命周期
要求
bean 生命周期 概述
bean 的生命周期:从调用 beanFactory 的 doGetBean 开始,到这个 bean 被销毁,可以总结为以下七个阶段:
注意
- 划分的阶段和名称并不重要,重要的是理解整个过程中做了哪些事情
3. 处理 dependsOn
4. 选择 scope 策略
scope 理解为从 xxx 范围内找这个 bean 更加贴切
单例
多例
自定义
5.1 创建 bean - 创建 bean 实例
记忆标红的
要点 | 总结 |
---|---|
有自定义 TargetSource 的情况 | 由 AnnotationAwareAspectJAutoProxyCreator 创建代理返回 |
Supplier 方式创建 bean 实例 | 为 Spring 5.0 新增功能,方便编程方式创建 bean 实例 |
FactoryMethod 方式 创建 bean 实例 | ① 分成静态工厂与实例工厂;② 工厂方法若有参数,需要对工厂方法参数进行解析,利用 resolveDependency;③ 如果有多个工厂方法候选者,还要进一步按权重筛选 |
AutowiredAnnotationBeanPostProcessor 选择构造 | ① 优先选择带 @Autowired 注解的构造;② 若有唯一的带参构造,也会入选 |
mbd.getPreferredConstructors | 选择所有公共构造,这些构造之间按权重筛选 |
采用默认构造 | 如果上面的后处理器和 BeanDefiniation 都没找到构造,采用默认构造,即使是私有的 |
5.2 创建 bean - 依赖注入
记忆标红的
要点 | 总结 |
---|---|
AutowiredAnnotationBeanPostProcessor (注解匹配) | 识别 @Autowired 及 @Value 标注的成员,封装为 InjectionMetadata 进行依赖注入 |
CommonAnnotationBeanPostProcessor(注解匹配) | 识别 @Resource 标注的成员,封装为 InjectionMetadata 进行依赖注入 |
resolveDependency | 用来查找要装配的值,可以识别:① Optional;② ObjectFactory 及 ObjectProvider;③ @Lazy 注解;④ @Value 注解(${ }, #{ }, 类型转换);⑤ 集合类型(Collection,Map,数组等);⑥ 泛型和 @Qualifier(用来区分类型歧义);⑦ primary 及名字匹配(用来区分类型歧义) |
AUTOWIRE_BY_NAME (根据名字匹配) | 根据成员名字找 bean 对象,修改 mbd 的 propertyValues,不会考虑简单类型的成员 |
AUTOWIRE_BY_TYPE (根据类型匹配) | 根据成员类型执行 resolveDependency 找到依赖注入的值,修改 mbd 的 propertyValues |
applyPropertyValues | 根据 mbd 的 propertyValues 进行依赖注入(即xml中 ) |
依赖注入阶段-相同成员依赖注入优先级(不重要)
精确指定成员依赖那个bean > 按名字/按类型 > 用注解方式匹配
5.3 创建 bean - 初始化
要点 | 总结 |
---|---|
内置 Aware 接口的装配 | 包括 BeanNameAware,BeanFactoryAware 等 |
扩展 Aware 接口的装配 | 由 ApplicationContextAwareProcessor 解析,执行时机在 postProcessBeforeInitialization |
@PostConstruct | 由 CommonAnnotationBeanPostProcessor 解析,执行时机在 postProcessBeforeInitialization |
InitializingBean | 通过接口回调执行初始化 |
initMethod | 根据 BeanDefinition 得到的初始化方法执行初始化,即 或 @Bean(initMethod) |
创建 aop 代理 | 由 AnnotationAwareAspectJAutoProxyCreator 创建,执行时机在 postProcessAfterInitialization |
需要记的就三步:
①调用@Aware接口;
②创建初始化方法(三种);
③创建aop代理;
5.4 创建 bean - 注册可销毁 bean
在这一步判断并登记可销毁 bean
6. 类型转换处理
要求
循环依赖的产生
set 方法(包括成员变量)的循环依赖如图所示
可以在【a 创建】和【a set 注入 b】之间加入 b 的整个流程来解决
【b set 注入 a】 时可以成功,因为之前 a 的实例已经创建完毕
a 的顺序,及 b 的顺序都能得到保障
构造循环依赖的解决
public class App60_1 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(@Lazy B b) {
log.debug("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println();
}
}
public class App60_2 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private ObjectProvider<B> b;
public A(ObjectProvider<B> b) {
log.debug("A({})", b);
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println(context.getBean(A.class).b.getObject());
System.out.println(context.getBean(B.class));
}
}
public class App60_3 {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
ClassPathBeanDefinitionScanner scanner = new ClassPathBeanDefinitionScanner(context.getDefaultListableBeanFactory());
scanner.scan("com.itheima.app60.sub");
context.refresh();
System.out.println();
}
}
@Component
class A {
private static final Logger log = LoggerFactory.getLogger("A");
private B b;
public A(B b) {
log.debug("A(B b) {}", b.getClass());
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
<dependency>
<groupId>javax.injectgroupId>
<artifactId>javax.injectartifactId>
<version>1version>
dependency>
public class App60_4 {
static class A {
private static final Logger log = LoggerFactory.getLogger("A");
private Provider<B> b;
public A(Provider<B> b) {
log.debug("A({}})", b);
this.b = b;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
static class B {
private static final Logger log = LoggerFactory.getLogger("B");
private A a;
public B(A a) {
log.debug("B({}})", a);
this.a = a;
}
@PostConstruct
public void init() {
log.debug("init()");
}
}
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean("a", A.class);
context.registerBean("b", B.class);
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.refresh();
System.out.println(context.getBean(A.class).b.get());
System.out.println(context.getBean(B.class));
}
}
一级缓存
作用是保证单例对象仅被创建一次
getBean("a")
流程后,最后会将成品 a 放入 singletonObjects 一级缓存getBean("a")
流程时,先从一级缓存中找,这时已经有成品 a,就无需再次创建一级缓存与循环依赖
一级缓存无法解决循环依赖问题,分析如下
getBean("a")
a.setB()
时,需要走 getBean("b")
流程,红色箭头 1b.setA()
时,又回到了 getBean("a")
的流程,红色箭头 2二级缓存
解决思路如下:
a.setB()
以及 b.setA()
将 a 及 b 的半成品对象(未完成依赖注入和初始化)放入此缓存对于上面的图
a = new A()
执行之后就会把这个半成品的 a 放入 singletonFactories 缓存,即 factories.put(a)
a.setB()
,走入 getBean("b")
流程,红色箭头 3b.setA()
时,需要一个 a 对象,有没有呢?有!factories.get()
在 singletonFactories 缓存中就可以找到,红色箭头 4 和 5二级缓存与创建代理
二级缓存无法正确处理循环依赖并且包含有代理创建的场景,分析如下
a.init
完成之后才能创建代理 pa = proxy(a)
factories.put(a)
向 singletonFactories 中放入的还是原始对象三级缓存
简单分析的话,只需要将代理的创建时机放在依赖注入之前即可,但 spring 仍然希望代理的创建时机在 init 之后,只有出现循环依赖时,才会将代理的创建时机提前。所以解决思路稍显复杂:
factories.put(fa)
放入的既不是原始对象,也不是代理对象而是工厂对象 fab.setA()
注入的就是代理对象,保证了正确性,红色箭头 7a.init
完成后,无需二次创建代理,从哪儿找到 pa 呢?earlySingletonObjects 已经缓存,蓝色箭头 9当成品对象产生,放入 singletonObject 后,singletonFactories 和 earlySingletonObjects 就中的对象就没有用处,清除即可
面试题: Spring 事务失效的几种场景及原因
要求
1. 抛出检查异常导致事务不能正确回滚
检查异常:需要通过语法,要么在方法声明这加上throws说明要抛出检查异常,要么用try…catch…把异常捉起来,这种语法上有强制要求的对异常进行处理的异常叫检查异常;
反之叫未检查异常:RuntimeException,error这l两个异常及其子类
@Service
public class Service1 {
@Autowired
private AccountMapper accountMapper;
@Transactional
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
@Transactional(rollbackFor = Exception.class)
//只要遇到Exception及其子类都会回滚;2. 业务方法内自己 try-catch 异常导致事务不能正确回滚
@Service
public class Service2 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
try {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
原因:事务通知只有捉到了目标抛出的异常,才能进行后续的回滚处理,如果目标自己处理掉异常,事务通知无法知悉
解法1:异常原样抛出
throw new RuntimeException(e);
解法2:手动设置 TransactionStatus.setRollbackOnly()
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
调用事务状态对象,事务通知的静态方法TransactionInterceptor.currentTransactionStatus()
拿到当前事物的状态,告诉应该提交还是回滚;3. aop 切面顺序导致导致事务不能正确回滚
@Service
public class Service3 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
new FileInputStream("aaa");
accountMapper.update(to, amount);
}
}
}
@Aspect
public class MyAspect { //切面类
@Around("execution(* transfer(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
LoggerUtils.get().debug("log:{}", pjp.getTarget());
try {
return pjp.proceed();
} catch (Throwable e) {
e.printStackTrace(); //自定义切面没能正确抛出异常,自己捉住了
return null;
}
}
}
原因:事务切面优先级最低,但如果自定义的切面优先级和他一样,则还是自定义切面在内层(结果还是事务切面先执行,自定义切面后执行),这时若自定义切面没有正确抛出异常自己捉住了(最外面的事务切面不知道异常),导致事务不能正确回滚;
解法1、2:同情况2 中的解法:1、2
解法3:调整切面顺序(事务切面与自定义切面),在 MyAspect 上添加 @Order(Ordered.LOWEST_PRECEDENCE - 1)
调高优先级(不推荐)
4. 非 public 方法导致的事务失效
@Service
public class Service4 {
@Autowired
private AccountMapper accountMapper;
@Transactional //下面的方法一定要是public
void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
}
原因:Spring 为方法创建代理、添加事务通知、前提条件都是该方法是 public 的
解法1:改为 public 方法
解法2:添加 bean 配置如下(不推荐)
@Bean
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource(false); //改成false之后@Transactional就能对非公共方法也生效
}
5. 父子容器导致的事务失效
springboot中没有父子容器,也就没有这个问题,只有传统的springmvc会出现这个问题;
package day04.tx.app.service;
// ...
@Service
public class Service5 {
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) throws FileNotFoundException {
int fromBalance = accountMapper.findBalanceBy(from);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
}
控制器类
package day04.tx.app.controller;
// ...
@Controller
public class AccountController {
@Autowired
public Service5 service;
public void transfer(int from, int to, int amount) throws FileNotFoundException {
service.transfer(from, to, amount);
}
}
App 配置类
@Configuration
@ComponentScan("day04.tx.app.service")
@EnableTransactionManagement
// ...
public class AppConfig {
// ... 有事务相关配置
}
Web 配置类
@Configuration
@ComponentScan("day04.tx.app")
// ...
public class WebConfig {
// ... 无事务配置
}
现在配置了父子容器,WebConfig 对应子容器,AppConfig 对应父容器,发现事务依然失效
原因:子容器扫描范围过大,把未加事务配置的 service 扫描进来
解法1:各扫描各的,不要图简便(MVC的子容器就扫自己的controller,不要扫service,service交给父容器去扫描)
解法2:不要用父子容器,所有 bean 放在同一容器
6. 调用本类方法导致传播行为失效
@Service
public class Service6 {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
解法1
@Service
public class Service6 {
@Autowired
private Service6 proxy; // 本质上是一种循环依赖
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
System.out.println(proxy.getClass());
proxy.bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
解法2,还需要在 AppConfig 上添加 @EnableAspectJAutoProxy(exposeProxy = true)
@Service
public class Service6 {
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void foo() throws FileNotFoundException {
LoggerUtils.get().debug("foo");
((Service6) AopContext.currentProxy()).bar();
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void bar() throws FileNotFoundException {
LoggerUtils.get().debug("bar");
}
}
7. @Transactional 没有保证原子行为
@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
上面的代码实际上是有 bug 的,假设 from 余额为 1000,两个线程都来转账 1000,可能会出现扣减为负数的情况(多线程上出现了指令交错)
8. @Transactional 方法导致的 synchronized 失效
针对上面的问题,能否在方法上加 synchronized 锁来解决呢?
@Service
public class Service7 {
private static final Logger logger = LoggerFactory.getLogger(Service7.class);
@Autowired
private AccountMapper accountMapper;
@Transactional(rollbackFor = Exception.class)
public synchronized void transfer(int from, int to, int amount) {
int fromBalance = accountMapper.findBalanceBy(from);
logger.debug("更新前查询余额为: {}", fromBalance);
if (fromBalance - amount >= 0) {
accountMapper.update(from, -1 * amount);
accountMapper.update(to, amount);
}
}
public int findBalance(int accountNo) {
return accountMapper.findBalanceBy(accountNo);
}
}
答案是不行,原因如下:
synchronized 保证的仅是目标方法的原子性,环绕目标方法的还有 commit 等操作,它们并未处于 sync 块内(SQL语句是原子的,但是不包含提交,期间还是有人插队;)
面试题: SpringMVC 执行流程
要求
概要
我把整个流程分成三个阶段
准备阶段
在 Web 容器第一次用到 DispatcherServlet
的时候,会创建其对象并执行 init 方法(在传统的MVC整合spring时,由web容器也就是Tomcat来创建)
init 方法内会创建 Spring Web 容器,并调用容器 refresh 方法
refresh 过程中会创建并初始化 SpringMVC 中的重要组件, 例如 MultipartResolver,HandlerMapping,HandlerAdapter,HandlerExceptionResolver、ViewResolver 等
容器初始化后,会将上一步初始化好的重要组件,赋值给 DispatcherServlet
的成员变量,留待后用
匹配阶段
用户发送的请求统一到达前端控制器 DispatcherServlet
DispatcherServlet 遍历所有 HandlerMapping ,找到与路径匹配的处理器
① HandlerMapping 有多个,每个 HandlerMapping 会返回不同的处理器对象,谁先匹配,返回谁的处理器。其中能识别 @RequestMapping 的优先级最高
② 对应 @RequestMapping 的处理器是 HandlerMethod,它包含了控制器对象和控制器方法信息
③ 其中路径与处理器的映射关系在 HandlerMapping 初始化时就会建立好
调用阶段
由 HandlerAdapter(适配器) 调用 HandlerMethod(处理器)
① 调用前处理不同类型的参数
② 调用后处理不同类型的返回值
第 2 步没有异常
① 返回 ModelAndView
② 执行拦截器 postHandle 方法
③ 解析视图,得到 View 对象,进行视图渲染
最后都会执行拦截器的 afterCompletion 方法
如果控制器方法标注了 @ResponseBody 注解(要返回json数据啦),则在第 2 步,就会生成 json 结果,并标记 ModelAndView 已处理,这样就不会执行第 3 步的视图渲染
要求
提示
- 注解的详细列表请参考:面试题-spring-注解.xmind
- 下面列出了视频中重点提及的注解,考虑到大部分注解同学们已经比较熟悉了,仅对个别的作简要说明
事务注解
@EnableTransactionManagement
【启用声明式的事务
控制】,会额外加载 4 个 bean
@Transactional
【加在public的方法上表示方法是受事务控制哒、加在类上表示类上的所有公共方法都受事务控制】核心
@Order
【多个bean需要控制他们之间的顺序时使用,数字越小优先级越高】切面
@EnableAspectJAutoProxy
组件扫描与配置类
@Component
、@Controller
、@Service
、@Repository
【配合做组件扫描的,标注了这几个注解的类,一旦扫描到,就会纳入到spring的容器管理】
@ComponentScan
【指定一个起始包名,以这个起始包名开始去扫描它的包以及它的子孙后代包,扫描上面四个注解,扫描到了就把他们加入spring容器管理】
@Conditional
【在上面的组件扫描时做一些条件装配,符合条件的才加入到spring容器中;可以配合上面的组件扫描,也可以配合下面的@bean】
@Configuration
【加配置类】
@Bean
【标注配置类里那些方法要作为bean的Configuration定义】
@Import
【(把一些类型交给spring管理)导入其他的配置类,导入其它的Selector,根据返回的名字加入到bean的容器中】
四种用法
① 引入单个 bean
② 引入一个配置类
③ 通过 Selector 引入多个类
④ 通过 beanDefinition 注册器
package day04.boot;
public class TestImport {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
AnnotationConfigUtils.registerAnnotationConfigProcessors(context.getDefaultListableBeanFactory());
context.registerBean(MyConfig.class);
context.refresh();
for (String name : context.getBeanDefinitionNames()) {
System.out.println(name);
}
}
@Configuration
// @Import(Bean1.class) // 1. 引入单个 bean
// @Import(OtherConfig.class) // 2. 引入一个配置类
@Import(MySelector.class) // 3. 通过 Selector 引入多个类
// @Import(MyRegistrar.class) // 4. 通过 beanDefinition 注册器
static class MyConfig {
}
static class MySelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{Bean3.class.getName(), Bean4.class.getName()};
}
}
static class MyRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registry.registerBeanDefinition("bean5", BeanDefinitionBuilder.genericBeanDefinition(Bean5.class).getBeanDefinition());
}
}
static class Bean5 {
}
static class Bean3 {
}
static class Bean4 {
}
static class Bean1 {
}
@Configuration
static class OtherConfig {
@Bean
public Bean2 bean2() {
return new Bean2();
}
}
static class Bean2 {
}
}
解析规则
package day04.boot;
public class TestDeferredImport {
public static void main(String[] args) {
GenericApplicationContext context = new GenericApplicationContext();
DefaultListableBeanFactory beanFactory = context.getDefaultListableBeanFactory();
beanFactory.setAllowBeanDefinitionOverriding(false); // 不允许同名定义覆盖,默认为true;
AnnotationConfigUtils.registerAnnotationConfigProcessors(beanFactory);
context.registerBean(MyConfig.class);
context.refresh();
System.out.println(context.getBean(MyBean.class));
}
// 1. 同一配置类中, @Import 先解析 @Bean 后解析
// 2. 同名定义, 默认后面解析的会覆盖前面解析的
// 3. 不允许覆盖的情况下, 如何能够让 MyConfig(主配置类) 的配置优先? (虽然覆盖方式能解决)
// 4. DeferredImportSelector 最后工作, 可以简单认为先解析 @Bean, 再 Import
@Configuration
@Import(MySelector.class)
static class MyConfig { // 主配置 - 程序员编写的
@Bean
public MyBean myBean() {
return new Bean1();
}
}
static class MySelector implements DeferredImportSelector {
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
return new String[]{OtherConfig.class.getName()};
}
}
@Configuration
static class OtherConfig { // 从属配置 - 自动配置
@Bean
@ConditionalOnMissingBean
public MyBean myBean() {
return new Bean2();
}
}
interface MyBean {
}
static class Bean1 implements MyBean {
}
static class Bean2 implements MyBean {
}
}
@Lazy
@PropertySource
【读取外部的Properties文件,作为键值信息加入到(因外润门特)】
依赖注入
@Autowired
【加在方法参数上,加在成员变量上完成依赖注入】@Qualifier
【依赖注入式,如果同一类型有多个bean,@Qualifier会根据名字进一步区分】@Value
【值注入,配合#{},${}完成内嵌值注入】mvc mapping 【与路径映射相关的注解】
@RequestMapping
,可以派生多个注解如 @GetMapping 等;【建立请求路径跟控制器方法之间的映射关系,当一个请求过来以后根据请求的路径跟@RequestMapping中的路径建立匹配,匹配上了就知道应该由标注了@RequestMapping注解的方法来处理请求;当@RequestMapping加在类上,当类上有多个控制器方法,有相同的路径前缀时,把相同的路径前缀提取出来,放在类上的@RequestMapping里】mvc rest
@RequestBody
:【处理请求体中的json数据的,把json数据转换为Java的对象】@ResponseBody
:【把控制器返回的Java对象转换为json数据,写入到响应体】,组合 @Controller => @RestController@ResponseStatus
:【可以控制响应的状态码】mvc 统一处理
@ControllerAdvice
:【全局的异常处理,可以把做统一处理的异常方法、转换器方法都可以放在其标注的类中,达到统一处理的目的】,组合 @ResponseBody => @RestControllerAdvice@ExceptionHandler
:【将方法上标注该注解,表明该注解是处理异常的方法】mvc 参数
@PathVariable
:【获取请求路径中的参数值】mvc ajax
@CrossOrigin
:【解决ajax的跨域问题,原理是往响应头上面加一些特殊的头,允许ajax跨域请求】boot auto
@SpringBootApplication
:【springboot程序都要假的注解,是一个组合注解,其中就包含下面的两个外加@ComponenScan】@EnableAutoConfiguration
@SpringBootConfiguration
boot condition
@ConditionalOnClass
,classpath(类路径)下存在某个 class(类) 时,条件才成立@ConditionalOnMissingBean
,beanFactory(容器) 内不存在某个 bean 时,条件才成立@ConditionalOnProperty
,配置文件中某个(键、值)与其某个值匹配时时,条件才成立boot properties
@ConfigurationProperties
,会将当前 bean 的属性与配置文件中的键值进行绑定【键的名称和属性名称相同的,就将值赋给属性,简化了bean的初始化】@EnableConfigurationProperties
,【启用上面注解的功能】会添加两个较为重要的 bean
要求
自动配置原理
@SpringBootConfiguration 是一个组合注解,由 @ComponentScan
、@EnableAutoConfiguration
和 @SpringBootConfiguration
组成
@SpringBootConfiguration
与普通 @Configuration
相比,唯一区别是前者要求整个 app 中只出现一次
@ComponentScan
@EnableAutoConfiguration
也是一个组合注解,由下面注解组成
@AutoConfigurationPackage
– 用来记录它所标注的类所在的包META-INF/spring.factories
中的自动配置类为什么不使用 @Import 直接引入自动配置类
有两个原因:
@Import(自动配置类.class)
,引入的配置解析优先级较高,自动配置类的解析应该在主配置没提供时作为默认配置因此,采用了 @Import(AutoConfigurationImportSelector.class)
AutoConfigurationImportSelector.class
去读取 META-INF/spring.factories
中的自动配置类,实现了弱耦合。AutoConfigurationImportSelector.class
实现了 DeferredImportSelector 接口,让自动配置的解析晚于主配置的解析面试题:spring中的Singleton Bean是否是单例模式
要求
1. Spring 中的 Singleton
请大家区分 singleton pattern 与 Spring 中的 singleton bean
2. Spring 中的 Builder【建造器模式】
定义 Separate the construction of a complex object from its representation so that the same construction process can create different representations
它的主要亮点有三处:
较为灵活的构建产品对象
在不执行最后 build 方法前,产品对象都不可用
构建过程采用链式调用,看起来比较爽
Spring 中体现 Builder (构建器)模式的地方:
org.springframework.beans.factory.support.BeanDefinitionBuilder
org.springframework.web.util.UriComponentsBuilder
org.springframework.http.ResponseEntity.HeadersBuilder
org.springframework.http.ResponseEntity.BodyBuilder
3. Spring 中的 Factory Method(工厂方法模式)
定义 Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses
根据上面的定义,Spring 中的 ApplicationContext 与 BeanFactory 中的 getBean 都可以视为工厂方法,它隐藏了 bean (产品)的创建过程和具体实现
Spring 中其它工厂:
org.springframework.beans.factory.FactoryBean
@Bean 标注的静态方法及实例方法
ObjectFactory 及 ObjectProvider
前两种工厂主要封装第三方的 bean 的创建过程,后两种工厂可以推迟 bean 创建,解决循环依赖及单例注入多例等问题
4. Spring 中的 Adapter(适配器模式)
定义 Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces
典型的实现有两处:
5. Spring 中的 Composite(组合模式)
定义 Compose objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objects and compositions of objects uniformly
典型实现有:
org.springframework.web.method.support.HandlerMethodArgumentResolverComposite
org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite
org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
org.springframework.web.servlet.view.ViewResolverComposite
composite 对象的作用是,将分散的调用集中起来,统一调用入口,它的特征是,与具体干活的实现实现同一个接口,当调用 composite 对象的接口方法时,其实是委托具体干活的实现来完成
6. Spring 中的 Decorator(装饰器模式)
定义 Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality
典型实现:
7. Spring 中的 Proxy(代理模式)
定义 Provide a surrogate or placeholder for another object to control access to it
装饰器模式注重的是功能增强,避免子类继承方式进行功能扩展,而代理模式更注重控制目标的访问
典型实现:
8. Spring 中的 Chain of Responsibility(责任链)
定义 Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it
典型实现:
9. Spring 中的 Observer(观察者模式)
定义 Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically
典型实现:
10. Spring 中的 Strategy(策略模式)
定义 Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it
典型实现:
11. Spring 中的 Template Method(模板方法)
定义 Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm’s structure
典型实现: