Spring知识点
1、Spring中Bean是线程安全的吗?
在Spring
框架中,Bean
的线程安全性并非由Spring
容器本身直接保证,Bean
的线程安全性取决于Bean
的状态以及Bean
的作用域。
-
无状态
Bean
:如果Bean
是无状态的,即Bean
内部不保存任何状态信息,那么这样的Bean
是线程安全的。因为无论多少个线程访问,它们看到的都是相同的状态(即无状态)。Spring
框架中的很多Bean
,如Controller
、Service
和DAO
层,通常设计为无状态的,因此它们是线程安全的。 -
有状态
Bean
:如果Bean
是有状态的,即Bean
内部保存了状态信息,那么这样的Bean
就可能是线程不安全的。当多个线程同时访问和修改同一个有状态Bean
时,就可能发生资源竞争和数据不一致的问题。
此外,Bean
的作用域也会影响其线程安全性。Spring
框架中的Bean
默认是单例模式的,这意味着在整个容器中只有一个Bean
实例。因此,如果有多个线程同时访问和修改同一个单例Bean
,就可能出现线程安全问题。
为了解决这个问题,开发者可以采取一些措施来确保Bean
的线程安全性:
1)、改变Bean
的作用域:将Bean
的作用域从单例(singleton
)改为原型(prototype
),这样每次请求Bean
时都会创建一个新的实例,从而避免了多个线程共享同一个Bean
实例的问题。
2)、使用线程安全的数据结构:如果Bean
内部使用了共享的数据结构,可以考虑使用线程安全的数据结构来替代,如ConcurrentHashMap
等。
3)、避免在Bean
中保存状态信息:尽量将Bean
设计为无状态的,或者将状态信息封装在线程私有的变量中,例如使用ThreadLocal
来保存状态信息。
Spring
框架本身并不直接保证Bean
的线程安全性,开发者需要根据具体的情况来设计和实现线程安全的Bean
。
2、@Controller和@Service和@Repository是线程安全的吗?
默认配置下不是的,默认是单例的,可以加上@Scope
注解变为原型,就成了线程安全的。
如果局部变量是static
也不是线程安全的。
3、Spring中bean的作用域?
1)、singleton
(单例):这是默认的bean
作用域。在Spring IOC
容器中,每个bean
定义只对应一个实例。无论多少个bean
引用指向该bean
定义,它们都共享同一个bean
实例。
2)、prototype
(原型):每次从容器中请求该bean
时,都会创建一个新的bean
实例。这意味着每次注入或获取bean
时,都会得到一个新的对象实例。
3)、request
(请求):该作用域表示一个HTTP
请求的生命周期。在一个HTTP
请求中,bean
是单例的,不同的请求使用不同的bean
实例。这种作用域常用于处理单个HTTP
请求中的数据。
4)、session
(会话):该作用域表示一个HTTP
会话的生命周期。在一个HTTP
会话中,bean
是单例的,不同的会话使用不同的bean
实例。这种作用域通常用于存储与特定用户会话相关的信息。
5)、application
(应用):该作用域表示ServletContext
的生命周期。在一个Web
应用中,bean
是单例的,整个应用使用同一个bean
实例。这种作用域通常用于存储整个应用范围内的共享数据。
6)、websocket
(WebSocket):该作用域表示一个WebSocket
的生命周期。在一个WebSocket
连接中,bean
是单例的,不同的WebSocket
连接使用不同的bean
实例。这种作用域适用于处理WebSocket
连接相关的数据。
4、Spring的事务传播行为?
Spring
事务的传播行为说的是,当多个事务同时存在的时候,Spring
如何处理这些事务的行为。
事务传播行为实际上是使用简单的ThreadLocal
实现的,所以如果调用的方法是在新线程调用的,事务传播实际上是会失效的。
1)、propagation_required
:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。(没有新建有则加入)
2)、propagation_supports
:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。(支持当前事务,如果当前存在事务就加入,如果不存在就以非事务执行)
3)、propagation_mandatory
:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。(支持当前事务,如果当前存在事务就加入,如果不存在就抛出异常)
4)、propagation_requires_new
:创建新事务,无论当前存不存在事务,都创建新事务。(无论存在与否都新建)
5)、propagation_not_supported
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。(非事务执行,存在则挂起)
6)、propagation_never
:以非事务方式执行,如果当前存在事务,则抛出异常。(非事务执行,存在则抛出异常)
7)、propagation_nested
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED
属性执行。嵌套事务允许一个事务在另一个事务内部运行,并且内部事务可以独立回滚,而不影响外部事务。(存在在嵌套事务内执行,不存在则按required
执行)
5、Spring中的隔离级别?
1)、isolation_default
:默认的隔离级别,使用数据库默认的事务隔离级别。
2)、isolation_read_uncommitted
:读未提交,允许另外一个事务可以看到这个事务未提交的数据(允许事务在执行过程中,读取其他事务未提交的数据)。
3)、isolation_read_committed
:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新(允许事务在执行过程中,读取其他事务已经提交的数据)。
4)、isolation_repeatable_read
:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新(在同一个事务内,任意时刻的查询结果都是一致的)。
5)、isolation_serializable
:一个事务在执行的过程中完全看不到其它事务对数据库所做的更新,所有事务逐个依次执行。
6、Spring在多线程环境下如何保证事务一致性?
在Spring
框架中,要保证多线程环境下的事务一致性,可以采用以下几种策略:
1)、利用编程式事务解决
2)、直接利用JDBC
提供的API
来手动控制事务提交和回滚
3)、尝试采用分布式事务的思路来解决
7、Spring中Bean的生命周期?
Spring
中Bean
的生命周期是从Bean
实例化之后,即通过反射创建出对象之后,到Bean
成为一个完整对象,最终存储到单例池中。这个过程大体上分为以下四个阶段:
1)、实例化:这是Bean
生命周期的开始,Spring
使用Java
反射API
来创建Bean
的实例。
这里有一个比较重要的接口InstantiationAwareBeanPostProcessor
,会在实例化前后执行,分别执行postProcessBeforeInstantiation()
和postProcessAfterInstantiation()
方法。
2)、属性注入:在Bean
实例化之后,Spring
会按照Bean
定义中的信息以及通过配置文件中指定的属性值,对Bean
的属性进行注入。这个过程包括了对Bean
的依赖注入,也就是将其他Bean
注入到这个Bean
中。
属性注入以后Spring
会检测该对象是否实现了xxxAware
接口,通过Aware
类型的接口,可以让我们拿到Spring
容器的一些资源:
a、如果Bean
实现BeanNameAware
接口,会调用它实现的setBeanName(String name)
方法,注入Bean
的名字。
b、如果Bean
实现BeanClassLoaderAware
接口,调用setBeanClassLoader(ClassLoader classLoader)
方法,注入ClassLoader
对象的实例。
c、如果Bean
实现BeanFactoryAware
接口,会调用它实现的setBeanFactory(BeanFactory beanFactory)
方法,注入的是Spring
工厂。
d、如果Bean
实现ApplicationContextAware
接口,会调用setApplicationContext(ApplicationContext applicationContext)
方法,注入Spring
上下文。
3)、初始化:在属性注入之后,Spring
会调用Bean
的初始化方法。这通常是通过在Bean
的定义中指定<init-method>
标签或者在Bean
类中使用@PostConstruct
注解来实现的。如果Bean
实现了InitializingBean
接口,执行afeterPropertiesSet()
方法。在这个阶段,Bean
可以进行一些必要的初始化工作,比如加载配置文件、建立数据库连接等。
这里有一个比较重要的接口BeanPostProcessor
,会在初始化前后执行,分别执行postProcessBeforeInitialization()
和postProcessAfterInitialization()
方法。
spring aop
替换对象的时候并不在postProcessBeforeInstantiation
替换对象,而是在postProcessAfterInitialization
处理的。
4)、销毁:当Bean
不再需要时,Spring
会调用Bean
的销毁方法。这通常是通过在Bean
的定义中指定<destroy-method>
标签或者在Bean
类中使用@PreDestroy
注解来实现的。如果Bean
实现了DisposableBean
这个接口,会调用其实现的destroy()
方法执行销毁。在这个阶段,Bean
可以进行一些必要的清理工作,比如释放数据库连接、关闭文件流等。
8、AOP在Spring Bean生命周期的哪一步?
在Spring
框架中,AOP
(面向切面编程)的应用主要发生在Bean
的生命周期的初始化之后的阶段。具体来说,当Bean
的属性注入完成后,Spring
会检查该Bean
是否需要进行AOP
增强。如果需要,Spring
会为该Bean
生成一个代理对象,这个代理对象会包含目标Bean
的所有方法,并且会在方法调用前后织入切面逻辑。
这个代理对象的创建过程是在Bean
的初始化方法(如afterPropertiesSet()
或指定的<bean>
元素的init-method
)调用之后进行的。因此,我们可以说AOP
的应用在Spring Bean
生命周期的初始化之后的阶段。
需要注意的是,AOP
代理的创建是懒加载的,也就是说,只有当Bean
第一次被使用时,才会创建代理对象。此外,对于单例Bean
,代理对象只会被创建一次,并被缓存起来供后续使用。对于原型作用域(prototype
)的Bean
,每次请求都会创建一个新的代理对象。
9、Spring事务原理?
Spring
事务的本质实际上是数据库对事务的支持,没有数据库的事务支持,Spring
是无法提供事务功能的。Spring
事务管理主要依赖于AOP
(面向切面编程)来实现。
在Spring
中,事务管理是通过一个名为PlatformTransactionManager
的接口来实现的,它负责处理事务的提交、回滚等操作。Spring
提供了多种实现方式,如DataSourceTransactionManager
(用于JDBC
事务管理)、HibernateTransactionManager
(用于Hibernate ORM
框架的事务管理)等。
Spring
事务管理的核心是通过AOP
来实现的。在Spring
中,你可以通过@Transactional
注解来声明一个方法或类需要事务管理。当这些方法被调用时,Spring
会为其创建一个代理对象,并在代理对象中织入事务管理的逻辑。
当方法被调用时,代理对象会首先开启一个事务,然后执行目标方法。如果目标方法执行成功,则代理对象会提交事务。如果目标方法执行失败并抛出异常,则代理对象会回滚事务。这个过程是透明的,你不需要在代码中显式地调用事务的开启、提交或回滚方法。
需要注意的是,Spring
事务管理默认使用的是数据库的事务隔离级别和传播行为。你可以通过@Transactional
注解的属性来修改这些默认值。例如,你可以通过isolation
属性来设置事务的隔离级别,通过propagation
属性来设置事务的传播行为。
此外,Spring
还支持通过编程式事务管理,即通过TransactionTemplate
或PlatformTransactionManager
的API
来手动控制事务的开启、提交和回滚。但这种方式相对繁琐,且容易出错,因此在实际应用中较少使用。
10、Spring是如何解决循环依赖的?
什么是循环依赖:从字面上来理解就是A依赖B的同时B也依赖了A,一言以蔽之:两者相互依赖。
通常来说,如果问Spring
内部如何解决循环依赖,一定是默认的单例Bean
中,属性互相引用的场景。
Spring
框架通过三级缓存机制来解决循环依赖问题,这个三级缓存机制主要包括三个组件:
1)、一级缓存:也被称为单例池(singletonObjects
),它存放的是已经完全初始化好的Bean
实例(实例化和初始化都完成的对象)。
2)、二级缓存:也被称为早期曝光对象缓存(earlySingletonObjects
)。当一个Bean
被实例化但还未完成属性注入和初始化时,它会被放入这个缓存中。这个缓存的主要作用是为了解决循环依赖问题。当Bean A在初始化过程中需要依赖Bean B,而Bean B又依赖Bean A时,Spring可以从这个缓存中获取到还未完全初始化的Bean A,从而完成Bean B的初始化。
3)、三级缓存:也被称为早期曝光对象工厂(singletonFactories
)。当一个Bean被实例化但还未放入二级缓存时,它的工厂对象(用于创建Bean实例的对象)会被放入这个缓存中,用于创建二级缓存中的对象。这个缓存的主要作用是为了在需要的时候能够从工厂对象中创建出Bean实例。
当Spring遇到循环依赖问题时,它会按照以下步骤解决,当Bean A、Bean B两个类发生循环引用时大致流程:
1)、Bean A完成实例化后,去创建一个对象工厂,并放入三级缓存当中。
如果A被AOP代理,那么通过这个工厂获取到的就是A代理后的对象。
如果A没有被AOP 代理,那么这个工厂获取到的就是A实例化的对象。
2)、Bean A进行属性注入时,去创建Bean B。
3)、Bean B进行属性注入,需要Bean A,则从三级缓存中去取Bean A 工厂代理对象并注入,然后删除三级缓存中的Bean A工厂,将Bean A对象放入二级缓存。
4)、Bean B完成后续属性注入,直到初始化结束,将Bean B放入一级缓存。
5)、Bean A从一级缓存中取到Bean B并且注入Bean B,直到完成后续操作,将Bean A从二级缓存删除并且放入一级缓存,循环依赖结束。
通过这种三级缓存机制,Spring能够在不破坏单例模式的前提下,解决循环依赖问题。
11、二级缓存能不能解决Spring的循环依赖?
可以,三级缓存的功能是只有真正发生循环依赖的时候,才去提前生成代理对象,否则只会创建一个工厂并将其放入到三级缓存中,但是不会去通过这个工厂去真正创建对象。
如果要使用二级缓存解决循环依赖,意味着所有Bean
在实例化后就要完成AOP
代理,这样违背了Spring
设计的原则,Spring
在设计之初就是在Bean
生命周期的最后一步来完成AOP
代理,而不是在实例化后就立马进行AOP
代理。
如果单纯为了打破循环依赖,不需要三级缓存,两级就够了。三级缓存是否为延迟代理的创建,尽量不打破 Bean
的生命周期。
12、什么情况下循环依赖可以被处理?
1、AB相互依赖,均采用setter
方法注入,单例下可以被解决,原型下不可以被解决。因为原型下每一次getBean()
都会产生一个新的Bean,无法利用缓存,并且可能由于创建太多Bean导致OOM。
2、AB相互依赖,均采用构造器注入,无法解决循环依赖,因为无法利用缓存,new的时候就堵塞了,也及时先有鸡还是先有蛋的问题。
3、AB相互依赖,A中注入B的方式为setter方法,B中注入A的方式为构造器,可以解决。
4、AB相互依赖,B中注入A的方式为setter方法,A中注入B的方式为构造器,不可以解决。
如果是原型bean的循环依赖,Spring无法解决。
如果是构造参数注入的循环依赖,Spring无法解决。
- 如果是原型bean,那么就意味着每次都要去创建对象,无法利用缓存。
- 如果是构造方法注入,那么就意味着需要调用构造方法注入,也无法利用缓存。
在Spring中,只有同时满足以下两点才能解决循环依赖的问题:
- 依赖的Bean必须都是单例
- 依赖注入的方式,必须不全是构造器注入,且
beanName
字母序在前的不能是构造器注入
单例意味着只需要创建一次对象,后面就可以从缓存中取出来,字段注入,意味着我们无需调用构造方法进行注入。
13、ApplicationContext通常的实现是什么?BeanFactory 通常的实现是什么?
ApplicationContext
是Spring
框架中的一个核心接口,它代表了Spring
容器的上下文环境。ApplicationContext
提供了更完整的功能集,比基本的BeanFactory
更为强大,它包括了国际化处理、事件传播、资源加载等功能。
ApplicationContext
通常的实现:
1)、FileSystemXmlApplicationContext
:此容器从一个xml
文件中加载beans
的定义,xml Bean
配置文件的全路径名必须提供给它的构造函数。
2)、ClassPathXmlApplicationContext
:此容器也从一个xml
文件中加载beans
的定义,这里你需要正确设置classpath
因为这个容器将在classpath
里找bean配置。
3)、WebXmlApplicationContext
:此容器加载一个xml
文件,此文件定义了一个WEB
应用的所有bean
。它加载在Web
应用程序的/WEB-INF
目录下的XML
配置文件。
4)、AnnotationConfigApplicationContext
:这个类用于读取Java
配置类(使用@Configuration
注解的类)和组件扫描(使用@ComponentScan
注解)。它是Java
配置的基础。
5)、AnnotationConfigWebApplicationContext
:与AnnotationConfigApplicationContext
类似,但是专门为Web
应用程序设计,它支持Java
配置和组件扫描。
BeanFactory
是Spring
框架中的一个核心接口,它提供了一种先进的配置机制,使得应用程序可以在运行时管理对象及其依赖关系。与 ApplicationContext
相比,BeanFactory
提供了一种更为基础、低级别的配置方式。
BeanFactory
通常的实现:
最常用的BeanFactory
实现是XmlBeanFactory
类,它根据xml
文件中的定义加载beans
,该容器从xml
文件读取配置元数据并用它去创建一个完全配置的系统或应用。XmlBeanFactory
类在Spring
的后续版本中已被弃用,取而代之的是 DefaultListableBeanFactory
,它是BeanFactory
接口最常用和最重要的实现。
DefaultListableBeanFactory
不仅实现了BeanFactory
接口,还提供了更多的功能,如支持单例bean
、原型bean
、延迟加载bean
等。此外,它还支持bean
的别名注册、FactoryBean
的处理等高级特性。
除了DefaultListableBeanFactory
,BeanFactory
还有其他的实现,如SimpleAliasRegistry
,它提供了一个简单的别名注册机制。但是,在实际开发中,大多数情况下,开发者都是直接使用DefaultListableBeanFactory
或其子类,如 XmlWebApplicationContext
、FileSystemXmlApplicationContext
等,这些类内部都使用了 DefaultListableBeanFactory
作为 bean
工厂的实现。
总的来说,DefaultListableBeanFactory
是BeanFactory
接口最常用的实现,它提供了丰富的功能和灵活的配置方式,使得开发者可以轻松地管理和配置Spring
容器中的bean
。
14、解释不同方式的自动装配?
一级Spring的自动装配:在spring中,使用autowire来配置自动装载模式,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象。
有五种自动装配的方式,可以用来指导Spring容器用自动装配方式来进行依赖注入。
1)、no
:默认的方式是不进行自动装配,通过显式设置ref 属性来进行装配。
2)、byName
:通过参数名自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byname,之后容器试图匹配、装配和该bean的属性具有相同名字的bean。
3)、byType
:通过参数类型自动装配,Spring容器在配置文件中发现bean的autowire属性被设置成byType,之后容器试图匹配、装配和该bean的属性具有相同类型的bean。如果有多个bean符合条件,则抛出错误。
4)、constructor
:这个方式类似于byType, 但是要提供给构造器参数,如果没有确定的带参数的构造器参数类型,将会抛出异常。构造函数的参数通过byType进行装配。
5)、autodetect
:首先尝试使用constructor来自动装配,如果无法工作,则使用byType方式。
15、SpringBoot常用注解
1)、@RequestMapping
:将Web请求与请求处理类中的方法进行映射。
2)、@RequestBody
:将请求主体中的参数绑定到一个对象中。
3)、@Valid
:对请求主体中的参数进行校验。
4)、@GetMappings
:用于处理HTTP GET请求,并将请求映射到具体的处理方法中。
5)、@PostMapping
:用于处理HTTP POST请求,并将请求映射到具体的处理方法中。
6)、@PutMapping
:用于处理HTTP PUT请求,并将请求映射到具体的处理方法中。
7)、@DeleteMapping
:用于处理HTTP DELETE请求,并将请求映射到具体的处理方法中。
8)、@PatchMapping
:用于处理HTTP PATCH请求,并将请求映射到具体的处理方法中。
9)、@ControllerAdvice
:用来处理控制器所抛出的异常信息,需要和@ExceptionHandler、@InitBinder以及@ModelAttribute注解搭配使用。在被@ControllerAdvice所标注的类中定义一个用于处理具体异常的方法。
10)、@ExceptionHandler
:用于标注处理特定类型异常类所抛出异常的方法。
11)、@InitBinder
:在类中进行全局的配置,用于标注初始化WebDataBinider的方法,该方法用于对Http请求传递的表单数据进行处理,如时间格式化、字符串处理等。
12)、@ModelAttribute
:配置与视图相关的参数。
13)、@ResponseBody
:将控制器中方法的返回值写入到HTTP响应中。@RestController相当于是@Controller和@ResponseBody的组合注解。
14)、@ResponseStatus
:指定响应所需要的HTTP STATUS。
15)、@PathVariable
:将方法中的参数绑定到请求URI中的模板变量上。如果参数是一个非必须的,可选的项,则可以在@PathVariable中设置require = false。
16)、@RequestParam
:用于将方法的参数与Web请求的传递的参数进行绑定。特别的,如果传递的参数为空,还可以通过defaultValue设置一个默认值。
17)、@Controller
:用于标注Spring MVC的控制器。
18)、@RestController
:相当于@Controller和@ResponseBody的快捷方式。当使用此注解时,不需要再在方法上使用@ResponseBody注解。
19)、@ModelAttribute
:将方法的返回值绑定到具体的Model上。
20)、@CrossOrigin
:为请求处理类或请求处理方法提供跨域调用支持。
21)、@Configuration
:用于定义配置类,可替换XML配置文件,被注解的类内部包含有一个或多个被@Bean注解的方法,用于构建Bean定义,初始化Spring容器。
22)、@ComponentScan
:用于配置Spring需要扫描的被组件注解注释的类所在的包。
23)、@Component
:用于标注一个普通的组件类,被此注解的类需要被纳入到Spring Bean容器中并进行管理。
24)、@Service
:用于标注业务逻辑类。
25)、@Repository
:用于标注DAO层的数据持久化类。
26)、@DependsOn
:可以配置Spring IOC容器在初始化一个Bean之前,先初始化其他的Bean对象。
java
@Component
@DependsOn("dependson02")
public class Dependson01 {}
27)、@Bean
:告知Spring被此注解所标注的类将需要纳入到Bean管理工厂中。
28)、@Scope
:用来定义@Component标注的类的作用范围以及@Bean所标记的类的作用范围。
29)、@Autowired
:用于标记Spring将要解析和注入的依赖项。可以作用在构造函数、字段和setter方法上。
30)、@Primary
:当系统中需要配置多个具有相同类型的bean时,@Primary可以定义这些Bean的优先级。
31)、@PostConstruct
与@PreDestroy
:@PostConstruct注解用于标注在Bean被Spring初始化之前需要执行的方法,@PreDestroy注解用于标注Bean被销毁前需要执行的方法。
32)、@Qualifier
:当系统中存在同一类型的多个Bean时,可以使用@Autowired选择正确的依赖项。
33)、@SpringBootApplication
:一个快捷的配置注解,在被它标注的类中,可以定义一个或多个Bean,并自动触发自动配置Bean和自动扫描组件。此注解相当于@Configuration、@EnableAutoConfiguration和@ComponentScan的组合。
34)、@EnableAutoConfiguration
:用于通知Spring,根据当前类路径下引入的依赖包,自动配置与这些依赖包相关的配置项。@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
ENABLED_OVERRIDE_PROPERTY:用于覆盖自动配置是否启用的环境属性。
exclude:排除特定的自动配置类。
excludeName:排除特定的自动配置类名。
35)、@ConditionalOnClass
与@ConditionalOnMissingClass
:根据是否存在某个类作为判断依据来决定是否要执行某些配置。
java
@Configuration
@ConditionalOnClass(DataSource.class)
class MySQLAutoConfiguration {}
36)、@ConditionalOnBean
与@ConditionalOnMissingBean
:根据是否存在某个对象作为依据来决定是否要执行某些配置方法。
java
@Bean
@ConditionalOnBean(name="dataSource")
LocalContainerEntityManagerFactoryBean entityManagerFactory(){}
@Bean
@ConditionalOnMissingBean
public MyBean myBean(){}
37)、@ConditionalOnProperty
:根据Spring配置文件中的配置项是否满足配置要求,从而决定是否要执行被其标注的方法。
java
@Bean
@ConditionalOnProperty(name="alipay",havingValue="on")
Alipay alipay(){return new Alipay();}
38)、@ConditionalOnResource
:用于检测当某个配置文件存在时,则触发被其标注的方法。
java
@ConditionalOnResource(resources = "classpath:website.properties")
Properties addWebsiteProperties(){}
39)、@ConditionalOnWebApplication
与@ConditionalOnNotWebApplication
:用于判断当前的应用程序是否是Web应用程序。如果当前应用是Web应用程序,则使用Spring WebApplicationContext,并定义其会话的生命周期。
java
@ConditionalOnWebApplication
HealthCheckController healthCheckController(){}
40)、@ConditionalExpression
:让我们控制更细粒度的基于表达式的配置条件限制。当表达式满足某个条件或者表达式为真的时候,将会执行被此注解标注的方法。
java
@Bean
@ConditionalException("${localstore} && ${local == 'true'}")
LocalFileStore store(){}
41)、@Conditional
:可以控制更为复杂的配置条件,在Spring内置的条件控制注解不满足应用需求的时候,可以使用此注解定义自定义的控制条件,以达到自定义的要求。
java
@Conditioanl(CustomConditioanl.class)
CustomProperties addCustomProperties(){}
42)、Spring AOP
注解:@Aspect
,@Before
,@After
,@Around
,@Pointcut
。
16、Spring框架中有哪些不同类型的事件?
Spring
提供了以下5种标准的事件:
1)、上下文更新事件:在调用ConfigurableApplicationContext
接口中的refresh()
方法时被触发。
2)、上下文开始事件:当容器调用ConfigurableApplicationContext
的start()
方法开始/重新开始容器时触发该事件。
3)、上下文停止事件:当容器调用ConfigurableApplicationContext
的stop()
方法停止容器时触发该事件。
4)、上下文关闭事件:当ApplicationContext
被关闭时触发该事件,容器被关闭时其管理的所有单例bean都被销毁。
5)、请求处理事件:在Web应用中,当一个HTTP
请求结束触发该事件。
如果一个bean实现了ApplicationListener
接口,当一个ApplicationEvent
被发布以后,bean会自动被通知。
17、Spring IOC容器中只存放单例Bean吗?
Spring IOC
容器中只存放单例Bean
。
IOC
在初始化时,只会将scope= singleton
(单例)的对象进行实例化,而不会去实例化scope=prototype
的对象(多例)。
单例作用域下,每次共用一个bean实例,并且这个bean实例是被保存到容器中的。
说明多例作用域下,每次都会创建一个bean实例并返回。
18、Spring源码中应用的设计模式?
1)、工厂设计模式:Spring
使用工厂模式通过BeanFactory
和ApplicationContext
创建bean
对象,根据传入一个唯一的标识来获得Bean
对象。
2)、代理设计模式:Spring
的AOP
功能用到了JDK
的动态代理和CGLIB
字节码生成技术。
3)、单例设计模式:Spring
中的bean
默认都是单例的。Spring
依赖注入Bean
实例默认是单例的。Spring
的依赖注入(包括lazy-init
方式)都是发生在AbstractBeanFactory
的getBean
里。getBean
的doGetBean
方法调用getSingleton
进行bean
的创建。
4)、模板方法模式:Spring
中的jdbcTemplate
、hibernateTemplate
等以Template
结尾的对数据库操作的类,它们就使用到了模板模式。可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。
5)、包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。
6)、观察者模式:Spring
事件驱动模型就是观察者模式很经典的一个应用。Spring
的事件驱动模型使用的是观察者模式,Spring
中Observer
模式常用的地方是listener
的实现。如:ApplicationContextEvent
、ApplicationListener
。
7)、适配器模式:Spring AOP
的增强或通知(Advice
)使用到了适配器模式、Spring MVC
中也是用到了适配器模式适配Controller
。
8)、策略设计模式:例如Resource
的实现类,针对不同的资源文件,实现了不同方式的资源获取策略。
UrlResource
:访问网络资源的实现类。
ClassPathResource
:访问类加载路径里资源的实现类。
FileSystemResource
:访问文件系统里资源的实现类。
ServletContextResource
:访问相对于 ServletContext
路径里的资源的实现类。
InputStreamResource
:访问输入流资源的实现类。
ByteArrayResource
:访问字节数组资源的实现类。
9)、桥接模式:可以根据客户的需求能够动态切换不同的数据源。比如我们的项目需要连接多个数据库,客户在每次访问中根据需要会去访问不同的数据库。
10)、装饰器模式:Spring
中用到的包装器模式在类名上有两种表现:一种是类名中含有Wrapper
,另一种是类名中含有Decorator
。
19、构造方法注入和setter注入之间的区别吗?
1)、Setter
注入可以将依赖项部分注入,构造方法注入不能部分注入。因为调用构造方法必须传入正确的构造参数,否则就会报错。
2)、如果我们为同一属性提供Setter
和构造方法注入,Setter
注入将覆盖构造方法注入,但是构造方法注入不能覆盖setter
注入值。显然,构造方法注入被称为创建实例的第一选项。
3)、构造注入任意修改都会创建一个新实例,setter
注入不会创建新实例。
4)、构造注入用于设置很多属性,setter
注入用于设置少量属性。
5)、使用setter
注入你不能保证所有的依赖都被注入,这意味着你可以有一个对象依赖没有被注入。在另一方面构造方法注入直到你所有的依赖都注入后才开始创建实例。
6)、在构造函数注入,存在循环依赖问题,如果A和B对象相互依赖,A依赖于B,B也依赖于A,此时在创建对象的A或者B时,Spring
抛出ObjectCurrentlyInCreationException
。所以Spring
可以通过setter
注入,从而解决循环依赖的问题。
7)、构造器参数实现强制依赖,setter
方法实现可选依赖。
8)、构造注入先执行,setter
注入后执行。
20、聊聊Spring事务失效的12种场景?
【一、事务不生效】
【1、访问权限问题】
方法的访问权限被定义成了private
会导致事务失效,Spring
要求被代理方法必须是public
的。
自定义的事务方法(即目标方法),它的访问权限不是public
,而是private
、default
或protected
的话,Spring
则不会提供事务功能。
【2、方法用final修饰】
如果将事务方法定义成final
,这样会导致事务失效。
spring
事务底层使用了aop
,也就是通过jdk
动态代理或者cglib
,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final
修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。
注意:如果某个方法是static
的,同样无法通过动态代理,变成事务方法。
【3、方法内部调用】
有时候我们需要在某个Service
类的某个方法中,调用另外一个事务方法,该事务方法拥有事务的能力是因为spring aop
生成代理了对象,但是这种方法直接调用了this
对象的方法,所以该事务方法不会生成事务。由此可见,在同一个类中的方法直接内部调用,会导致事务失效。
【3.1、新加一个Service方法】
只需要新加一个Service
方法,把@Transactional
注解加到新Service
方法上,把需要事务执行的代码移到新方法中。
【3.2、在该Service类中注入自己】
如果不想再新加一个Service
类,在该Service
类中注入自己也是一种选择。
spring ioc
内部的三级缓存保证了它,不会出现循环依赖问题。
【3.3、通过AopContent类】
在该Service
类中使用AopContext.currentProxy()
获取代理对象,((ServiceA)AopContext.currentProxy())
。
【4、未被spring管理】
使用spring
事务的前提是:对象要被spring
管理,需要创建bean
实例。通常情况下,我们通过@Controller
、@Service
、@Component
、@Repository
等注解,可以自动实现bean
实例化和依赖注入的功能。
如果忘了加@Service
注解,那么该类不会交给spring
管理,所以它的方法也不会生成事务。
【5、多线程调用】
假设在事务方法add
中,调用了事务方法doOtherThing
,但是事务方法doOtherThing是在另外一个线程中调用的。这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。
同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。
【6、表不支持事务】
在mysql5
之前,默认的数据库引擎是myisam
。myisam好用,但有个很致命的问题是:不支持事务。
在mysql5以后,myisam已经逐渐退出了历史的舞台,取而代之的是innodb
。
有时候我们在开发的过程中,发现某张表的事务一直都没有生效,那不一定是spring
事务的锅,最好确认一下你使用的那张表,是否支持事务。
【7、未开启事务】
有时候,事务没有生效的根本原因是没有开启事务。
因为springboot
通过DataSourceTransactionManagerAutoConfiguration
类,已经默默的帮你开启了事务。但如果你使用的还是传统的spring项目,则需要在applicationContext.xml
文件中,手动配置事务相关参数。如果忘了配置,事务肯定是不会生效的。
默默的说一句,如果在pointcut
标签中的切入点匹配规则,配错了的话,有些类的事务也不会生效。
【二、事务不回滚】
【1、错误的传播特性】
在使用@Transactional
注解时,是可以指定propagation
参数的。如果我们在手动设置propagation参数的时候,把传播特性设置错了,比如:@Transactional(propagation = Propagation.NEVER)
,我们可以看到add方法的事务传播特性定义成了Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。
目前只有这三种传播特性才会创建新事务:REQUIRED
,REQUIRES_NEW
,NESTED
。
【2、自己吞了异常】
事务不会回滚,最常见的问题是:开发者在代码中手动try...catch
了异常。这种情况下spring
事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。如果想要spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。
【3、手动抛了别的异常】
即使开发者没有手动捕获异常,但如果抛的异常不正确,spring
事务也不会回滚。
因为spring
事务,默认情况下只会回滚RuntimeException
(运行时异常)和Error
(错误),对于普通的Exception
(非运行时异常),它不会回滚。
【4、自定义了回滚异常】
在使用@Transactional
注解声明事务时,有时我们想自定义回滚的异常,spring
也是支持的。可以通过设置rollbackFor
参数,来完成这个功能。但如果这个参数的值设置错了,就会引出一些莫名其妙的问题,例如:@Transactional(rollbackFor = BusinessException.class)
。如果在执行代码,程序报错了,抛了SqlException
、DuplicateKeyException
等异常。而BusinessException
是我们自定义的异常,报错的异常不属于BusinessException
,所以事务也不会回滚。即使rollbackFor
有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。
这是为什么呢?因为如果使用默认值,一旦程序抛出了Exception
,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成:Exception
或Throwable
。
【5、嵌套事务回滚多了】
java
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
roleService.doOtherThing();
}
这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing
方法时,如果出现了异常,只回滚doOtherThing
方法里的内容,不回滚 userMapper.insertUser
里的内容,即回滚保存点。但事实是,insertUser也回滚了为什么?因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。
怎么样才能只回滚保存点呢?
java
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
try {
roleService.doOtherThing();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
可以将内部嵌套事务放在try/catch
中,并且不继续往上抛异常。这样就能保证,如果内部嵌套事务中出现异常,只回滚内部事务,而不影响外部事务。
21、让人头痛的大事务问题到底要如何解决?
在使用spring
事务时,有个让人非常头疼的问题,就是大事务问题。
通常情况下,我们会在方法上@Transactional
注解,填加事务功能,但@Transactional
注解,如果被加到方法上,有个缺点就是整个方法都包含在事务当中了。如果query
方法非常多,调用层级很深,而且有部分查询方法比较耗时的话,会造成整个事务非常耗时,而从造成大事务问题。
【大事务引发的问题】
在分享解决办法之前,先看看系统中如果出现大事务可能会引发哪些问题:
- 死锁
- 回滚时间长
- 并发情况下数据库连接池被沾满
- 锁等待
- 接口超时
- 数据库主从延迟
可以看出如果系统中出现大事务时,问题还不小,所以我们在实际项目开发中应该尽量避免大事务的情况。如果我们已有系统中存在大事务问题,该如何解决呢?
【解决办法】
【1、少用@Transactional注解,使用编程式事务】
少用@Transactional
注解为什么?
1、@Transactional
注解是通过spring
的aop
起作用的,但是如果使用不当,事务功能可能会失效。
2、@Transactional
注解一般加在某个业务方法上,会导致整个业务方法都在同一个事务中,粒度太粗,不好控制事务范围,是出现大事务问题的最常见的原因。
那我们该怎么办呢?
可以使用编程式事务,在spring
项目中使用TransactionTemplate
类的对象,手动执行事务。部分代码如下:
java
transactionTemplate.execute((status) => {doSameThing...return Boolean.TRUE;})
使用TransactionTemplate
的编程式事务功能自己灵活控制事务的范围,是避免大事务问题的首选办法。
建议在项目中少使用@Transactional
注解开启事务,并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用@Transactional
注解开启事务开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。
上面聊的这些内容都是基于@Transactional
注解的,主要说的是它的事务问题,我们把这种事务叫做:声明式事务
。其实,spring
还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种事务叫做:编程式事务
。在spring
中为了支持编程式事务,专门提供了一个类:TransactionTemplate
,在它的execute
方法中,就实现了事务的功能。
相较于@Transactional
注解声明式事务,我更建议大家使用,基于TransactionTemplate
的编程式事务。主要原因如下:
- 避免由于
spring aop
问题,导致事务失效的问题。 - 能够更小力度地控制事务的范围,更直观。
【2、将查询(select)方法放到事务外】
如果出现大事务,可以将查询(select
)方法放到事务外,也是比较常用的做法,因为一般情况下这类方法是不需要事务的。这样就能有效的减少事务的粒度。
【3、事务中避免远程调用】
我们在接口中调用其他系统的接口是不能避免的,由于网络不稳定,这种远程调的响应时间可能比较长,如果远程调用的代码放在某个事物中,这个事物就可能是大事务。当然,远程调用不仅仅是指调用接口,还有包括:发MQ
消息,或者连接redis
、mongodb
保存数据等。
远程调用的代码可能耗时较长,切记一定要放在事务之外。使用编程式事务更好控制一些。
有些朋友可能会问,远程调用的代码不放在事务中如何保证数据一致性呢?这就需要建立:重试+补偿机制
,达到数据最终一致性了。
【4、事务中避免一次性处理太多数据】
如果一个事务中需要处理的数据太多,也会造成大事务问题。比如为了操作方便,你可能会一次批量更新1000条数据,这样会导致大量数据锁等待,特别在高并发的系统中问题尤为明显。
解决办法是分页处理,1000条数据,分50页,一次只处理20条数据,这样可以大大减少大事务的出现。
【5、非事务执行】
在使用事务之前,我们都应该思考一下,是不是所有的数据库操作都需要在事务中执行?
其实有些方法是可以不在事务中执行的,比如操作日志和统计数量这种业务允许少量数据不一致的情况。
当然大事务中要鉴别出哪些方法可以非事务执行,其实没那么容易,需要对整个业务梳理一遍,才能找出最合理的答案。
【6、异步处理】
还有一点也非常重要,是不是事务中的所有方法都需要同步执行?我们都知道,方法同步执行需要等待方法返回,如果一个事务中同步执行的方法太多了,势必会造成等待时间过长,出现大事务问题。例如:order
方法用于下单,delivery
方法用于发货,是不是下单后就一定要马上发货呢?答案是否定的。这里发货功能其实可以走MQ
异步处理逻辑。
22、CGLIB动态代理和JDK动态代理的区别?
1)、实现机制 :JDK
动态代理主要基于Java
的反射机制,它要求目标对象必须实现至少一个接口,然后代理对象会实现这些接口,并将方法调用委托给InvocationHandler
处理。CGLIB
动态代理则基于字节码操作,通过继承目标类(而不是实现接口)来创建代理对象。它使用第三方库ASM
或CGLIB
来动态生成目标类的子类,并重写其中的方法。
2)、目标类限制 :JDK
动态代理要求目标类至少实现一个接口,因为它通过接口来创建代理对象。如果目标类没有实现任何接口,那么JDK
动态代理将无法使用。CGLIB
动态代理没有这样的限制,它可以通过继承来创建代理对象,因此可以代理没有实现接口的类。
3)、性能 :在性能上,JDK
动态代理的创建速度通常比CGLIB
动态代理要快,因为它不需要生成新的类。然而,在方法调用上,JDK
动态代理的性能可能较低,因为它需要通过反射来调用方法。CGLIB
动态代理的创建速度较慢,因为它需要生成新的类。但是,在方法调用上,CGLIB
动态代理的性能通常更高,因为它可以直接调用子类的方法,而无需通过反射。
4)、依赖库 :JDK
动态代理是Java
标准库的一部分,无需引入其他依赖。CGLIB
动态代理则需要依赖CGLIB
库或其他第三方库。
5)、应用场景 :JDK
动态代理适用于代理接口的场景,例如Spring
中的事务处理、日志记录等。CGLIB
代理适用于代理类的场景,例如Spring
中的AOP
切面编程等。
总的来说,JDK
动态代理和CGLIB
动态代理各有其优点和适用场景。在选择使用哪种代理方式时,应根据具体的需求和条件进行权衡。例如,如果目标类已经实现了接口,那么JDK
动态代理可能是一个更好的选择。如果目标类没有实现接口,那么CGLIB
动态代理可能更适合。
CGLIB
是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法因为是继承,所以该类或方法最好不要声明成final
。
1、如果目标对象实现了接口,默认情况下会采用JDK
的动态代理实现AOP
。
2、如果目标对象实现了接口,可以强制使用CGLIB
实现AOP
。
3、如果目标对象没有实现了接口,必须采用CGLIB
库,spring
会自动在JDK
动态代理和CGLIB
之间转换。
23、静态代理和动态代理区别?
1)、静态代理通常只代理一个类,动态代理可以代理一个接口下的多个实现类,也可以代理一个类,不需要业务类继承接口。
2)、静态代理事先知道要代理的是什么,而动态代理不知道要代理什么东西,只有在运行时才知道。
3)、静态代理是在编译阶段就确定代理类的代码,并在程序运行前就已经存在了代理类的class
文件。动态代理是在运行时通过反射机制动态生成代理类,即编译完成后没有实际的class
文件,而是在运行时动态生成类字节码,并加载到JVM
中。
4)、静态代理对象需要实现接口,否则不能使用静态代理。动态代理对象不需要实现接口,但是要求目标对象必须实现接口,否则不能使用动态代理。
5)、动态代理是通过实现JDK
里的InvocationHandler
接口的invoke
方法,或者使用CGLIB
框架通过派生的子类来实现代理。
总结来说,静态代理在编译时就确定了代理类,而动态代理则在运行时动态生成代理类。静态代理适用于代理一个已知类的情况,而动态代理适用于代理一个接口或类,且目标对象可能尚未实现的情况。
24、为什么springboot使用cglib作为默认动态代理?
主要是为了提供更广泛的代理支持,并在一些情况下提供更好的性能。以下是一些原因:
1)、类代理支持 :CGLIB
动态代理可以适用于任何类型的目标类,无论它是否实现了接口,而JDK
动态代理只能适用于实现了接口的目标类。这意味着CGLIB
动态代理可以覆盖JDK
动态代理的所有场景,而JDK
动态代理不能覆盖CGLIB
动态代理的所有场景。
因此,为了保证SpringBoot
中的AOP
(面向切面编程)功能可以应用于任何类型的Bean
(无论它是否实现了接口),SpringBoot
默认使用CGLIB
作为代理的实现方式。
2)、性能优势:CGLIB
动态代理在生成代理对象时需要消耗更多的时间和内存资源,因为它需要操作字节码,而JDK
动态代理在生成代理对象时相对较快,因为它只需要操作反射。但是,在执行代理方法时,CGLIB
动态代理比JDK
动态代理要快得多,因为它直接调用目标方法,而不需要通过反射。
在SpringBoot
中,通常只会在容器启动时生成一次代理对象,并缓存起来,而在运行时会频繁地执行代理方法。因此,在整体性能上,CGLIB
动态代理比JDK
动态代理要优越。
CGLIB
不能代理final
类的方法,可能导致某些场景下无法生成代理。在选择默认代理方式时,Spring Boot
的设计考虑了这些因素,以提供更广泛、更灵活的代理支持。
25、JDK动态代理为什么只能代理有接口的类?
JDK
动态代理之所以只能代理实现了接口的类,主要是受到其实现原理和Java
语言特性的影响。具体原因如下:
1)、Java
的继承机制:在Java
中,一个类只能有一个父类。因此,如果使用基于类的动态代理,代理类必须继承一个类,这就限制了被代理的类必须是单继承的。相比之下,接口可以被多个类实现,这提供了更大的灵活性。JDK
动态代理的底层实现与Java
的继承机制紧密相关,当使用JDK
动态代理时,Proxy
类在运行时动态地生成一个代理类,这个代理类继承自java.lang.reflect.Proxy
,并实现了目标接口。由于代理类已经继承了java.lang.reflect.Proxy
类,根据Java
的继承机制,它无法再继承其他类。
2)、接口的约束性:接口在Java
中具有更强的约束性,通过接口可以很好地定义和限制一组行为。JDK
动态代理要求被代理的对象必须实现至少一个接口,这有助于确保代理行为的一致性,并且符合Java
面向接口编程的设计原则。
25、JDK动态代理为什么只能代理有接口的类?
JDK
动态代理之所以只能代理实现了接口的类,主要是受到其实现原理和Java
语言特性的影响。具体原因如下:
1)、Java
的继承机制:在Java
中,一个类只能有一个父类。因此,如果使用基于类的动态代理,代理类必须继承一个类,这就限制了被代理的类必须是单继承的。相比之下,接口可以被多个类实现,这提供了更大的灵活性。JDK
动态代理的底层实现与Java
的继承机制紧密相关,当使用JDK
动态代理时,Proxy
类在运行时动态地生成一个代理类,这个代理类继承自java.lang.reflect.Proxy
,并实现了目标接口。由于代理类已经继承了java.lang.reflect.Proxy
类,根据Java
的继承机制,它无法再继承其他类。
2)、接口的约束性:接口在Java
中具有更强的约束性,通过接口可以很好地定义和限制一组行为。JDK
动态代理要求被代理的对象必须实现至少一个接口,这有助于确保代理行为的一致性,并且符合Java
面向接口编程的设计原则。
动态代理实际上是程序在运行中,根据被代理的接口来动态生成代理类的class
文件,并加载class
文件运行的过程,通过反编译被生成的 $Proxy0.class
文件发现:
java
public final class $Proxy0 extends Proxy implements Interface {
public $Proxy0(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}
// 该方法为被代理接口的业务方法,代理类都会自动生成相应的方法,里面去执行invocationHandler 的invoke方法。
public final void sayHello(String paramString) {
try {
this.h.invoke(this, m3, new Object[] { paramString });
return;
}
catch (Error|RuntimeException localError) {
throw localError;
}
catch (Throwable localThrowable) {
throw new UndeclaredThrowableException(localThrowable);
}
}
}
java
是单继承,动态生成的代理类已经继承了Proxy类的,就不能再继承其他的类,所以只能靠实现被代理类的接口的形式,故JDK的动态代理必须有接口。
26、什么是IOC?
Spring
框架的核心是Spring
容器,Spring
容器创建对象并将它们装配在一起,配置并管理它们的完整声明周期,几乎所有的框架都依赖Spring
框架。
IOC
(Inversion of control
,控制反转),指将对象的控制权转移给Spring
容器,并由容器根据配置文件去创建实例,控制实例的生命周期(创建、销毁)和管理各个实例之间的依赖关系。
IOC
让对象的创建不用去New
了,可以由Spring
自动生产,使用Java
的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。IOC
让相互协作的组件保持松散的耦合,降低模块间的耦合度,利于功能的复用,提高代码的可维护性和扩展性。
IOC执行流程:
1)、配置文件的加载
2)、容器的初始化
3)、Bean
的实例化
4)、依赖注入
5)、Bean
的声明周期管理
6)、容器的关闭
IOC的实现机制:
工厂模式+反射机制
27、什么是DI?
IOC
的一点就是在程序运行时,动态的向某个对象提供它所需要的其它对象,这一点是通过依赖注入实现的。
DI
(Dependency Injection
,依赖注入),指应用程序在运行时依赖IOC
容器来动态注入对象需要的外部资源。Spring
的依赖注入具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象,执行的对象的方法,改变对象的属性。
在依赖注入中,不必创建对象,但必须描述如何创建它们。不是在代码中将组件和服务连接在一起,而是描述配置文件中哪些组件需要哪些服务,由IOC
容器将它们装配在一起。
依赖注入简单来说就是当一个对象需要另一个对象时,可以把另一个对象注入到对象中去。说白了依赖注入是指把Bean
添加到IOC
容器中的一种形式。
依赖注入把应用的代码量降到最低,使代码更简洁,并且可以达到松散耦合度。
Spring可以注入的对象:
NULL,空串,list,set,map,props。
Spring提供以下几种集合的配置元素:
<list>
类型用于注入一列值,允许有相同的值。
<set>
类型用于注入一组值,不允许有相同的值。
<map>
类型用于注入一组键值对,键和值都可以为任意类型。
<props>
类型用于注入一组键值对,键和值都只能为String类型。
28、依赖注入的方式?
1)、构造器注入:构造器注入通过容器触发一个类的构造器来实现的,该类有一系列参数,每个参数代表一个对其它类的依赖。
2)、Setter
方法注入:Setter
方法注入是容器通过调用无参构造器或无参static
工厂方法实例化bean
之后,调用该bean
的setter
方法,即实现了基于setter
的依赖注入。
3)、接口注入:实现接口中的方法。
你两种依赖方式都可以使用,构造器注入和Setter
方法注入。最好的解决方案是用构造器参数实现强制依赖,setter
方法实现可选依赖。
29、Spring基于XML注入bean的几种方式?
1)、Set
方法注入;
2)、构造器注入(1、通过index
设置参数的位置、2、通过type
设置参数类型);
3)、静态工厂注入;
4)、实例工厂注入;
30、Bean实例化有哪些方式?
1)、使用类构造器实例化(默认无参数)
2)、使用静态工厂方法实例化(简单工厂模式)
3)、使用实例化工厂方法实例化(工厂方法模式)
31、BeanFactory和 ApplicationContexts有什么区别?
BeanFactory
是一个底层的IOC
容器,是一个包含Bean
集合的工厂类,ApplicationContext
接口扩展了BeanFactory
接口,在其基础上提供了一些额外功能,这两个都可以作为Spring
的容器。
BeanFactory
是Spring
里面最底层的接口,包含了各种bean
的定义,读取bean
配置文档,管理bean
的加载、实例化,控制bean
的生命周期,维护bean
之间的依赖关系。
相同点:BeanFactory
和ApplicationContext
是Spring
的两大核心接口,都可以当做Spring
的容器。
不同点:
1)、BeanFactory
使用懒加载,ApplicationContext
使用即时加载。BeanFactory
只有在使用到某个Bean
时(调用getBean()
),才对该Bean
进行加载实例化。这样,我们就不能提前发现一些存在的Spring
的配置问题。如果Bean
的某一个属性没有注入,BeanFactory
加载后,直至第一次使用调用getBean
方法才会抛出异常。ApplicationContext
它是在容器启动时,一次性创建了所有的Bean
。这样,在容器启动时,我们就可以发现Spring
中存在的配置错误,这样有利于检查所依赖属性是否注入。
2)、BeanFactory
通常以编程的方式被创建,ApplicationContext
还能以声明的方式创建。BeanFactory
不支持基于依赖的注解,ApplicationContext
支持基于依赖的注解。(声明即注解)
3)、BeanFactory
和ApplicationContext
都支持BeanPostProcessor
、BeanFactoryPostProcessor
的注册使用,但两者之间的区别是:BeanFactory
需要手动注册,而ApplicationContext
则是自动注册。
4)、BeanFactory
使用语法显式提供资源对象,ApplicationContext
自己创建和管理资源对象。
5)、当应用程序配置Bean
较多时,ApplicationContext
占用内存空间较大,程序启动较慢。BeanFactory
刚好相反。ApplicationContext
启动后预载入所有的单实例Bean
,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory
,ApplicationContext
唯一的不足是占用内存空间,当应用程序配置Bean
较多时,程序启动较慢。
6)、BeanFactory
不支持国际化,ApplicationContext
支持国际化。
7)、功能支持,ApplicationContext
是BeanFactory
的子接口,在BeanFactory
的基础上提供了更加多的功能:
a)、继承MessageSource
支持国际化。
b)、统一的资源文件访问方式。
c)、提供在监听器中注册bean
的事件。
d)、同时加载多个配置文件。
e)、载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次。
BeanFactory = ApplicationContext.getParentBeanFactory()
32、如何给Spring 容器提供配置元数据?在Spring中如何配置Bean?将一个类声明为Spring Bean的注解有哪些?
1)、基于XML
配置:<property/>、<constructor-arg/>、<bean/>
。
2)、基于Java
注解配置:在相关的类、方法或字段声明上使用注解,在Spring2.5
开始引入。
启用注解:<beans><context:annotation-config/></beans>
在Java
类中使用注解来定义和配置Bean
。
一些重要的注解:@Component
、@Service
、@Repository
、@Controller
通过@ComponentScan
扫描
3)、基于Java
配置:通过使用@Bean
和@Configuration
来实现,Spring3.0
开始引入。
1、@Bean
注解扮演与<bean/>
元素相同的角色,产生一个Bean
对象,交由Spring
容器管理。
2、@Configuration
类允许通过简单地调用同一个类中的其它@Bean
方法来定义bean
间依赖关系。
4)、属性文件
5)、环境变量和命令行参数(properties
)
6)、配置文件(json
、yaml
)
33、Spring 使用Bean的注解?
1)、@Required
:适用于bean
属性setter
方法。
2)、@Autowired
:适用于bean
属性setter
方法,non-setter
方法、构造函数和属性。依照类型进行装配。
3)、@Qualifier
:用来消除多个bean
混乱来保证唯一的bean注入。
4)、@Resource
:是J2EE
的标准,Spring
兼容,依照名称进行装配。
5)、@Inject
:JSR330
中的规范,需要导入javax.inject.Inject
包才能注入。可以根据类型自动装配,如需按照名称进行装配,需要配合@Named
使用。
34、@Autowired、@Qualifier、@Resource、@Qualifier?
用来自动装配指定的Bean
。
在启动Spring IOC
时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor
后置处理器,当容器扫描到@Autowied
、@Resource
或@Inject
时,就会在IOC
容器自动查找需要的bean
,并装配给该对象的属性。
1)、@Autowired
注解:修饰Setter
方法、构造器、属性或者具有任意名称和/或多个参数的方法。
默认情况下,它是类型驱动的注入。
在使用@Autowired
时,首先在容器中查询对应类型的bean
:
- 如果查询结果刚好为一个,就将该
bean
装配给@Autowired
指定的数据。 - 如果查询的结果不止一个,那么
@Autowired
会根据名称来查找。 - 如果上述查找的结果为空,那么会抛出异常。解决方法时,使用
required=false
。
2)、@Required
注解:表明Bean
的属性必须在配置的时候设置。
若@Required
注解的Bean
属性未被设置,容器将抛出BeanInitializationException
。
@Required
应用于bean
属性 setter
方法。
Setter
注入的缺点之一是很难检查出所需的所有属性是否已经注入,为了解决这个问题,您可以使用@Required
注解,在Setter
方法上使用@Required
注解。
3)、@Qualifier
注解:当有多个相同类型的bean
却只有一个需要自动装配时,将@Qualifier
注解和@Autowire
注解结合使用以消除这种混淆,指定需要装配的确切的bean
。
java
public class Customer{
@Autowired
@Qualifier("personA")
private Person person;
}
4)、@Resource
注解:javax.annotation.Resource
用于setter
方法和属性
@Resource
默认使用byName
进行注入
@Resource
有name
(名称)和type
(类型)两个属性
如果既不指定Name
,也不指定Type
,这时将使用反射机制使用byName
进行注入。
@Resource
装配顺序:
- 同时执行了
Name
和Type
,查找唯一Bean
,找不到就抛出异常。 - 如果指定了
Name
,按byName
,找不到抛出异常。 - 如果指定了
Type
,按byType
,找不到或找到多个抛出异常。 - 如果既没有指定
Name
,也没有指定Type
,则按照Name
。如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Resource
的作用相当于@Autowired
,只不过@Autowired
按照byType
自动注入。
35、@Component和@Bean的区别是什么?
1、作用对象不同:@Component
注解作用于类,而@Bean
注解作用于方法。
2、@Component
注解通常是通过类路径扫描(@ComponentScan
注解定义要扫描的路径)来自动侦测以及自动装配到Spring
容器中,@Bean
注解通常是在标有该注解的方法中定义产生这个bean
。
3、@Bean
注解比@Component
注解的自定义性更强,而且很多地方只能通过@Bean
注解来注册bean
,当引用第三方库的类需要装配到Spring
容器的时候,就只能通过@Bean
注解来实现。
36、Spring的单例实现原理?
不使用懒汉和饿汉模式,使用另外一种特殊化的单例模式,它被称为单例注册表。
Spring
框架对单例的支持是采用单例注册表的方式进行实现的,而这个注册表的缓存是HashMap
对象,如果配置文件中的配置信息不要求使用单例,Spring
会采用新建实例的方式返回对象实例。
37、Spring的底层实现机制是什么?
使用Demo4j
(解析xml
)+Java
反射机制。
38、Spring的两种代理JDK和CGLIB?
1、如果目标对象实现了接口,默认情况下会采用JDK
的动态代理实现AOP
。
2、如果目标对象实现了接口,可以强制使用CGLIB
实现AOP
。
3、如果目标对象没有实现了接口,必须采用CGLIB
库,Spring
会自动在JDK
动态代理和CGLIB
之间转换。
39、哪些是重要的bean生命周期方法? 你能重载它们吗?
有两个重要的bean
生命周期方法,第一个是setup
,它是在容器加载bean
的时候被调用。第二个方法是 teardown
它是在容器卸载类的时候被调用。
bean
标签有两个重要的属性(init-method
和destroy-method
),用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct
和@PreDestroy
)。
40、Spring延迟依赖注入的方式?
1、使用@Lazy注解
@Lazy
注解是Spring
提供的一种简单而直接的方式来实现延迟依赖注入。当在字段或方法上使用@Lazy
注解时,Spring
容器不会在启动时立即创建该bean
的实例,而是在首次访问该bean
时再进行创建和注入。这种方式适用于那些创建和初始化过程比较耗时,或者不是立即需要的bean
。
2、在配置文件中设置lazy-init属性
对于基于XML
的Spring
配置,可以在<bean>
标签中设置lazy-init
属性为true
来实现延迟初始化。这样,Spring
容器在启动时不会立即创建该bean
的实例,而是在首次请求时创建。
3、使用ObjectFactory或ObjectProvider
ObjectFactory
和ObjectProvider
是Spring
提供的接口,它们提供了一种延迟获取bean
实例的方式。与@Lazy
注解不同,ObjectFactory
和ObjectProvider
允许开发者在需要时显式地调用getObject()
方法来获取bean
实例,从而实现了更细粒度的控制。
ObjectFactory
:这是一个简单的接口,只包含一个getObject()
方法,用于获取bean
实例。
ObjectProvider
:ObjectProvider
是ObjectFactory
的扩展,提供了更多的功能,如getIfAvailable()
(如果bean
存在则获取,否则返回null
)和getIfUnique()
(如果容器中只有一个该类型的bean
则获取,否则抛出异常)等方法。
java
@Autowired
private ObjectProvider<SomeDependency> someDependencyProvider;
// 在需要时获取bean实例
SomeDependency dependency = someDependencyProvider.getIfAvailable();
4、使用Spring AOP实现延迟代理
虽然这种方式不是直接用于延迟依赖注入,但Spring
的AOP
(面向切面编程)功能可以用来实现类似的效果。通过定义一个切面,可以在访问bean
的方法前后插入自定义的逻辑,包括延迟创建bean
实例的逻辑。然而,这种方式通常比较复杂,且不是实现延迟依赖注入的首选方法。
41、什么是AOP?
AOP
:面向切面编程,是一种编程范式,通过预编译的方式和运行期间动态代理实现程序功能的统一维护的一种技术。
AOP
通常用来隔离不同业务逻辑,如事务管理,日志管理。
Spring AOP
的方式:cglib
动态代理和jdk
动态代理。
说白了AOP
就是通过某种匹配规则去匹配方法,然后再添加对应的逻辑处理。AOP
用于将那些于业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可用的模块,这个模块被命名为切面。减少从夫代码,降低耦合度。
AOP
个核心概念:
1)、切面(aspect
):类似于java
中的类声明,一般是封装的可重用的模块,常用于应用中配置事务和日志管理。@Aspect
和<aop:aspect>
来定义一个切面。
2)、切点(pointcut
):通过一种规则匹配正则表达式,当有连接点可以匹配到切点时,就会触发切点相关联的指定通知。切点分为execution
方式和annotation
方式。
3)、通知(advice
):在切面中某个连接点采取的动作,通知方式总共有5种。
4)、连接点(joinpoint
):根据切点拦截到的具体方法,是应用程序执行Spring AOP
的位置。
5)、织入(weaving
):连接切面和目标对象创建一个通知对象的过程。
6)、目标对象(target
):包含连接点的对象,也被称作通知对象。
例子:
java
// 创建操作日志注解类OperLog
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperLog {}
java
// 创建切面类记录操作日志
@Aspect
@Component
public class OperLogAspect {}
java
// 设置操作日志切入点 记录操作日志 在注解的位置切入代码
@Pointcut("@annotation(com.aop.log.annotation.OperLog)")
public void operLogPoinCut() {}
java
// 设置操作异常切入点记录异常日志 扫描所有controller包下操作
@Pointcut("execution(* com.aop.log.controller..*.*(..))")
public void operExceptionLogPoinCut() {}
java
// 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
@AfterReturning(value = "operLogPoinCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) {}
java
// 使用
@OperLog(operModul = "销售管理-订单新增", operType = OprLogConst.ADD, operDesc = "订单新增功能")
public RespBean addOrderInfo(OrderInfo orderInfo) {
42、Spring通知有哪些类型?
1)、前置通知(Before Advice
):在目标方法执行之前执行执行的通知。这些类型的Advice
在joinpoint
方法之前执行,并使用@Before
注解标记进行配置。
2)、返回后通知(AfterReturning Advice
):在目标方法执行之后执行的通知。这些类型的Advice
在连接点方法正常执行后执行,并使用@AfterReturning
注解标记进行配置。
3)、异常通知(AfterThrowing Advice
):在目标方法抛出异常时执行的通知。 这些类型的Advice
仅在joinpoint
方法通过抛出异常退出并使用@AfterThrowing
注解标记配置时执行。
4)、后置通知(After Advice
):在目标方法执行之后执行的通知,和返回后通知不同之处在于,后置通知是在方法正常返回后执行的通知,如果方法没有正常返回后置通知不会执行,而最终通知是一定会执行的通知。这些类型的Advice
在连接点方法之后执行,无论方法退出是正常还是异常返回,并使用@After
注解标记进行配置。
5)、环绕通知(Around Advice
):在目标方法执行之前和之后都可以执行额外代码的通知。这些类型的Advice
在连接点之前和之后执行,并使用@Around
注解标记进行配置。
同一个aspect
,不同通知(advice
)的执行顺序:
a)、没有异常情况下的执行顺序:
- around before advice;
- before advice;
- target method执行;
- around after advice;
- after advice;
- afterReturning;
b)、有异常情况下的执行顺序:
- around before advice;
- before advice;
- target method执行;
- around after advice;
- after advice;
- afterThrowing;
不同aspect
,不同advice
的执行顺序:
Spring AOP
通过指定 aspect
的优先级,来控制不同aspect
,advice
的执行顺序 ,有两种方式:
Aspect
类添加注解:org.springframework.core.annotation.Order
,使用注解value
属性指定优先级。
Aspect
类实现接口:org.springframework.core.Ordered
,实现Ordered
接口的getOrder()
方法。
其中,数值越低,表明优先级越高,@Order
默认为最低优先级,即最大数值。
最终,不同aspect
,advice
的执行顺序:
- 入操作(
Around
(接入点执行前)、Before
),优先级越高,越先执行。 - 一个切面的入操作执行完,才轮到下一切面,所有切面入操作执行完,才开始执行接入点。
- 出操作(
Around
(接入点执行后)、After
、AfterReturning
、AfterThrowing
),优先级越低,越先执行。 - 一个切面的出操作执行完,才轮到下一切面,直到返回到调用点。
同一个aspect
,相同advice
的执行顺序:
顺序不能确定,而且 @Order
在advice
上也无效。
可以将advice
合并为一个advice
。
将两个advice
分别抽离到各自的aspect
。
Transactional Aspect
的优先级:
Spring
事务管理(Transaction Management
),也是基于Spring AOP
。
在Spring AOP
的使用中,有时我们必须明确自定义aspect
的优先级低于或高于事务切面(Transaction Aspect
),所以我们需要知道:
-
事务切面优先级:默认为最低优先级
-
事务的增强类型:
Around advice
,其实不难理解,进入方法开启事务,退出方法提交或回滚,所以需要环绕增强。 -
如何修改事务切面的优先级: 在开启事务时,通过设置
@EnableTransactionManagement
和<tx:annotation-driven/>
中的,order
属性来修改事务切面的优先级。
43、AOP有哪些方式?
AOP
实现的关键在于代理模式,AOP
代理主要分为静态代理和动态代理,静态代理的代表为AspectJ
,动态代理则以Spring AOP
为代表。
1)、AspectJ
是静态代理的增强,所谓静态代理,就是AOP
框架会在编译阶段生成AOP
代理类,因此也称为编译时增强,它会在编译阶段将AspectJ
(切面)织入到Java
字节码中,运行的时候就是增强之后的AOP
对象。
2)、Spring AOP
使用的动态代理,所谓的动态代理就是说AOP
框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP
对象,这个AOP
对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
3)、静态代理与动态代理区别在于生成AOP
代理对象的时机不同,相对来说AspectJ
的静态代理方式具有更好的性能,但是AspectJ
需要特定的编译器进行处理,而Spring AOP
则无需特定的编译器处理。
在Spring
中默认情况下使用JDK
动态代理实现AOP
,如果proxy-target-class
设置为true
或者使用了优化策略那么会使用CGLIB
来创建动态代理。
44、AOP有哪些实现方式?
实现AOP
的技术,主要分为两大类:
1、静态代理:指使用AOP
框架提供的命令进行编译,从而在编译阶段就可生成AOP
代理类,因此也称为编译时增强。
- 编译时编织(特殊编译器实现)
- 类加载时编织(特殊的类加载器实现)
2、动态代理:在运行时在内存中"临时"生成AOP
动态代理类,因此也被称为运行时增强。
JDK
动态代理CGLIB
45、Spring AOP and AspectJ AOP有什么区别?
Spring AOP
基于动态代理方式实现;AspectJ
基于静态代理方式实现。
Spring AOP
仅支持方法级别的PointCut
;提供了完全的AOP
支持,它还支持属性级别的PointCut
。
Spring AOP
是属于运行时增强,而AspectJ
是编译时增强。Spring AOP
基于代理(Proxying
),而AspectJ
基于字节码操作(Bytecode Manipulation
)。
Spring AOP
已经集成了AspectJ
,AspectJ
应该算得上是Java
生态系统中最完整的AOP
框架了,AspectJ
相比于Spring AOP
功能更加强大,但是Spring AOP
相对来说更简单。
如果切面比较少,两者性能差异不大。如果切面太多,最好选择AspectJ
,它比SpringAOP
快很多。