Spring常见面试题总结

一. 概述

1.Spring是什么?

Spring是一个轻量级的开发框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。

2.Spring的一些重要模块
  • Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IOC 依赖注入功能。
  • Spring Aspects : 该模块为与AspectJ的集成提供支持。
  • Spring AOP :提供了面向方面的编程实现。
  • Spring JDBC : Java数据库连接。
  • Spring JMS :Java消息服务。
  • Spring ORM : 用于支持Hibernate等ORM工具。
  • Spring Web : 为创建Web应用程序提供支持。
  • Spring Test : 提供了对 JUnit 和 TestNG 测试的支持。
¥3. Spring 的优点
  1. spring属于低侵入式设计,代码的污染极低;
  2. spring的DI机制将对象之间的依赖关系交由框架处理减低组件的耦合性
  3. Spring提供了AOP技术,支持将一些通用任务,如安全、事务、日志、权限等进行集中式管理,从而提供更好的复用。
  4. spring对于主流的应用框架提供了集成支持。
¥4.Spring 框架中都用到了哪些设计模式?
  • 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
  • 单例模式:Bean默认为单例模式。
  • 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
  • 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
  • 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。
¥5. Spring框架中有哪些不同类型的事件

Spring 提供了以下5种标准的事件:

  • 上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。
  • 上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。
  • 上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。
  • 上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。
  • 请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

二. SpringIOC

¥1.对Spring的IOC的理解,IOC的意义
  • IoC(Inverse of Control:控制反转)是一种设计思想,就是将原本在程序中手动创建对象的控制权,交由Spring框架来管理
  • IoC 容器就像是一个工厂一样,当我们需要创建一个对象的时候,只需要配置好配置文件/注解即可,完全不用考虑对象是如何被创建出来的。 在实际项目中一个 Service 类可能有几百甚至上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可能会把人逼疯。如果利用 IoC 的话,你只需要配置好,然后在需要的地方引用就行了,这大大增加了项目的可维护性且降低了开发难度。
¥2. Spring 中的 bean 的作用域有哪些?
  1. singleton:单例的,每次容器返回的对象是同一个(默认作用域)。
  2. prototype :多例的,每次返回的对象是新创建的实例。
  3. request:仅作用于HttpRequest,每次Http请求都会创建一个新的bean。
  4. session:仅作用于HttpSession,不同的Session使用不同的实例,相同的Session使用同一个实例。
  5. global session :仅作用于HttpSession,所有的Session使用同一个实例。
3. @Component 和 @Bean 的区别是什么?
  1. 作用对象不同: @Component 注解作用于类,而@Bean注解作用于方法。
  2. @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的示例,当我需要用它的时候还给我。
  3. @Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现
4. Spring框架中的单例Beans是线程安全的么?Spring如何处理线程并发问题?
  • Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行处理。
  • 但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。

解决方案:

  1. 将多态bean的作用域由“singleton”变更为“prototype”。(不同线程操作不同对象,自然没有线程安全问题)
  2. 在Bean对象中尽量避免定义可变的成员变量
  3. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。
5.依赖注入的方式,注入注解的差别
  • 构造器注入
  • Set方法注入
  • 注解注入:
  1. @Autowired自动按类型注入,如果有多个匹配则按照指定bean的id查找, 默认情况下它要求依赖对象必须存在(可以设置它required属性为false);@Qualifier在自动按照类型注入的基础之上,再按照 Bean 的 id 注入,给成员变量注入时必须搭配@Autowired,给方法注入时可单独使用;
  2. @Resource直接按照 Bean 的 id 注入;只有当找不到与名称匹配的bean才会按照类型来装配注入。
  3. @Value用于注入基本数据类型和String。
6. 构造器依赖注入和 Setter方法注入的区别
  • 构造函数注入:没有部分注入,不会覆盖 setter 属性,任意修改都会创建一个新实例,适用于设置很多属性
  • setter 注入:有部分注入,会覆盖 setter 属性,任意修改不会创建一个新实例,适用于设置少量属性
