Spring-总结
如想了解更多更全面的Java必备内容可以阅读:所有JAVA必备知识点面试题文章目录:
文章目录
- Spring-总结
- 1、Spring的设计初衷是什么?
- 2、对控制反转(IOC)的理解有哪些?
- 3、对依赖注入(DI)的理解有哪些
- 4、对面向切面(AOP)的理解有哪些?
- 5、对BeanFactory接口的了解有哪些?
- 6、Spring模块都有哪些?
- 7、Spring各模块都有哪些常见的功能?
- 8、对Spring各模块之间的依赖关系了解?
- 9、基于 Xml 的 IOC 容器的初始化过程?
- 10、基于Annotation的IOC初始化过程?
- 11、什么时候进行依赖注入?
- 12、Spring依赖注入的过程?
- 13、说说Spring IOC容器的延时加载?
- 14、请说一下Spring中FactoryBean和BeanFactory的区别?
- 15、Spring IOC容器提供了哪两种管理Bean依赖关系的方式?
- 16、对autowiring实现原理的分析?
- 17、Spring AOP应用场景有哪些?
- 18、在AOP中,什么是切面、连接点、通知、切入点、目标对象、AOP代理、前置通知、后置通知、返回后通知、环绕通知、异常通知?
- 19、实现Spring AOP的两种方式?
- 20、Spring AOP实现的主要流程?
- 21、Spring AOP代理对象的生成策略?
- 22、Spring MVC 请求处理流程?
- 23、SpringMVC九大组件分别是什么?
- 24、简单说说SpringMVC工作机制?
- 25、简单说说spring的事务机制,以及是如何管理的?
- 26、说说Spring事务的传播?
- 27、事务失效的场景?
- 28、数据库隔离级别都有哪些?
- 29、Spring中的隔离级别都有哪些?
- 30、简述一下Spring5有哪些新特性?
- 31、使用 Spring 框架能带来哪些好处?
- 32、在 Java 中依赖注入有哪些方式?
- 33、BeanFactory和ApplicationContext有什么区别?
- 34、Spring 提供几种配置方式来设置元数据?
- 35、如何使用 XML 配置的方式配置 Spring?
- 36、怎样用注解的方式配置 Spring?
- 37、Spring 提供哪些配置形式?
- 38、请解释 Spring Bean 的生命周期?
- 39、Spring Bean 作用域之间的区别?
- 40、Spring 框架中的bean是线程安全的么?
- 41、请举例说明如何在 Spring 中注入一个Java集合?
- 42、请解释Spring Bean的自动装配?
- 43、请解释各种自动装配模式的区别?
- 44、@Required和@Autowired的区别与联系?
- 46、Spring 框架中有哪些不同类型的事件?
- 47、FileSystemResource 和 ClassPathResource 有何区别?
- 48、在Spring框架中如何更有效的使用JDBC?
- 49、在Spring中可以注入null或空字符串吗?
- 50、Spring 框架中都用到了哪些设计模式?
- 51、Spring是如何解决循环依赖的问题的?
- 52、Spring三级缓存的查询逻辑(getSingleton方法的整个过程)?
- 53、为什么Spring的一二三级缓不能解决构造器的循环依赖?
- 54、如何解决构造器的循环依赖?
- 55、为什么多实例Bean不能解决循环依赖?
1、Spring的设计初衷是什么?
Spring是为解决企业级应用开发的复杂性而设计,它可以做很多事。但归根到底支撑Spring的仅仅是少许的基本理念,而所有的这些基本理念都能可以追溯到一个最根本的使命:简化开发。
它主要采取了4个关键策略:
- 基于POJO的轻量级和最小侵入性编程;
- 通过依赖注入和面向接口松耦合;
- 基于切面和惯性进行声明式编程;
- 通过切面和模板减少样板式代码;
而它主要是通过:面向Bean(BOP)、依赖注入(DI) 以及面向切面(AOP) 这三种方式来达成的。
2、对控制反转(IOC)的理解有哪些?
控制反转(Inversion of Control, IOC):当某个Java对象(调用者)需要调用另一个Java对象(被调用者,即被依赖对象)时,在传统模式下,调用者通常会采用“new 被调用者”的代码方式来创建对象,如图1所示。这种方式会导致调用者与被调用者之间的耦合性增加,不利于后期项目的升级和维护。
在使用Spring框架之后,对象的实例不再由调用者来创建,而是由Spring容器来创建,Spring容器会负责控制程序之间的关系,而不是由调用者的程序代码直接控制。这样,控制权由应用代码转移到了Spring容器,控制权发生了反转。即就是Spring的控制反转。
3、对依赖注入(DI)的理解有哪些
依赖注入(Dependency Injection, DI):从Spring容器的角度来看,Spring容器负责将被依赖对象赋值给调用者的成员变量,这相当于为调用者注入了它依赖的实例,这就是Spring的依赖注入。
4、对面向切面(AOP)的理解有哪些?
面向切面(Aspect Oriented Programming,AOP):通过动态代理实现程序功能的统一维护的一种技术,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
它允许程序员对横切关注点或横切典型的职责分界线的行为(例如日志和事务管理)进行模块化。
AOP 的功能完全集成到了 Spring 事务管理、日志和其他各种特性的上下文中。
AOP 编程的常用场景有:Authentication(权限认证)、Auto Caching(自动缓存处理)、Error Handling(统一错误处理)、Debugging(调试信息输出)、Logging(日志记录)、Transactions(事务处理)等。
5、对BeanFactory接口的了解有哪些?
BeanFactory接口:是 Spring 框架中的核心接口,它是工厂模式的具体实现。允许通过名称创建和检索对象。BeanFactory 也可以管理对象之间的关系。
BeanFactory 使用控制反转对应用程序的配置和依赖性规范与实际的应用程序代码进行了分离。但 BeanFactory 容器实例化后并不会自动实例化 Bean,只有当 Bean 被使用时,BeanFactory 容器才会对该 Bean 进行实例化与依赖关系的装配。
BeanFactory 最底层支持两个对象模型:
- 单例:提供了具有特定名称的全局共享实例对象,可以在查询时对其进行检索。
- 原型:确保每次检索都会创建单独的实例对象。在每个用户都需要自己的对象时,采用原型模式。
其中BeanFactory作为最顶层的一个接口类,它定义了IOC容器的基本功能规范,BeanFactory有三个重要的子类:
ListableBeanFactory(示这些Bean是可列表化的)、HierarchicalBeanFactory(这些Bean是有继承关系的,也就是每个Bean有可能有父Bean)和AutowireCapableBeanFactory(定义Bean的自动装配规则)。这三个接口共同定义了Bean的集合、Bean之间的关系、以及Bean行为。
//spring5.2 BeanFactory 部分源码如下:
public interface BeanFactory {
//对 FactoryBean 的转义定义,因为如果使用 bean 的名字检索 FactoryBean 得到的对象是工厂生成的对象,
//如果需要得到工厂本身,需要转义
String FACTORY_BEAN_PREFIX = "&";
//根据 bean 的名字,获取在 IOC 容器中得到 bean 实例
Object getBean(String name) throws BeansException;
//根据 bean 的名字和 Class 类型来得到 bean 实例,增加了类型安全验证机制。
<T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException;
Object getBean(String name, Object... args) throws BeansException;
<T> T getBean(Class<T> requiredType) throws BeansException;
<T> T getBean(Class<T> requiredType, Object... args) throws BeansException;
//提供对 bean 的检索,看看是否在 IOC 容器有这个名字的 bean
boolean containsBean(String name);
//根据 bean 名字得到 bean 实例,并同时判断这个 bean 是不是单例
boolean isSingleton(String name) throws NoSuchBeanDefinitionException;
boolean isPrototype(String name) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String name, @Nullable Class<?> typeToMatch) throws NoSuchBeanDefinitionException;
//得到 bean 实例的 Class 类型
@Nullable
Class<?> getType(String name) throws NoSuchBeanDefinitionException;
//得到 bean 的别名,如果根据别名检索,那么其原名也会被检索出来
String[] getAliases(String name);
.............
}
6、Spring模块都有哪些?
7、Spring各模块都有哪些常见的功能?
核心容器:
- spring-core模块和spring-beans模块,是Spring框架的核心模块,包含了控制反转和依赖注入。
- spring-context模块,构架于核心模块之上,他扩展了BeanFactory,为她添加了Bean生命周期控制、框架事件体系以及资源加载透明化等功能。此外该模块还提供了许多企业级支持,如邮件访问、远程访问、任务调度等,ApplicationContext是该模块的核心接口,她的超类是BeanFactory。与BeanFactory不同,ApplicationContext容器实例化后会自动对所有的单实例Bean进行实例化与依赖关系的装配,使之处于待用状态。
- spring-context-support模块,是对SpringIOC容器的扩展支持,以及IOC子容器。
- spring-context-indexer模块,是Spring的类管理组件和Classpath扫描。
- spring-expression模块,是统一表达式语言(EL)的扩展模块,可以查询、管理运行中的对象,同时也方便的可以调用对象方法、操作数组、集合等。它的语法类似于传统EL,但提供了额外的功能,最出色的要数函数调用和简单字符串的模板函数。
AOP和设备支持:
- spring-aop模块,是Spring的另一个核心模块,是AOP主要的实现模块。
- spring-aspects模块,集成自AspectJ框架,主要是为Spring AOP提供多种AOP实现方法。
- spring-instrument模块,是基于JAVA SE中的"java.lang.instrument"进行设计的,应该算是AOP的一个支援模块,主要作用是在JVM启用时,生成一个代理类,程序员通过代理类在运行时修改类的字节,从而改变一个类的功能,实现AOP的功能。
数据访问与集成:
- spring-jdbc模块,是Spring提供的JDBC抽象框架的主要实现模块,用于简化Spring JDBC操作。主要是提供JDBC模板方式、关系数据库对象化方式、SimpleJdbc方式、事务管理来简化JDBC编程,主要实现类是JdbcTemplate、SimpleJdbcTemplate以及NamedParameterJdbcTemplate。
- spring-tx模块,是Spring JDBC事务控制实现模块。使用Spring框架,它对事务做了很好的封装,通过它的AOP配置,可以灵活的配置在任何一层。
- spring-orm模块,是ORM框架支持模块,主要集成Hibernate,Java Persistence API(JPA)和Java Data Objects(JDO)用于资源管理、数据访问对象(DAO)的实现和事务策略。
- spring-oxm模块,是一个O/M-mapper,将java对象映射成XML数据,或者将XML数据映射成java对象
- spring-jms模块,能够发送和接收信息,自Spring Framework 4.1以后,他还提供了对spring-messaging模块的支撑。
Web组件:
- spring-web模块,为Spring提供了最基础Web支持,主要建立于核心容器之上,通过Servlet或者Listeners来初始化IOC容器,也包含一些与Web相关的支持。
- spring-webmvc模块,是一个的Web-Servlet模块,实现了Spring MVC(model-view-Controller)的Web应用。
- spring-websocket模块,主要是与Web前端的全双工通讯的协议。
- spring-webflux模块,是一个新的非堵塞函数式Reactive Web框架,可以用来建立异步的,非阻塞,事件驱动的服务。
通信报文:
- spring-messaging模块,是从Spring4开始新加入的一个模块,主要职责是为Spring框架集成一些基础的报文传送应用。
集成测试:
- spring-test模块,主要为测试提供支持.
8、对Spring各模块之间的依赖关系了解?
9、基于 Xml 的 IOC 容器的初始化过程?
- 寻找入口
ApplicationContext app = new ClassPathXmlApplicationContext("application.xml"); //AnnotationConfigApplicationContext、FileSystemXmlApplicationContext、XmlWebApplicationContext等都继承自父容器 //AbstractApplicationContext,主要用到了装饰器模式和策略模式,最终都是调用refresh()方法。 super(parent); this.setConfigLocations(configLocations); if(refresh){ this.refresh(); }
- 获得配置路径
- 开始启动
SpringIOC容器对Bean配置资源的载入是从refresh()函数开始的,refresh()是一个模板方法,规定了IOC容器的启动流程,有些逻辑要交给其子类去实现。它对Bean配置资源进行载入ClassPathXmlApplicationContext通过调用其父类AbstractApplicationContext的refresh()函数启动整个IOC容器对Bean定义的载入过程,现在我们来详细看看refresh()中的逻辑处理:@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { // Prepare this context for refreshing. //1、调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识 prepareRefresh(); // Tell the subclass to refresh the internal bean factory. //2、告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从 //子类的refreshBeanFactory()方法启动 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context. //3、为BeanFactory配置容器特性,例如类加载器、事件处理器等 prepareBeanFactory(beanFactory); try { // Allows post-processing of the bean factory in context subclasses. //4、为容器的某些子类指定特殊的BeanPost事件处理器 postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context. //5、调用所有注册的BeanFactoryPostProcessor的Bean invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation. //6、为BeanFactory注册BeanPost事件处理器. //BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件 registerBeanPostProcessors(beanFactory); // Initialize message source for this context. //7、初始化信息源,和国际化相关. initMessageSource(); // Initialize event multicaster for this context. //8、初始化容器事件传播器. initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses. //9、调用子类的某些特殊Bean初始化方法 onRefresh(); // Check for listener beans and register them. //10、为事件传播器注册事件监听器. registerListeners(); // Instantiate all remaining (non-lazy-init) singletons. //11、初始化所有剩余的单例Bean finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event. //12、初始化容器的生命周期事件处理器,并发布容器的生命周期事件 finishRefresh(); } catch (BeansException ex) { if (logger.isWarnEnabled()) { logger.warn("Exception encountered during context initialization - " + "cancelling refresh attempt: " + ex); } // Destroy already created singletons to avoid dangling resources. //13、销毁已创建的Bean destroyBeans(); // Reset 'active' flag. //14、取消refresh操作,重置容器的同步标识。 cancelRefresh(ex); // Propagate exception to caller. throw ex; } finally { // Reset common introspection caches in Spring's core, since we // might not ever need metadata for singleton beans anymore... //15、重设公共缓存 resetCommonCaches(); } } }
- 创建容器
先判断BeanFactory是否存在,如果存在则先销毁beans并关闭beanFactory,接着创建DefaultListableBeanFactory,并调用loadBeanDefinitions(beanFactory)装载bean定义。//AbstractRefreshableApplicationContext类 @Override protected final void refreshBeanFactory() throws BeansException { //如果已经有容器,销毁容器中的bean,关闭容器 if (hasBeanFactory()) { destroyBeans(); closeBeanFactory(); } try { //创建IOC容器 DefaultListableBeanFactory beanFactory = createBeanFactory(); beanFactory.setSerializationId(getId()); //对IOC容器进行定制化,如设置启动参数,开启注解的自动装配等 customizeBeanFactory(beanFactory); //调用载入Bean定义的方法,主要这里又使用了一个委派模式,在当前类中只定义了抽象的loadBeanDefinitions方法,具体的实现调用子类容器 loadBeanDefinitions(beanFactory); synchronized (this.beanFactoryMonitor) { this.beanFactory = beanFactory; } } catch (IOException ex) { throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex); } }
- 载入配置路径
- 分配路径处理策略
- 解析配置文件路径
- 开始读取配置内容
- 准备文档对象
DocumentLoader 将 Bean 配置资源转换成 Document 对象。 - 分配解析策略
接下来调用registerBeanDefinitions() 启动Spring IOC容器对Bean定义的解析过程。 - 将配置载入内存
- 载入< bean>元素
,对 Bean 配置信息中使用最多的元素交由 BeanDefinitionParserDelegate 来解析。
注意:在解析元素过程中没有创建和实例化 Bean 对象,只是创建了 Bean 对象的定义类
BeanDefinition,将元素中的配置信息设置到 BeanDefinition 中作为记录,当依赖注入时才
使用这些记录信息创建和实例化具体的 Bean 对象。 - 载入< property>元素
元素中元素的相关
配置是如何处理的:
1、ref 被封装为指向依赖对象一个引用。
2、value 配置都会封装成一个字符串类型的对象。
3、ref 和 value 都通过“解析的数据类型属性值.setSource(extractSource(ele));”方法将属性值/引用
与所引用的属性关联起来。
在方法的最后对于元素的子元素通过 parsePropertySubElement ()方法解析 - 载入< property>的子元素
- 载入< list>的子元素
- 分配注册策略
调用BeanDefinitionReaderUtils 的 registerBeanDefinition() 方法向IOC容器注册解析的Bean。//将解析的 BeanDefinitionHold 注册到容器中 public static void registerBeanDefinition(BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)throws BeanDefinitionStoreException { //获取解析的 BeanDefinition 的名称 String beanName = definitionHolder.getBeanName(); //向 IOC 容器注册 BeanDefinition registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); //如果解析的 BeanDefinition 有别名,向容器为其注册别名 String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
- 向容器注册
DefaultListableBeanFactory中使用一个HashMap的集合对象存放IOC容器中注册解析的BeanDefinition.这些BeanDefinition 信息已经可以使用,并且可以被检索,IOC 容器的作用就是对这些注册的 Bean 定义信息进行处理和维护。这些的注册的 Bean 定义信息是 IOC 容器控制反转的基础,正是有了这些注册的数据,容器才可以进行依赖注入。
10、基于Annotation的IOC初始化过程?
注解(Annotation)是JDK1.5中引入的一个新特性,用于简化Bean的配置,可以取代XML配置文件。
随着SpringBoot的主流,基于注解的开发甚至实现了零配置。
- 定位Bean扫描路径
在Spring中管理注解Bean定义的容器有两个:AnnotationConfigApplicationContext和AnnotationConfigWebApplicationContext,后者是前者的Web版本,两者的用法以及对注解的处理方式几乎没有差别。
Spring对注解的处理分为两种方式:- 直接将注解Bean注册到容器中:可以在初始化容器时注册;也可以在容器创建之后手动调用注册方法向容器注册,然后通过手动刷新容器,使得容器对注册的注解Bean进行处理。
- 通过扫描指定的包及其子包下的所有类:在初始化注解容器时指定要自动扫描的路径;如果容器创建以后向给定路径动态添加了注解Bean,则需要手动调用容器扫描的方法,然后手动刷新容器,使得容器对所注册的Bean进行处理。
- 读取Annotation元数据
- AnnotatedBeanDefinitionReader的register()方法向容器注册指定的注解Bean。
- AnnotationScopeMetadataResolver通过resolveScopeMetadata()方法解析注解Bean定义类的作用域元信息,即判断注册的Bean是原生类型(prototype)还是单态(singleton)类型。
- AnnotationConfigUtils类的processCommonDefinitionAnnotations()在向容器注册Bean之前,首先对注解Bean定义类中的通用Spring注解进行处理。
- AnnotationConfigUtils类的applyScopedProxyMode()方法根据注解Bean定义类中配置的作用域为其应用相应的代理策略。
- BeanDefinitionReaderUtils校验BeanDefinition信息,然后将Bean添加到容器中一个管理BeanDefinition的HashMap中。
- 扫描指定包并解析为BeanDefinition
1.通过调用类路径Bean定义扫描器ClassPathBeanDefinitionScanner扫描给定包及其子包下的所有类。
2.ClassPathScanningCandidateComponentProvider的findCandidateComponents()方法扫描给定包及其子包的类。 - 注册注解BeanDefinition
11、什么时候进行依赖注入?
当SpringIOC容器完成了Bean定义资源的定位、载入和解析注册以后,IOC容器中已经管理类Bean定义的相关数据,但是此时IOC容器还没有对所管理的Bean进行依赖注入,依赖注入在以下两种情况发生:
- 用户第一次调用getBean()方法时,IOC容器触发依赖注入。BeanFactory接口中定义了几个getBean()方法,就是用户向IOC容器索取管理的Bean的方法。
- 当用户在配置文件中将元素配置了lazy-init=false属性,即让容器在解析注册Bean定义时进行预实例化,触发依赖注入。
12、Spring依赖注入的过程?
- 寻找获取 Bean 的入口
如果 Bean 定义的单例模式(Singleton),则容器在创建之前先从缓存中查找,以确保整个容器中只存在一个实例对象。如果 Bean定义的是原型模式(Prototype),则容器每次都会创建一个新的实例对象。除此之外,Bean 定义还可以扩展为指定其生命周期范围。#####AbstractBeanFactory 的 getBean() //获取 IOC 容器中指定名称的 Bean @Override public Object getBean(String name) throws BeansException { //doGetBean 才是真正向 IOC 容器获取被管理 Bean 的过程 return doGetBean(name, null, null, false); } //获取 IOC 容器中指定名称和类型的 Bean @Override public <T> T getBean(String name, @Nullable Class<T> requiredType) throws BeansException { //doGetBean 才是真正向 IOC 容器获取被管理 Bean 的过程 return doGetBean(name, requiredType, null, false); } //获取 IOC 容器中指定名称和参数的 Bean @Override public Object getBean(String name, Object... args) throws BeansException { //doGetBean 才是真正向 IOC 容器获取被管理 Bean 的过程 return doGetBean(name, null, args, false); } //获取 IOC 容器中指定名称、类型和参数的 Bean public <T> T getBean(String name, @Nullable Class<T> requiredType, @Nullable Object... args)throws BeansException { //doGetBean 才是真正向 IOC 容器获取被管理 Bean 的过程 return doGetBean(name, requiredType, args, false); }
- 开始实例化
Bean实例创建过程交由其实现类AbstractAutowireCapableBeanFactory的createBean()方法完成。 - 选择Bean实例化策略
使用工厂方法和自动装配特性的 Bean 的实例化相当比较清楚,调用相应的工厂方法或者参数匹配的构造方法即可完成实例化对象的工作,但是对于我们最常使用的默认无参构造方法就需要使用相应的初始化策略(JDK 的反射机制或者 CGLib)来进行初始化了,在方法 getInstantiationStrategy().instantiate()中就具体实现类使用初始策略实例化对象。 - 执行Bean实例化
如果 Bean 有方法被覆盖了,则使用 JDK 的反射机制进行实例化,否则,使用 CGLib 进行实例化。 - 准备依赖注入
对属性的注入过程分以下两种情况:- 属性值类型不需要强制转换时,不需要解析属性值,直接准备进行依赖注入。
- 属性值需要进行类型强制转换时,如对其他对象的引用等,首先需要解析属性值,然后对解析后的属性值进行依赖注入。
- 解析属性注入规则
当容器在对属性进行依赖注入时,如果发现属性值需要进行类型转换,如属性值是容器中另一个 Bean实例对象的引用,则容器首先需要根据属性值解析出所引用的对象,然后才能将该引用对象注入到目标实例对象的属性上去,对属性进行解析的由 resolveValueIfNecessary()方法实现, - 注入赋值
BeanWrapperImpl 类主要是对容器中完成初始化的 Bean 实例对象进行属性的依赖注入,即把 Bean对象设置到它所依赖的另一个 Bean 的属性中去。然而,BeanWrapperImpl 中的注入方法实际上由AbstractNestablePropertyAccessor 来实现的。
Spring IOC 容器是如何将属性的值注入到 Bean 实例对象中去的:- 对于集合类型的属性,将其属性值解析为目标类型的集合后直接赋值给属性。
- 对于非集合类型的属性,大量使用了 JDK 的反射机制,通过属性的 getter()方法获取指定属性注入以前的值,同时调用属性的 setter()方法为属性设置注入后的值。看到这里相信很多人都明白了 Spring的 setter()注入原理。
13、说说Spring IOC容器的延时加载?
我们知道IOC容器的初始化过程就是对Bean定义资源的定位、载入和注册,此时容器对Bean的依赖注入并没有发生,依赖注入主要是在应用程序第一次向容器索取Bean时,通过getBean()方法的调用完成。
当Bean定义资源的< Bean>元素中配置了lazy-init=false属性时,容器将会在初始化的时候对所配置的Bean进行预实例化,Bean的依赖注入在容器初始化的时候就已经完成。这样,当应用程序第一次向容器索取被管理的Bean时,就不用再初始化和对Bean进行依赖注入了,直接从容器中获取已经完成依赖注入的现成Bean,可以提高应用第一次向容器获取Bean的性能。
14、请说一下Spring中FactoryBean和BeanFactory的区别?
BeanFactory:Bean工厂,是一个工厂(Factory),是SpringIOC容器的最顶层接口,它的作用是管理Bean,即实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
FactoryBean:工厂Bean,是一个Bean,作用是产生其他bean实例。通常情况下,这种Bean没有什么特别的要求,仅需要提供一个工厂方法,该方法用来返回其他Bean实例。通常情况下,Bean无须自己实现工厂模式,Spring容器担任工厂角色;但少数情况下,容器中的Bean本身就是工厂,其作用是产生其它Bean实例。
当用户使用容器本身时,可以使用转义字符”&”来得到FactoryBean本身,以区别通过FactoryBean产生的实例对象和FactoryBean对象本身。在BeanFactory中通过如下代码定义了该转义字符:
StringFACTORY_BEAN_PREFIX="&";
如果myJndiObject是一个FactoryBean,则使用&myJndiObject得到的是myJndiObject对象,而不是myJndiObject产生出来的对象。
15、Spring IOC容器提供了哪两种管理Bean依赖关系的方式?
- 显式管理:通过BeanDefinition的属性值和构造方法实现Bean依赖关系管理。
- autowiring:SpringIOC容器的依赖自动装配功能,不需要对Bean属性的依赖关系做显式的声明,只需要在配置好autowiring属性,IOC容器会自动使用反射查找属性的类型和名称,然后基于属性的类型或者名称来自动匹配容器中管理的Bean,从而自动地完成依赖注入。
16、对autowiring实现原理的分析?
- 对Bean的属性代调用getBean()方法,完成依赖Bean的初始化和依赖注入。
- 将依赖Bean的属性引用设置到被依赖的Bean属性上。
- 将依赖Bean的名称和被依赖Bean的名称存储在IOC容器的集合中。
17、Spring AOP应用场景有哪些?
AOP是OOP的延续,是Aspect Oriented Programming的缩写,意思是面向切面编程。可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
AOP设计模式孜孜不倦追求的是调用者和被调用者之间的解耦,AOP可以说也是这种目标的一种实现。我们现在做的一些非业务,如:日志、事务、安全等都会写在业务代码中(也即是说,这些非业务类横切于业务类),但这些代码往往是重复,复制——粘贴式的代码会给程序的维护带来不便,AOP就实现了把这些业务需求与系统需求分开来做。这种解决的方式也称代理机制。
18、在AOP中,什么是切面、连接点、通知、切入点、目标对象、AOP代理、前置通知、后置通知、返回后通知、环绕通知、异常通知?
- 切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”。“切面”在ApplicationContext中< aop:aspect>来配置。
- 连接点(Joinpoint):程序执行过程中的某一行为,例如,MemberService.get的调用或者MemberService.delete抛出异常等行为。
- 通知(Advice):“切面”对于某个“连接点”所产生的动作。其中,一个“切面”可以包含多个“通知”。
- 切入点(Pointcut):匹配连接点的断言,在AOP中通知和一个切入点表达式关联。切面中的所有通知所关注的连接点,都由切入点表达式来决定。
- 目标对象(TargetObject):被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
- AOP代理(AOPProxy):在SpringAOP中有两种代理方式,JDK动态代理和CGLib代理。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,反之,采用CGLib代理。强制使用CGLib代理需要将< aop:config>的proxy-target-class属性设为true。
- 前置通知(BeforeAdvice) :在某连接点之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在< aop:aspect>里面使用< aop:before>元素进行声明。例如,TestAspect中的doBefore方法。
- 后置通知(AfterAdvice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在< aop:aspect>里面使用< aop:after>元素进行声明。例如,ServiceAspect中的returnAfter方法,所以Teser中调用UserService.delete抛出异常时,returnAfter方法仍然执行。
- 返回后通知(AfterReturnAdvice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在< aop:aspect>里面使用< after-returning>元素进行声明。
- 环绕通知(AroundAdvice):包围一个连接点的通知,可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在< aop:aspect>里面使用< aop:around>元素进行声明。例如,ServiceAspect中的around方法。
- 异常通知(AfterThrowingAdvice):在方法抛出异常退出时执行的通知。ApplicationContext中在< aop:aspect>里面使用< aop:after-throwing>元素进行声明。例如,ServiceAspect中的returnThrow方法。
注意:可以将多个通知应用到一个目标对象上,即可以将多个切面织入到同一目标对象。
19、实现Spring AOP的两种方式?
- 使用注解配置 Spring AOP
- 第一步,在 xml 文件中声明激活自动扫描组件功能,同时激活自动代理功能。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="com.springStudy"/> <context:annotation-config /> </beans>
- 第二步,为 Aspect 切面类添加注解
//声明这是一个组件 @Component //声明这是一个切面 Bean @Aspect @Slf4j public class AnnotaionAspect { //配置切入点,该方法无方法体,主要为方便同类中其他方法使用此处配置的切入点 @Pointcut("execution(* com.pattern.spring.aop.service..*(..))") public void aspect(){ } /* * 配置前置通知,使用在方法 aspect()上注册的切入点 * 同时接受 JoinPoint 切入点对象,可以没有该参数 */ @Before("aspect()") public void before(JoinPoint joinPoint){ log.info("before 通知 " + joinPoint); } //配置后置通知,使用在方法 aspect()上注册的切入点 @After("aspect()") public void after(JoinPoint joinPoint){ log.info("after 通知 " + joinPoint); } //配置环绕通知,使用在方法 aspect()上注册的切入点 @Around("aspect()") public void around(JoinPoint joinPoint){ long start = System.currentTimeMillis(); try { ((ProceedingJoinPoint) joinPoint).proceed(); long end = System.currentTimeMillis(); log.info("around 通知 " + joinPoint + "\tUse time : " + (end - start) + " ms!"); } catch (Throwable e) { long end = System.currentTimeMillis(); log.info("around 通知 " + joinPoint + "\tUse time : " + (end - start) + " ms with exception :" + e.getMessage()); } } //配置后置返回通知,使用在方法 aspect()上注册的切入点 @AfterReturning("aspect()") public void afterReturn(JoinPoint joinPoint){ log.info("afterReturn 通知 " + joinPoint); } //配置抛出异常后通知,使用在方法 aspect()上注册的切入点 @AfterThrowing(pointcut="aspect()", throwing="ex") public void afterThrow(JoinPoint joinPoint, Exception ex){ log.info("afterThrow 通知 " + joinPoint + "\t" + ex.getMessage()); } }
- 使用XML配置 Spring AOP,大概有四种方式:
- 1.配置ProxyFactoryBean,显式地设置advisors, advice, target等
- 2.配置AutoProxyCreator,这种方式下,还是如以前一样使用定义的bean,但是从容器中获得的其实已经是代理对象
- 3.通过< aop:config>来配置
- 4.通过< aop: aspectj-autoproxy>来配置,使用AspectJ的注解来标识通知及切入点
<bean id="xmlAspect" class="com.pattern.spring.aop.aspect.XmlAspect"></bean>
<!-- AOP 配置 -->
<aop:config>
<!-- 声明一个切面,并注入切面 Bean,相当于@Aspect -->
<aop:aspect ref="xmlAspect">
<!-- 配置一个切入点,相当于@Pointcut -->
<aop:pointcut expression="execution(* com.pattern.spring.aop.service..*(..))"
id="simplePointcut"/>
<!-- 配置通知,相当于@Before、@After、@AfterReturn、@Around、@AfterThrowing -->
<aop:before pointcut-ref="simplePointcut" method="before"/>
<aop:after pointcut-ref="simplePointcut" method="after"/>
<aop:after-returning pointcut-ref="simplePointcut" method="afterReturn"/>
<aop:after-throwing pointcut-ref="simplePointcut" method="afterThrow" throwing="ex"/>
</aop:aspect>
</aop:config>
20、Spring AOP实现的主要流程?
- 首先,寻找入口:Spring 的 AOP 是通过接入 BeanPostProcessor 后置处理器开始的。
- 其次,选择策略:AbstractAutoProxyCreator 类的wrapIfNecessary()方法。
/** * Wrap the given bean if necessary, i.e. if it is eligible for being proxied. * @param bean the raw bean instance * @param beanName the name of the bean * @param cacheKey the cache key for metadata access * @return a proxy wrapping the bean, or the raw bean instance as-is */ protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } // 判断是否不应该代理这个 bean if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } /* * 判断是否是一些 InfrastructureClass 或者是否应该跳过这个 bean。 * 所谓 InfrastructureClass 就是指 Advice/PointCut/Advisor 等接口的实现类。 * shouldSkip 默认实现为返回 false,由于是 protected 方法,子类可以覆盖。 */ if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } // 获取这个 bean 的 advice // Create proxy if we have advice. Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName,null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); // 创建代理 Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
- 然后,调用方法:JdkDynamicAopProxy 和 ObjenesisCglibAopProxy的getProxy()方法。
- 最后,触发通知:在为 AopProxy 代理对象配置拦截器的实现中,有一个取得拦截器的配置过程,这个过程是由 DefaultAdvisorChainFactory 实现的,这个工厂类负责生成拦截器链,在它的getInterceptorsAndDynamicInterceptionAdvice 方法中。
21、Spring AOP代理对象的生成策略?
Spring 提供了两种方式来生成代理方式,分别是JdkDynamicAopProxy 和 ObjenesisCglibAopProxy。
#### Spring5.2源码如下:
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
//如果实现了接口,使用jdk动态代理
return new JdkDynamicAopProxy(config);
}
//使用CGLib动态代理
return new ObjenesisCglibAopProxy(config);
}else {
//默认使用jdk动态代理
return new JdkDynamicAopProxy(config);
}
}
/**
* Determine whether the supplied {@link AdvisedSupport} has only the
* {@link org.springframework.aop.SpringProxy} interface specified
* (or no proxy interfaces specified at all).
*/
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 &&
SpringProxy.class.isAssignableFrom(ifcs[0])));
}
}
22、Spring MVC 请求处理流程?
- DispatcherServlet是SpringMVC中的前端控制器(FrontController),负责接收Request并将Request转发给对应的处理组件。
- HanlerMapping是SpringMVC中完成url到Controller映射的组件。DispatcherServlet接收Request,然后从HandlerMapping查找处理Request的Controller。
- Controller处理Request,并返回ModelAndView对象,Controller是SpringMVC中负责处理Request的组件(类似于Struts2中的Action),ModelAndView是封装结果视图的组件。
- 视图解析器解析ModelAndView对象并返回对应的视图给客户端。
容器初始化时会建立所有url和Controller中的Method的对应关系,保存到HandlerMapping中,用户请求是根据Request请求的url快速定位到Controller中的某个方法。在Spring中先将url和Controller的对应关系,保存到Map<url,Controller>中。Web容器启动时会通知Spring初始化容器(加载Bean的定义信息和初始化所有单例Bean),然后SpringMVC会遍历容器中的Bean,获取每一个Controller中的所有方法访问的url,然后将url和Controller保存到一个Map中;这样就可以根据Request快速定位到Controller,因为最终处理Request的是Controller中的方法,Map中只保留了url和Controller中的对应关系,所以要根据Request的url进一步确认Controller中的Method,这一步工作的原理就是拼接Controller的url(Controller上@RequestMapping的值)和方法的url(Method上@RequestMapping的值),与request的url进行匹配,找到匹配的那个方法;确定处理请求的Method后,接下来的任务就是参数绑定,把Request中参数绑定到方法的形式参数上。
23、SpringMVC九大组件分别是什么?
- HandlerMappings:是用来查找Handler的,也就是处理器,具体的表现形式可以是类也可以是方法。比如,标注了@RequestMapping的每个method都可以看成是一个Handler,由Handler来负责实际的请求处理。HandlerMapping在请求到达之后,它的作用便是找到请求相应的处理器Handler和Interceptors。
- HandlerAdapters:是一个适配器。由于请求交给Servlet的时候,由于Servlet的方法结构都是如doService(HttpServletRequestreq,HttpServletResponseresp)这样的形式,所以作用是固定的Servlet处理方法调用具体的Handler来进行处理。
- HandlerExceptionResolvers:是用来处理Handler过程中产生的异常情况的组件。此组件的作用是根据异常设置ModelAndView,之后再交给render()方法进行渲染,而render()便将ModelAndView渲染成页面。不过有一点,HandlerExceptionResolver只是用于解析对请求做处理阶段产生的异常,而渲染阶段的异常则不归他管了,这也是SpringMVC组件设计的一大原则分工明确互不干涉。
- ViewResolvers:视图解析器。这个接口只有一个resolveViewName()方法。从方法的定义就可以看出,Controller层返回的String类型的视图名viewName,最终会在这里被解析成为View。View是用来渲染页面的,也就是说,它会将程序返回的参数和数据填入模板中,最终生成html文件。
- RequestToViewNameTranslator:这个组件的作用,在于从Request中获取viewName,因为ViewResolver是根据ViewName查找View,但有的Handler处理完成之后,没有设置View也没有设置ViewName,便要通过这个组件来从Request中查找viewName。
- LocaleResolver:国际化,从request中解析出Locale,在中国大陆地区,Locale当然就会是zh-CN之类,用来表示一个区域。这个类也是i18n的基础。
- ThemeResolver:用来解析主题的。负责从request中解析出主题名,ThemeSource则根据主题名找到具体的主题,其抽象也就是Theme,通过Theme来获取主题和具体的资源。
- MultipartResolver:作用就是用来封装普通的request,使其拥有处理文件上传的功能,通过将普通的Request包装成MultipartHttpServletRequest来实现。MultipartHttpServletRequest可以通过getFile()直接获得文件,如果是多个文件上传,还可以通过调用getFileMap得到Map<FileName,File>这样的结构。
- FlashMapManager:FlashMap继承自HashMap,在Session中保存的FlashMap是List类型,也就是说一个Session可以保存多个FlashMap,FlashMap用于存放重定向(Redirect)时所传递的参数。而FlashMapManager就是用来管理FlashMap的。
24、简单说说SpringMVC工作机制?
- 初始化九大组件
初始化,首先找到DispatcherServlet这个类,必然是寻找init()方法,然后又调用了一个重要的initServletBean()方法,最后有调用了onRefresh()方法。@Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } /** * Initialize the strategy objects that this servlet uses. * <p>May be overridden in subclasses in order to initialize further strategy objects. */ //初始化策略 protected void initStrategies(ApplicationContext context) { //多文件上传的组件 initMultipartResolver(context); //初始化本地语言环境 initLocaleResolver(context); //初始化模板处理器 initThemeResolver(context); //handlerMapping initHandlerMappings(context); //初始化参数适配器 initHandlerAdapters(context); //初始化异常拦截器 initHandlerExceptionResolvers(context); //初始化视图预处理器 initRequestToViewNameTranslator(context); //初始化视图转换器 initViewResolvers(context); //FlashMap 管理器 initFlashMapManager(context); }
- ApplicationContext初始化时用Map保存所有url和Controller类的对应关系
HandlerMapping的子类AbstractDetectingUrlHandlerMapping实现了initApplicationContext()方法。@Override public void initApplicationContext() throws ApplicationContextException { super.initApplicationContext(); detectHandlers(); } /** * 建立当前 ApplicationContext 中的所有 Controller 和 url 的对应关系 */ protected void detectHandlers() throws BeansException { ApplicationContext applicationContext = obtainApplicationContext(); if (logger.isDebugEnabled()) { logger.debug("Looking for URL mappings in application context: " + applicationContext); } // 获取 ApplicationContext 容器中所有 bean 的 Name String[] beanNames = (this.detectHandlersInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(applicationContext, Object.class) : applicationContext.getBeanNamesForType(Object.class)); // 遍历 beanNames,并找到这些 bean 对应的 url for (String beanName : beanNames) { // 找 bean 上的所有 url(Controller 上的 url+方法上的 url),该方法由对应的子类实现 String[] urls = determineUrlsForHandler(beanName); if (!ObjectUtils.isEmpty(urls)) { // 保存 urls 和 beanName 的对应关系,put it to Map<urls,beanName>, // 该方法在父类 AbstractUrlHandlerMapping 中实现 registerHandler(urls, beanName); } else { if (logger.isDebugEnabled()) { logger.debug("Rejected bean name '" + beanName + "': no URL paths identified"); } } } } /** 获取 Controller 中所有方法的 url,由子类实现,典型的模板模式 **/ protected abstract String[] determineUrlsForHandler(String beanName);
- 根据请求url找到对应的Controller
DispatcherServlet的核心方法为doService(),doService()中的核心逻辑由doDispatch()实现,调用的getHandler(processedRequest)方法实际上就是从HandlerMapping中找到url和Controller的对应关系。也就是Map< url,Controller>。 - 从Controller中找到处理请求的方法
从Map< urls,beanName>中取得Controller后,经过拦截器的预处理方法,再通过反射获取该方法上的注解和参数,解析方法和参数上的注解,然后反射调用方法获取ModelAndView结果视图。最后,调用的就是RequestMappingHandlerAdapter的handle()中的核心逻辑由handleInternal(request,response,handler)实现。 - Request参数绑定到方法的形参,执行方法处理请求,并返回结果视图。
25、简单说说spring的事务机制,以及是如何管理的?
Spring的事务机制包括声明式事务和编程式事务。
- 编程式事务管理:Spring推荐使用TransactionTemplate。
- 声明式事务管理:将我们从复杂的事务处理中解脱出来,获取连接,关闭连接、事务提交、回滚、异常处理等这些操作都不用我们处理了,Spring都会帮我们处理。
原理:是通过一个动态代理对所有需要事务管理的Bean进行加载,并根据配置在invoke方法中对当前调用的方法名进行判定,并在method.invoke方法前后为其加上合适的事务管理代码,这样就实现了Spring式的事务管理。
Spring事务管理主要包括3个接口:
- 事务管理器(PlatformTransactionManager):是Spring事务管理的核心接口。主要功能是事务管理器,是用于平台相关事务的管理,包括commit事务的提交;rollback事务的回滚;getTransaction事务状态的获取三种方法。
ackage org.springframework.transaction; public interface PlatformTransactionManager { TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; void commit(TransactionStatus status) throws TransactionException; void rollback(TransactionStatus status) throws TransactionException; }
- 事务属性的定义(TransactionDefinition):主要功能是事务定义信息,是用来定义事务相关的属性,给事务管理器PlatformTransactionManager使用的。而且在TransactionDefinition接口中定义了它自己的传播行为和隔离级别。包括getIsolationLevel:获取隔离级别;getPropagationBehavior:获取传播行为;getTimeout:获取超时时间;isReadOnly:是否只读四种方法。
package org.springframework.transaction; public interface TransactionDefinition { int PROPAGATION_REQUIRED = 0; int PROPAGATION_SUPPORTS = 1; int PROPAGATION_MANDATORY = 2; int PROPAGATION_REQUIRES_NEW = 3; int PROPAGATION_NOT_SUPPORTED = 4; int PROPAGATION_NEVER = 5; int PROPAGATION_NESTED = 6; int ISOLATION_DEFAULT = -1; int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED; int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED; int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ; int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE; int TIMEOUT_DEFAULT = -1; int getPropagationBehavior(); int getIsolationLevel(); int getTimeout(); boolean isReadOnly(); String getName(); }
- 事务具体运行状态(TransactionStatus):是事务管理过程中,每个时间点事务的状态信息,它可以封装许多代码,节省我们的工作量。包括hasSavepoint():返回这个事务内部是否包含一个保存点;isCompleted():返回该事务是否已经提交或回滚;isNewTransaction():判断当前事务是否是一个新事务这三种方法。
package org.springframework.transaction; public interface TransactionStatus extends SavepointManager, Flushable { boolean isNewTransaction(); boolean hasSavepoint(); void setRollbackOnly(); boolean isRollbackOnly(); @Override void flush(); boolean isCompleted(); }
26、说说Spring事务的传播?
所谓spring事务的传播属性,就是定义存在多个事务同时存在的时候,spring应该如何处理这些事务的行为。
常量名称 | 常量解释 |
---|---|
PROPAGATION_REQUIRED | 如果当前存在事务,则加入该事务,如果当前没有事务,就新建一个事务。这是最常见的选择,也是Spring默认的事务的传播。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。新建的事务将和被挂起的事务没有任何关系,是两个独立的事务,外层事务失败回滚之后,不能回滚内层事务执行的结果,内层事务失败抛出异常,外层事务捕获,也可以不处理回滚操作。 |
PROPAGATION_SUPPORTS | 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行 |
PROPAGATION_MANDATORY | 如果当前存在事务,则加入该事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。 |
27、事务失效的场景?
- 作用于接口:不推荐这种使用方法,因为一旦标注在Interface上并且配置了Spring AOP 使用CGLib动态代理,将会导致@Transactional注解失效。
- @Transactional 应用在非 public 修饰的方法上。注意:protected、private修饰的方法上使用 @Transactional 注解,虽然事务无效,但不会有任何报错,这是我们很容犯错的一点。
- @Transactional 注解属性 propagation 设置不当。这种失效是由于配置错误,若是配置以下几种 propagation,处理不当,事务将不会发生回滚。
- TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务,如果当前没有事务,就抛出异常。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
- @Transactional 注解属性 rollbackFor 设置不当。
rollbackFor 可以指定能够触发事务回滚的异常类型。Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
- 同一个类中方法调用,导致@Transactional失效。
比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。
因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。 - 异常被你的 catch“吃了”导致@Transactional失效。
如果B方法内部抛了异常,而A方法此时try catch了B方法的异常。
ServiceA中由于你手动的捕获这个异常并进行处理,ServiceA认为当前事务应该正常commit。此时就出现了前后不一致,也就是因为这样,抛出了前面的UnexpectedRollbackException异常。
spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。 - 数据库引擎不支持事务。MySQL数据库默认使用支持事务的innodb引擎。一旦数据库引擎切换成不支持事务的myisam。
28、数据库隔离级别都有哪些?
隔离级别 | 隔离级别的值 | 导致的问题 |
---|---|---|
Read-Uncommitted | 0 | 导致脏读 |
Read-Committed | 1 | 避免脏读,允许不可重复读和幻读 |
Repeatable-Read | 2 | 避免脏读,不可重复读,允许幻读 |
Serializable | 3 | 串行化读,事务只能一个一个执行,避免了脏读、不可重复读、幻读。执行效率慢,使用时慎重 |
29、Spring中的隔离级别都有哪些?
常量 | 解释 |
---|---|
ISOLATION_DEFAULT | 这是个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别。另外四个与JDBC的隔离级别相对应。 |
ISOLATION_READ_UNCOMMITTED | 这是事务最低的隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。这种隔离级别会产生脏读,不可重复读和幻像读。 |
ISOLATION_READ_COMMITTED | 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。 |
ISOLATION_REPEATABLE_READ | 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。 |
ISOLATION_SERIALIZABLE | 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。 |
30、简述一下Spring5有哪些新特性?
- 升级到JavaSE8和JavaEE7:要求将Java8作为最低的JDK版本。
- 反应式编程模型:是一种编程思想、编程方式,是为了简化并发编程而出现的。与传统的处理方式相比,它能够基于数据流中的事件进行反应处理。例如:a+b=c的场景,在传统编程方式下如果a、b发生变化,那么我们需要重新计算a+b来得到c的新值。而反应式编程中,我们不需要重新计算,a、b的变化事件会触发c的值自动更新。这种方式类似于我们在消息中间件中常见的发布/订阅模式。最基本的处理单元是事件流。
- 使用注解进行编程
- 支持函数式编程:将请求委托给处理函数,这些函数接受一个服务器请求实例并返回一种反应式类型。
- 使用REST端点执行反应式编程
- 对HTTP/2支持
- Kotlin:Kotlin是一种在Java虚拟机上运行的静态类型编程语言,被称之为Android世界的Swift,由JetBrains设计开发并开源。Kotlin可以编译成Java字节码,也可以编译成JavaScript,方便在没有JVM的设备上运行。
- SpringWebFlux:模块的名称是spring-webflux。新的反应式Web框架。Flux和Mono是Reactor中的两个基本概念。Flux表示的是包含0到N个元素的异步序列。Mono表示的是包含0或者1个元素的异步序列
- 使用Lambda表达式注册Bean
- SpringWebMVC支持最新的API
- 使用JUnit5执行条件和并发测试
- 包清理和弃用
- …等等
31、使用 Spring 框架能带来哪些好处?
下面列举了一些使用 Spring 框架带来的主要好处:
- Dependency Injection(DI) 方法使得构造器和 JavaBean properties 文件中的依赖关系一目了然。
- 与 EJB 容器相比较,IOC 容器更加趋向于轻量级。这样一来IOC容器在有限的内存和CPU资源的情况下进行应用程序的开发和发布就变得十分有利。
- Spring 利用了已有的技术比如 ORM 框架、logging 框架、J2EE、Quartz和 JDK Timer,以及其他视图技术。
- Spring 框架是按照模块的形式来组织的。由包和类的编号就可以看出其所属的模块,开发者仅仅需要选用他们需要的模块即可。
- 要测试一项用 Spring 开发的应用程序十分简单,因为测试相关的环境代码都已经囊括在框架中了。更加简单的是,利用 JavaBean 形式的 POJO 类,可以很方便的利用依赖注入来写入测试数据。
- Spring的Web框架亦是一个精心设计的Web MVC框架,为开发者们在web框架的选择上提供了一个除了主流框架比如 Struts、过度设计的、不流行 web 框架的以外的有力选项。
- Spring提供了一个便捷的事务管理接口,适用于小型的本地事务处理(比如在单 DB 的环境下)和复杂的共同事务处理(比如利用 JTA 的复杂 DB 环境)。
32、在 Java 中依赖注入有哪些方式?
构造器注入、Setter 方法注入、接口注入
33、BeanFactory和ApplicationContext有什么区别?
首先ApplicationContext是BeanFactory的子接口。
BeanFactory可以理解为含有bean集合的工厂类。BeanFactory包含了种bean的定义,以便在接收到客户端请求时将对应的bean实例化。
BeanFactory还能在实例化对象时生成协作类之间的关系。此举将bean自身与bean客户端的配置中解放出来。BeanFactory还包含了bean生命周期的控制,调用客户端的初始化方法(initialization Methods)和销毁方法(destruction Methods)。
从表面上看,ApplicationContext如同beanfactory一样具有bean定义、bean关联关系的设置,
根据请求分发bean的功能。但ApplicationContext在此基础上还提供了其他的功能。
- 提供了支持国际化的文本消息
- 统一的资源文件读取方式
- 已在监听器中注册的bean的事件
以下是三种较常见的ApplicationContext实现方式:
- ClassPathXmlApplicationContext:从classpath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中取得。
ApplicationContextcontext=newClassPathXmlApplicationContext(“application.xml”); - FileSystemXmlApplicationContext:由文件系统中的XML配置文件读取上下文。
ApplicationContextcontext=newFileSystemXmlApplicationContext(“application.xml”); - XmlWebApplicationContext:由Web应用的XML文件读取上下文。
34、Spring 提供几种配置方式来设置元数据?
将 Spring 配置到应用开发中有以下三种方式:
- 基于 XML 的配置
- 基于注解的配置
- 基于 Java 的配置
35、如何使用 XML 配置的方式配置 Spring?
Spring 的 XML 配置方式是使用被 Spring 命名空间的所支持的一系列的 XML 标签来实现的。Spring
有以下主要的命名空间:context、beans、jdbc、tx、aop、mvc 和 aso。
<beans>
<!-- JSON Support -->
<bean name="viewResolver"
class="org.springframework.web.servlet.view.BeanNameViewResolver"/>
<bean name="jsonTemplate"
class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"/>
</beans>
36、怎样用注解的方式配置 Spring?
注解装配在Spring 中是默认关闭的。所以需要在Spring文件中配置一下才能使用基于注解的装配模式。
<!--在标签配置完成以后,就可以用注解的方式在 Spring 中向属性、方法和构造方法中自动装配变量。-->
<beans>
<context:annotation-config/>
</beans>
37、Spring 提供哪些配置形式?
Spring对Java 配置的支持是由**@Configuration 注解和@Bean 注解**来实现的。
- @Bean 注解:方法将会实例化、配置和初始化一个新对象,这个对象将由 Spring 的 IOC 容器来管理。@Bean 声明所起到的作用与元素类似。
- @Configuration 注解:类则表示这个类的主要目的是作为 bean 定义的资源。被@Configuration 声明:的类可以通过在同一个类的内部调用@bean方法来设置嵌入bean的依赖关系。
最简单的@Configuration 声明类请参考下面的代码:
@Configuration
public class AppConfig{
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}
38、请解释 Spring Bean 的生命周期?
Spring Bean 的生命周期简单易懂。在一个bean实例被初始化时,需要执行一系列的初始化操作以达到可用的状态。同样的,当一个bean不在被调用时需要进行相关的析构操作,并从 bean 容器中移除。
bean factory负责管理在spring容器中被创建的bean的生命周期。Bean的生命周期由两组回调(call back)方法组成。
- 初始化之后调用的回调方法。
- 销毁之前调用的回调方法。
Spring 框架提供了以下四种方式来管理 bean 的生命周期事件:
- InitializingBean 和 DisposableBean 回调接口
- 针对特殊行为的其他 Aware 接口
- Bean 配置文件中的 Custominit()方法和 destroy()方法
- @PostConstruct 和@PreDestroy 注解方式
39、Spring Bean 作用域之间的区别?
Spring容器中的bean可以分为5个范围如下:
- singleton:这种bean范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个bean的实例,单例的模式由bean factory自身来维护。
- prototype:原形范围与单例范围相反,为每一个bean请求提供一个实例。
- request:在请求bean范围内,会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。
- Session:与请求范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
- global-session:global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。
40、Spring 框架中的bean是线程安全的么?
Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者结合业务而定。
但实际上,大部分的bean并没有可变的状态(比如 Serview 类和 DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态 bean 的作用域由“singleton”变更为“prototype”。
41、请举例说明如何在 Spring 中注入一个Java集合?
Spring 提供了以下四种集合类的配置元素:
- 该标签用来装配可重复的list值。
- 该标签用来装配没有重复的set值。
- 该标签可用来注入键和值可以为任何类型的键值对。
- 该标签支持注入键和值都是字符串类型的键值对。
42、请解释Spring Bean的自动装配?
在Spring框架中,在配置文件中设定bean的依赖关系是一个很好的机制,Spring容器还可以自动装配合作关系 bean 之间的关联关系。
这意味着Spring可以通过向Bean Factory中注入的方式自动完成bean之间的依赖关系。自动装配可以设置在每个bean上,也可以设定在特定的 bean 上。
使用XML 配置根据名称将一个bean设置为自动装配:
<bean id="employeeDAO" class="com.gupaoedu.EmployeeDAOImpl" autowire="byName" />
使用@Autowired 注解来自动装配指定的 bean。
@Autowired
public EmployeeDAOImpl ( EmployeeManager manager ) {
this.manager = manager;
}
//在使用@Autowired 注解之前需要在按照如下的配置方式在 Spring 配置文件进行配置才可以使用。
<context:annotation-config />
43、请解释各种自动装配模式的区别?
在Spring框架中共有 5 种自动装配:
- no:这是Spring框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在bean定义中用标签明确的设置依赖关系。
- byName:该选项可以根据bean名称设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的名称自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
- byType:该选项可以根据bean类型设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的类型自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。
- constructor:造器的自动装配和byType模式类似,但是仅仅适用于与有构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。
- autodetect:该模式自动探测使用构造器自动装配或者byType自动装配。首先,首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在bean内部没有找到相应的构造器或者是无参构造器,容器就会自动选择byTpe的自动装配方式。
44、@Required和@Autowired的区别与联系?
@Required | @Autowired | |
---|---|---|
区别 | 1.@Required作用在Setter方法上(用于检查一个Bean的属性的值在配置期间是否被赋予或设置(populated))。 2.@Required作用在Setter方法上就必须赋值,否则容器就会抛出一个BeanInitializationException异常。 | 1.@Autowired 可以作用在Setter 方法中,属性,构造函数中。 2.可以使用 @Autowired 的(required=false)选项关闭默认行为。也就是被标注的属性不会被赋值。 |
联系 | .@Required作用在Setter方法上需要生成Setter方法。 | 1.@Autowired 作用在Setter 方法也需要生成Setter方法。2.@Autowired 作用在属性上,则可以省略Setter方法,根据Bean类型来注入。 |
46、Spring 框架中有哪些不同类型的事件?
Spring 的 ApplicationContext 提供了支持事件和代码中监听器的功能。
Spring 提供了以下 5 中标准的事件:
- 上下文更新事件(ContextRefreshedEvent):该事件会在 ApplicationContext 被初始化或者更新时发布。也可以在调用 ConfigurableApplicationContext 接口中的 refresh()方法时被触发。
- 上下文开始事件(ContextStartedEvent):当容器调用 ConfigurableApplicationContext 的 Start()方法开始/重新开始容器时触发该事件。
- 上下文停止事件(ContextStoppedEvent):当容器调用 ConfigurableApplicationContext 的 Stop()方法停止容器时触发该事件。
- 上下文关闭事件(ContextClosedEvent):当 ApplicationContext 被关闭时触发该事件。容器被关闭时,其管理的所有单例 Bean 都被销毁。
- 请求处理事件(RequestHandledEvent):在 Web 应用中,当一个 http 请求(request)结束触发该事件。
还可以通过扩展 ApplicationEvent 类来开发自定义的事件。
47、FileSystemResource 和 ClassPathResource 有何区别?
FileSystemResource在配置文件中读取配置文件。
ClassPathResource在环境变量中读取配置文件。
48、在Spring框架中如何更有效的使用JDBC?
Spring 框架中通过使用模板类能更有效的使用 JDBC,也就是所谓的 JdbcTemplate。
49、在Spring中可以注入null或空字符串吗?
完全可以。
50、Spring 框架中都用到了哪些设计模式?
Spring 框架中使用到了大量的设计模式,下面列举了比较有代表性的:
- 代理模式:AOP思想的底层实现技术,Spring 中采用 JDK Proxy 和 CgLib 类库。
- 单例模式:在spring 配置文件中定义的 bean 默认为单例模式。
- 模板模式:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
- 委派模式:Srping 提供了 DispatcherServlet 来对请求进行分发。
- 工厂模式:BeanFactory 用来创建对象的实例,贯穿于 BeanFactory/ApplicationContext接口的核心理念。
51、Spring是如何解决循环依赖的问题的?
什么是循环依赖:假设A依赖B,B依赖A这样就构成了循环依赖。
循环依赖的场景:构造器循环依赖 和 属性循环依赖 或者 前两者组合。
现象:在实例化A时调用getBean() - - -> doGetBean,发现A依赖的B的实例,此时调用doGetBean去实例B,实例化的B的时候发现又依赖A,如果不解决这个循环依赖的话此时的doGetBean将会无限循环下去,导致内存溢出,程序奔溃。
属性循环依赖解决方案:Spring提供的三级缓存。
public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
//一级缓存:存放完全实例化属性赋值完成的Bean,直接可以使用。
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//三级缓存:存放实例化完成的Bean工厂。
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
//二级缓存:存放早期Bean的引用,尚未属性装配的Bean【将对象Bean提前曝光出来让大家认识,让大家使用】
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);
···
}
假设A依赖B,B依赖A(注意:这里是set属性依赖)主要执行步骤:
- A依次执行doGetBean、查询缓存(依次查询:一级缓存、二级缓存、三级缓存)、createBean创建实例,实例化完成放入三级缓存singletonFactories中,接着执行populateBean方法装配属性,但是发现有一个属性是B的对象。
- 因此再次调用doGetBean方法创建B的实例,依次执行doGetBean、查询缓存、createBean创建实例,实例化完成之后放入三级缓存singletonFactories中,执行populateBean装配属性,但是此时发现有一个属性是A对象。
- 因此再次调用doGetBean创建A的实例,但是执行到getSingleton查询缓存的时候,从三级缓存中查询到了A的实例(早期引用,未完成属性装配),此时直接返回A,并且将A对象放到二级缓存中,然后将A从三级缓存中移除。那么B就完成了属性装配,此时是一个完整的对象放入到一级缓存singletonObjects中。
- B创建完成了,则A自然完成了属性装配,也创建完成放入了一级缓存singletonObjects中。
52、Spring三级缓存的查询逻辑(getSingleton方法的整个过程)?
- 首先从singletonObjects(一级缓存)中尝试获取
- 如果获取不到并且对象在创建中,则从earlySingletonObjects(二级缓存)中获取
- 如果二级缓存也为空,并且允许从三级缓存中获取,通过一级缓存对象加同步锁
- 再从一级缓存获取
- 如果一级缓存为空,再从二级缓存获取
- 如果二级缓存为空,从三级缓存获取
- 如果三级缓存不为空
- 获取对象
- 将对象放到二级缓存
- 将对象从三级缓存中移除
protected Object getSingleton(String beanName, boolean allowEarlyReference) {
//从singletonObjects(一级缓存)中尝试获取
Object singletonObject = this.singletonObjects.get(beanName);
//如果获取不到并且对象在创建中
if (singletonObject == null && this.isSingletonCurrentlyInCreation(beanName)) {
//从earlySingletonObjects(二级缓存)中获取
singletonObject = this.earlySingletonObjects.get(beanName);
//如果二级缓存也为空,并且允许从三级缓存中获取
if (singletonObject == null && allowEarlyReference) {
synchronized(this.singletonObjects) {
//再从一级缓存获取
singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null) {//一级缓存为空
//再从二级缓存获取
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null) {//二级缓存为空
//从三级缓存获取
ObjectFactory<?> singletonFactory = (ObjectFactory)this.singletonFactories.get(beanName);
if (singletonFactory != null) {//三级缓存不为空
//获取对象
singletonObject = singletonFactory.getObject();
//将对象放到二级缓存
this.earlySingletonObjects.put(beanName, singletonObject);
//将对象从三级缓存中移除
this.singletonFactories.remove(beanName);
}
}
}
}
}
}
return singletonObject;
}
53、为什么Spring的一二三级缓不能解决构造器的循环依赖?
在Bean调用构造器实例化之前,一二三级缓存并没有Bean的任何相关信息,在实例化之后才放入三级缓存中,因此当getBean的时候缓存并没有命中,这样就抛出了循环依赖的异常了。
54、如何解决构造器的循环依赖?
#### 构造器的循环依赖---简单例子:
@Component
public class A{
private B b;
public A(B b) {
this.b = b;
}
}
@Component
public class B{
private A a;
public B(A a) {
this.a= a;
}
}
解决方案:将其中一个的构造函数中使用 @Lazy 注解。比如以下方式:
//A依赖的B由于标识了@Lazy注解,因此注入的是一个代理对象,顺利完成了A实例的构造;
//而B依赖的A是直注入完整的A对象本身。
public A(@Lazy B b) {
this.b = b;
}
@Lazy注解的原理:通过在构造器参数中标识@Lazy注解,Spring 生成并返回了一个代理对象。
55、为什么多实例Bean不能解决循环依赖?
多实例Bean是每次创建都会调用doGetBean方法,根本没有使用一二三级缓存,肯定不能解决循环依赖。
·····
内容后续不断更新中 ~~~
====================================================================
······
帮助他人,快乐自己,最后,感谢您的阅读!
所以如有纰漏或者建议,还请读者朋友们在评论区不吝指出!
个人网站…知识是一种宝贵的资源和财富,益发掘,更益分享…