¥7. 单例bean如何引用多例bean
  • 如果使用@Autowired将多例bean注入单例bean中,该单例bean保存了一个创建好的多例bean的引用;但每次使用该单例bean时都去使用其保存好的那个多例bean,因此每次使用的多例bean都是同一个
  • 解决方法:
  1. spring官方使用@Lookup或标签,解决单例引用多例的问题
  2. 阿里官方推荐,这种方法最暴力,多例bean直接不使用spring管理,让用户调用方法时,自己new一个,这样每次使用的都是新创建的bean。这种做法即完全脱离spring管理,用户自己负责实例的生命周期
¥8. 如何指定Bean的初始化和销毁方法

初始化方法会在Bean实例化且赋值完毕后调用,销毁方法会在容器关闭时调用(单例Bean)

  1. 在@Bean注解的initMethod和destroyMethod属性中指定
  2. 实现InitialzingBean接口定义初始化方法,实现Disposable接口定义销毁方法
  3. @PostConstruct注解定义初始化方法,@PreDestroy定义销毁方法

初始化和销毁方法可用于获取/关闭资源

9. Aware接口的作用和使用
  • BeanNameAware、BeanFactoryAware、ApplicationContextAware
    学名叫感知器,可以让bean能感知到整个容器上下文信息的接口。
  • 让Bean实现这几个接口,即可在该Bean中注入BeanFactory,ApplicationContext,BeanName从而感知到容器中的其他Bean
public class LifeCycleBean implements BeanNameAware, 
BeanFactoryAware, ApplicationContextAware{
@Override
	public void setBeanName(String name) {
		System.out.println("BeanNameAware被调用, 获取到的beanName:" + name);
	}

	@Override
	public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
		this.beanFactory = beanFactory;
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}

  • 实现原理:spring在创建过程中,AwareProcessor发现该Bean实现了Aware接口,则回调子类的setBeanName, setBeanFactory, setApplicationContext,将注入BeanFactory,ApplicationContext,BeanName注入Bean中
¥10. Spring能解决什么状况下的循环依赖,不能解决什么状况下的循环依赖,为什么?
  • spring只能解决setter注入单例模式下的循环依赖问题
  • 不能解决构造器注入和原型模式下的循环依赖
  • 因为解决循环依赖必须要满足2个条件:
  1. 需要用于提前曝光的缓存
  2. 属性的注入时机必须发生在提前曝光动作之后,不管是填充还是初始化都行,总之不能在实例化,因为提前曝光动作在实例化之后
  • 构造器注入,实例化的过程是调用new A(B b);的过程,这时的A还未创建出来,根本是不可能提前曝光的,因此无法获取到三级缓存,进而导致⑩异常的抛出
  • 原型模式每次都是重新生成一个全新的bean,根本没有缓存一说。这将导致实例化A完,填充发现需要B,实例化B完又发现需要A,而每次的A又都要不一样,所以死循环的依赖下去。唯一的做法就是利用循环依赖检测,发现原型模式下存在循环依赖并抛出异常

三. SpringIOC源码剖析

¥1. SpringIOC运行原理简述

spring IoC主要分为两个阶段:

  1. 将XML,注解类等配置文件读入到spring容器内生成一个个beanDefinition,并最终将所有的beanDefinition注册到一个BeanDefinitionMap中
  2. 根据BeanDefinition对Bean的定义对Bean进行装配,交付给用户
¥2. 什么是BeanDefinition

BeanDefinition用于管理各种对象以及对象间的依赖关系,是对依赖关系的数据抽象

3. 配置文件的解析

运行原理中的第一阶段,根据配置方式的不同有不同的解析实现:

  • XmlBeanFactory解析过程包括将XML文件转换为Resource——>Resource经过编码通过getInputStream获取文件流——>读取文件流转化成DOM树——>将DOM树解析为BeanDefinition——>将BeanDefinition注册到BeanDefinitionMap中
  • 注解方式的解析方式更为复制,其在容器中添加了一个BeanFactory的后置处理器,并在执行invokeBeanFactoryPostProcess()时完成配置类的解析,且解析过程中还创建了配置类的代理类

配置文件的解析了解即可,更重要的第二阶段,如何对bean进行装配(依赖注入)

4. DefaultListableBeanFactory
  • DefaultListableBeanFactory是整个bean加载的核心部分,是Spring注册及加载bean的默认实现
  • XmlBeanFactory继承自DefaultListableBeanFactory,不同的地方其实是在XmlBeanFactory中使用了自定义的XML读取器XmlBeanDefinitionReader,实现了个性化的BeanDefinitionReader读取
  • ApplicationContext中容器的实现也是DefaultListableBeanFactory,但是添加了许多额外的功能
¥5. BeanFactory和FactoryBean,ApplicationContext的区别?
  • BeanFactory和ApplicationContext的区别
  1. ①BeanFactory和ApplicationConext都是IOC容器,用来管理bean的IOC容器或对象工厂,但BeanFactory只实现了最基本但容器功能,如读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系;ApplicationConext是BeanFactory的子接口,对其功能进行了扩展,如支持不同的信息源,应用事件
  2. BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。
    ApplicationContext,它是在容器启动时,一次性创建了所有的Bean(懒加载和多例bean除外)。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。
  3. 相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。
  • FactoryBean是一个向容器中配置Bean的接口可以生产或者装饰对象的工厂Bean,可以通过实现该接口自定义的实例化Bean的逻辑。
6. ApplicationContext的初始化流程

ApplicationContext的refresh()方法流程:

  1. prepareRefresh() :刷新前的预处理
  2. obtainFreshBeanFactory(): 获取BeanFactory,Xml配置文件则在该步完成将配置文件解析成BeanDefinition
  3. prepareBeanFactory(beanFactory):BeanFactory预准备工作(添加部分后置处理器等)
  4. postProcessBeanFactory(beanFactory):空方法,用于后序实现
  5. invokeBeanFactoryPostProcessors(beanFactory):执行BeanFactory的后置处理器,注解方式将配置类解析成BeanDefinition在这步实现加粗样式
  6. registerBeanPostProcessors(beanFactory):注册Bean的后置处理器
  7. initMessageSource():初始化MessageSource组件(用于国际化)
  8. initApplicationEventMulticaster():初始化事件派发器
  9. onRefresh():留给子容器重写
  10. registerListeners() :注册监听器
  11. finishBeanFactoryInitialization(beanFactory):初始化所有非懒加载单实例Bean,重中之重!即上面提到的第二阶段
  12. 完成容器初始化创建工作
7. Bean依赖注入流程

在finishBeanFactoryInitialization(beanFactory)中,会迭代获取所有的beanName,依次对各个单例Bean进行依赖注入,流程如下:

  1. 判断是否为FactoryBean,如果是,则通过FactoryBean的方式生产Bean
  2. 尝试从缓存(singletonObjects)中获取,第一次获取时还未创建,自然获取不到,则启动单实例bean的创建流程singletonObject = singletonFactory.getObject();
  3. 实例化阶段
    1. resolveBeforeInstantiation(beanName, mbdToUse);实例化前置处理,如果该步创建了代理对象则返回(一般不会在这里创建)
    2. createBeanInstance(beanName, mbd, args);通过反射创建出该bean实例
    3. applyMergedBeanDefinitionPostProcessors(mbd, beanType, beanName);beanDefinition合并后的后置处理,比如@Autowired在属性上,就是在这里解析的,但是仅仅只是单纯的解析,还没有实现注入
  4. populateBean(beanName, mbd, instanceWrapper);属性填充阶段
    1. 拿到后置处理器InstantiationAwareBeanPostProcessor
    2. 执行postProcessProperties:@Autowired在属性上,就是在这里注入的
    3. 执行postProcessPropertyValues:bean属性的后置处理器
    4. applyPropertyValues(beanName, mbd, bw, pvs);利用反射对属性进行赋值
  5. 初始化阶段
    1. invokeAwareMethods(beanName, bean);回调各类Aware的set方法,实现注入
    2. applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);执行后置处理器的初始化前置处理
    3. invokeInitMethods(beanName, wrappedBean, mbd); 触发自定义初始化方法
    4. applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);执行后置处理器的初始化后置处理
  6. 将创建好的Bean添加到SingletonObjects中
¥ 7.Spring中bean对象的生命周期
  1. Spring对bean进行实例化
  2. bean属性赋值和依赖注入
  3. 处理Aware接口(invokeAwareMethod)
  4. 调用BeanPostProcessor的post-ProcessBeforeInitialization方法。
  5. bean的初始化
  6. 调用BeanPostProcessor的post-ProcessAfterInitialization方法。
  7. 将bean放入IOC容器以供使用。
  8. 关闭容器时,调用DisposableBean的destroy方法,如果bean使用destroy-method声明了自定义销毁方法,该方法也会被调用。

在整个实例化期间涉及到9次BeanPostProcessor的调用在各个时期对bean进行增强

8. 后置处理器BeanPostProcessor及其生命周期

spring使用模板模式,在bean的创建过程中安插了许多锚点,用户寻找对应的锚点,通过重写BeanPostProcessor的各个方法介入到bean的创建过程当中,从而对Bean实现增强

后置处理器可以插入的位置:

  1. postProcessBeforeInstantiation被调用
  2. 构造方法被调用
  3. postProcessAfterInstantiation被调用
  4. postProcessProperties被调用
  5. BeanNameAware,BeanFactoryAware,ApplicationContextAware被调用,
  6. postProcessBeforeInitialization被调用
  7. afterPropertiesSet被调用
  8. myInit自定义初始化方法被调用,
  9. postProcessAfterInitialization被调用
  10. bean创建完成 name
  11. DisposableBean被调用
  12. destroy-method自定义销毁方法被调用

除此之外BeanPostProcessor还有很多实现类,可以完成其他各个位置处Bean的增强

9.¥¥Spring如何解决循环依赖

Spring通过提前曝光机制,利用三级缓存解决循环依赖

三级缓存分别是:

  • singletonObject:一级缓存,该缓存key = beanName, value = bean;这里的bean是已经创建完成的,该bean经历过实例化->属性填充->初始化以及各类的后置处理。因此,一旦需要获取bean时,我们第一时间就会寻找一级缓存
  • earlySingletonObjects:二级缓存,该缓存key = beanName, value = bean;这里跟一级缓存的区别在于,该缓存所获取到的bean是提前曝光出来的,是还没创建完成的。也就是说获取到的bean只能确保已经进行了实例化,但是属性填充跟初始化肯定还没有做完,因此该bean还没创建完成,仅仅能作为指针提前曝光,被其他bean所引用
  • singletonFactories:三级缓存,该缓存key = beanName, value = beanFactory;在bean实例化完之后,属性填充以及初始化之前,如果允许提前曝光,spring会将实例化后的bean提前曝光,也就是把该bean转换成beanFactory并加入到三级缓存在需要引用提前曝光对象时再通过singletonFactory.getObject()获取。
    在这里插入图片描述
¥10. Spring通过提前曝光,直接曝光到二级缓存已经可以解决循环依赖问题了,为什么一定要三级缓存?
  • 三级缓存是一个工厂类,通过该工厂类获取提前曝光的bean时会获取AOP后置处理器对需要代理的类进行代理

因此需要三级缓存的原因

  1. 由于提前曝光在AOP代理之前,因此需要通过三级缓存的工厂来对bean进行提前AOP代理
  2. 另外,所有的单例bean在实例化后都会被进行提前曝光到三级缓存中,但是曝光的时候并不提前AOP代理,只有曝光,且被提前引用的时候才调用确保了被提前引用这个时机触发(如果没有三级缓存,则必须要提前代理好了直接放入二级缓存中,没这必要)
¥11. 如果循环依赖的时候,所有类又都需要Spring AOP自动代理,那Spring如何提前曝光?曝光的是原始bean还是代理后的bean?
  • 在三级缓存的工厂类bean进行了提前代理,曝光的已经是代理后的bean,并缓存了提前代理的bean;
  • 后续再进行AOP代理时,如果在缓存中发现已经进行过提前代理,那么就不再进行重复AOP代理
  • 返回bean时如果已经进行了提前代理,则将二级缓存中提前代理过的bean返回给容器
  • 从而保证已经被AOP代理的类不会进行第二次AOP代理,且返回的Bean也是代理后的bean
	// AbstractAutoProxyCreator.java
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			// 获取缓存key
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			/// 查看该bean是否被Spring AOP提前代理!而缓存的是原始的bean,因此如果bean被提前代理过,这此处会跳过
			// 如果bean没有被提前代理过,null != bean 则进入AOP代理
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}
if (earlySingletonExposure) {
			// earlySingletonReference:二级缓存,缓存的是经过提前曝光提前Spring AOP代理的bean
			Object earlySingletonReference = getSingleton(beanName, false);
			if (earlySingletonReference != null) {
				// exposedObject跟bean一样,说明初始化操作没用应用Initialization后置处理器(指AOP操作)改变exposedObject
				// 主要是因为exposedObject如果提前代理过,就会跳过Spring AOP代理,所以exposedObject没被改变,也就等于bean了
				if (exposedObject == bean) {
					// 将二级缓存中的提前AOP代理的bean赋值给exposedObject,并返回
					exposedObject = earlySingletonReference;
				}
				........
12. 提前曝光的bean怎么拥有后面属性注入的依赖bean的引用
  • JDK动态代理时,会将目标对象target保存在最后生成的代理$proxy中,当调用$proxy方法时会回调h.invoke,而h.invoke又会回调目标对象target的原始方法。
  • 因此,其实在Spring AOP动态代理时,原始bean已经被保存在提前曝光代理中了。而后原始Bean继续完成属性填充和初始化操作。因为AOP代理$proxy中保存着traget也就是是原始bean的引用,因此后续原始bean的完善,也就相当于Spring AOP中的target的完善,这样就保证了Spring AOP的属性填充与初始化了
¥13. Spring循环依赖全过程

在这里插入图片描述

14. 怎么才能知道Spring的IOC容器已经完全初始化了

Spring的IOC容器完全初始化后,会发布一个ContextRefreshedEvent事件

可以自定义一个监听器监听该事件,当监听器方法被回调时说明IOC容器已经完全初始化

@Component
public class ListenerTest implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        System.out.println("容器初始化完毕");
    }
}

四. SpringAOP

¥1. 什么是AOP?为什么要使用AOP

AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性

2. Spring Aop的相关术语
  • Joinpoint(连接点):指那些被拦截到的点,在 spring 中这些点指的是方法,因为 spring 只支持方法类型的连接点。
  • Pointcut(切入点):指我们要对哪些 Joinpoint 进行拦截的定义。例如业务层实现类中被增强的方法都是切入点,切入点一定是连接点,但连接点不一定是切入点。
  • Advice(通知/增强):指拦截到 Joinpoint 之后所要做的事情。
  • Introduction(引介):引介是一种特殊的通知,在不修改类代码的前提下可以在运行期为类动态地添加一些方法或 Field。
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
  • Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类。
  • Target(目标):代理的目标对象。
  • Aspect(切面):是切入点和通知(引介)的结合。
3.Spring Aop有哪些通知类型及相关注解?
  • @Before前置通知
  • @AfterThrowing异常通知:方法抛出异常
  • @AfterReturning返回通知:方法必须正确返回结果
  • @After后置通知:无论是否正常执行都会执行
  • @Around环绕通知
4. SpringAOP和AspectJ的区别和联系

区别:

  • AOP是一种编程思想,而SpringAOP和AspectJ都是实现AOP的方式
  • Spring AOP 属于运行时增强,而 AspectJ 是编译时增强Spring AOP 基于代理,而 AspectJ 基于字节码操作
  • AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单

联系:

  • Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。,
  • 通过@EnableAspectJAutoProxy使得Spring AOP支持AspectJ 风格的语法
5. 静态代理与动态代理的区别
  • AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。
  • Spring AOP使用的动态代理,在每次运行时在内存中通过操作字节码临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法
  • 静态代理与动态代理区别在于生成AOP代理对象的时机不同,相对来说AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

五. SpringAOP原理及源码剖析

1. ¥Spring Aop的基本原理

Spring AOP是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,去创建代理对象,而对于没有实现接口的对象,Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理

2. JDK动态代理的使用

动态代理实现步骤:

  1. 创建一个实现接口InvocationHandler的类,它必须实现 invoke(Object proxy, Method method, Object[] args)方法

第一个参数obj一般是指代理类,method是被代理的方法,如上例中的request(),args为该方法的参数数组

  1. 创建被代理的类以及接口
  2. 通过Proxy的静态方法
    newProxyInstance(ClassLoaderloader, Class[] interfaces, InvocationHandler h)创建一个代理

第一个参数是类加载器,第二个参数是代理接口的class文件,第三个接口是上面实现的InvocationHandler

  1. 通过代理调用方法
TargetInterface newProxyInstance = (TargetInterface) Proxy.newProxyInstance(Target.class.getClassLoader(), 
								new Class[]{TargetInterface.class}, 
								new InvocationHandler(){
								 	    @Override
									    public Object invoke(Object proxy, Method method, Object[] args){
									    	return method.invoke(new Target(), args);
									    }
);

¥3. JDK动态代理的原理
  1. 根据接口信息为其方法生成字节码生成代理类的字节码(.class)
    • 该字节码反编译后的类
      1. 继承自Proxy并实现了代理的接口
      2. 在构造器中传入了创建的InvocationHandler h
      3. 对接口方法的实现变为this.h.invoke(this, m, new Object[] { paramString });即通过代理类调用接口的方法实际上是去调用InvocationHandler中的invoke实现,从而实现了代理功能
  2. 通过传入的类加载器加载该字节码生产Class对象
  3. 通过反射该Class对象生成代理类,将InvocationHandler作为构造器参数传入
¥4. JDK动态代理和CGLIB动态代理
  1. JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,Proxy为目标类生成一个继承自Proxy符合某一接口的代理对象并在调用目标方法时通过传入的InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起。
  2. 如果代理类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理(生成的代理对象继承自目标对象)。
¥5. AOP进行代理的时机
  • 当自定义了TargetSource,则在bean实例化前完成Spring AOP代理并且直接发生短路操作,返回bean
  • 正常情况下,都是在bean初始化后进行Spring AOP代理
  • 遇到循环依赖时,在三级缓存中提前曝光代理
¥6.¥AOP原理详解**
  1. @EnableAspectJAutoProxy向容器中导入AnnotationAwareAspectJAutoProxyCreator组件
  2. 该组件实质为一后置处理器,对postProcessAfterInitialization方法进行了实现,当bean完成初始化后调用该方法开始AOP代理
  3. 该方法中调用wrapIfNecessary()方法;
    1. 获取拦截器链(先获取所有Advisor,然后在所有Advisor中挑选适用的Advisor(该bean的拦截器,利用pointcut进行匹配))
    2. 创建代理对象
      1. 将Target(目标对象)、Interface(代理接口)、Advice(增强)保存在AdvisedSupport中
      2. 根据目标对象对类型选择具体的动态代理策略JdkDynamicAopProxy(JDK或Cglib,这里以JDK为例)
      3. JdkDynamicAopProxy根据AdvisedSupport中保存的属性创建代理对象Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);,这里传入this是因为JdkDynamicAopProxy实现了InvocationHandler,并实现了invoke()方法
      4. invoke()主要包括三个步骤:
        1. 获取之前创建的该类的拦截器链
        2. 将拦截器统一封装成ReflectiveMethodInvocation
        3. 执行拦截器链
  4. 返回代理对象
  5. 对目标方法进行调用时,实质执行JdkDynamicAopProxy的invoke()方法,即执行拦截器链
  6. 在拦截器链中通过责任链设计模式来保证各个通知的执行顺序
7. Cglib是怎么操作字节码的,ASM操作字节码的原理

CGLIB包的底层是通过使用一个小而快的 字节码处理框架ASM来转换字节码并生成新的类。除了CGLIB包, 脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的 字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

ASM可以以二进制的方式创建一个类的字节码,对于ClassWriter的每一个方法的调用会创建类的相应部分,例如调用visit方法会创建一个类的声明部分,调用visitMethod方法就会在这个类中创建一个新的方法,调用visitEnd方法表明对于该类的创建已经完成了。它最终会通过toByteArray方法返回一个数组,这个数组包含了整个class文件的完整字节码内容。

六. Spring声明式事务

1. Spring中的事务隔离级别
  1. ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别
  2. ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。
  3. ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。
  4. ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。
  5. ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新。
¥2.Spring的事务传播行为

Spring事务的传播行为指多个事务同时存在的时候,spring如何处理这些事务的行为

  • 支持当前事务的情况:
  1. PROPAGATION_REQUIRED(默认):如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。
  2. PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘
  3. PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常
  • 不支持当前事务的情况:
  1. PROPAGATION_REQUIRES_NEW:创建新事务,如果当前存在事务,则把当前事务挂起。
  2. PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  3. PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
  • 其他情况:
  1. PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。
  • 在外部方法未开启事务的情况下Propagation.NESTED和Propagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
  • 如果外部方法开启事务的话,Propagation.NESTED修饰的内部方法属于外部事务的子事务,外部主事务回滚的话,子事务也会回滚,而内部子事务可以单独回滚而不影响外部主事务和其他子事务
2.5 require和require_new的区别

在这里插入图片描述

现在在事务1中包含事务2和事务3,事务2的传播属性是REQUIRED_NEW,事务3的传播属性是REQUIRED

事务1开始执行,当执行到事务2,若事务2发生异常,则事务2自己回滚,换句话说,事务1不会回滚到自己的初始状态,而是由事务2自己回滚到自己的初始状态。

接下来继续执行事务3,由于事务3的传播属性是REQUIRED,所以当事务3发生异常时,事务1会回滚到初始状态

3. @Transactional(rollbackFor = Exception.class)注解了解吗?
  • 当@Transactional注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。如果类或者方法加了这个注解,那么这个类里面的方法抛出异常,就会回滚,数据库里面的数据也会回滚。
  • 在@Transactional注解中如果不配置rollbackFor属性,那么事务只会在遇到RuntimeException的时候才会回滚,加上rollbackFor=Exception.class,可以让事务在遇到非运行时异常时也回滚。
4. @Transactional在什么场景下会失效
  1. @Transactional 应用在非 public 修饰的方法上:在Spring AOP 代理时,会检查目标方法的修饰符是否为 public,不是 public则不会获取@Transactional 的属性配置信息。
  2. @Transactional 注解属性 propagation 设置错误
  3. @Transactional 注解属性 rollbackFor 设置错误:Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务
  4. 同一个类中方法调用,导致@Transactional失效:由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
  5. 异常被 catch捕获导致@Transactional失效
  6. 数据库引擎不支持事务
5. 事务的超时属性

timeout 事务的超时时间,默认值为-1(不会超时)。如果超过该时间限制但事务还没有完成,则自动回滚事务

6. 事务只读属性
  • 对于只有读取数据查询的事务,可以指定事务类型为 readonly,即只读事务。只读事务不涉及数据的修改,数据库会提供一些优化手段,适合用在有多条数据库查询操作的方法中。
  • 适用场景:如果你一次执行多条查询语句,例如统计查询,报表查询,在这种场景下,多条查询 SQL 必须保证整体的读一致性,否则,在前条 SQL 查询之后,后条 SQL 查询之前,数据被其他用户改变,则该次整体的统计查询将会出现读数据不一致的状态,此时,应该启用事务支持
7. @Transactional注解的实现原理
  1. @EnableTransactionalManagement注解给IOC容器中注册了InfrastrutureAdvisorAutoProxyCreator后置处理器和BeanFactoryTransactionAttributeSourceAdvisor事务增强器
  2. 在bean完成初始化后,该后置处理器会去判断该类是否添加了@Transactional注解,如果添加了,则把增强器添加到该bean的拦截器链中
  3. 在执行目标方法时,会调用增强器的TransactionInterceptor的invoke方法,在其实现逻辑中,先开启事务,然后执行目标方法,如果出现异常,则获取到事务管理器进行回滚操作,如果正常执行,则利用事务管理器提交事务

七. SpringBoot

1. Spring Boot 的优点
  1. Spring Boot 不需要编写大量配置文件,可以自动导入组件和默认配置,简化开发,提升效率。
  2. Spring 引导应用程序可以很容易地与Spring 生态系统集成,只需导入相关的Spring Boot Starters即可。
  3. Spring Boot 应用程序提供嵌入式 HTTP 服务器,如 Tomcat 和 Jetty,可以轻松地开发和测试 web 应用程序。
2. 什么是 Spring Boot Starters?
  • Spring Boot Starters 是一系列依赖关系的集合,通过导入Starter依赖可直接导入该服务所需的所有依赖。
  • Spring Boot Starters包含六该服务的组件和默认配置,做到开箱即用
3. Spring Boot 常用的两种配置文件
  • 可以通过 application.properties或者 application.yml 对 Spring Boot 程序进行简单的配置。如果,你不进行配置的话,就是使用的默认配置。
  • YAML 是一种人类可读的数据序列化语言。相比于 Properties 配置的方式,YAML 配置的方式更加直观清晰,简介明了,有层次感。
4. Spring Boot 常用的读取配置文件的方法
  1. 通过 @value 读取比较简单的配置信息
@Value("${name}")
String name;
  1. 通过@ConfigurationProperties读取并与 bean 绑定
@ConfigurationProperties(prefix = "library")
  1. @PropertySource读取指定的 properties 文件
@PropertySource("classpath:website.properties")
¥5. @SpringBootApplication 注解
package org.springframework.boot.autoconfigure;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
		@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
   ......
}

可以把 @SpringBootApplication看作是 @Configuration、@EnableAutoConfiguration、@ComponentScan 注解的集合。这三个注解的作用分别是:

  • @EnableAutoConfiguration:启用 SpringBoot 的自动配置机制
  • @ComponentScan: 扫描被@Component (@Service,@Controller)注解的 bean,注解默认会扫描该类所在的包下所有的类。
  • @Configuration:允许在上下文中注册额外的 bean 或导入其他配置类
6. Spring Boot 的自动配置原理
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

    Class<?>[] exclude() default {};

    String[] excludeName() default {};
}
  • @EnableAutoConfiguration 注解通过 Spring 提供的 @Import 注解导入了AutoConfigurationImportSelector类
  • AutoConfigurationImportSelector类中getCandidateConfigurations方法会将所有自动配置类的信息以 List 的形式返回。这些配置信息会被 Spring 容器作 bean 来管理。
  • 自动配置类通过@Conditional 注解决定是否加入容器中。如@ConditionalOnClass(指定的类必须存在于类路径下),@ConditionalOnBean(容器中是否有指定的 Bean),@ConditionalMissingOnBean(容器中不能有指定的 Bean)
7. Json相关注解
  • @JsonIgnoreProperties 作用在类上用于过滤掉特定字段不返回或者不解析。
  • @JsonIgnore一般用于类的属性上,作用和上面的@JsonIgnoreProperties 一样。
//生成json时将userRoles属性过滤
@JsonIgnoreProperties({"userRoles"})
public class User {

    private String userName;
    
    @JsonIgnore
    private List<UserRole> userRoles = new ArrayList<>();
}
  • @JsonFormat用来格式化 json 数据
@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", timezone="GMT")
private Date date;
  • @JsonUnwrapped扁平化对象
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值