Spring知识点

发布于:2024-07-30 ⋅ 阅读:(74) ⋅ 点赞:(0)

Spring知识点

1、Spring中Bean是线程安全的吗?

Spring框架中,Bean的线程安全性并非由Spring容器本身直接保证,Bean的线程安全性取决于Bean的状态以及Bean的作用域。

  • 无状态Bean:如果Bean是无状态的,即Bean内部不保存任何状态信息,那么这样的Bean是线程安全的。因为无论多少个线程访问,它们看到的都是相同的状态(即无状态)。Spring框架中的很多Bean,如ControllerServiceDAO层,通常设计为无状态的,因此它们是线程安全的。

  • 有状态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的生命周期?

SpringBean的生命周期是从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还支持通过编程式事务管理,即通过TransactionTemplatePlatformTransactionManagerAPI来手动控制事务的开启、提交和回滚。但这种方式相对繁琐,且容易出错,因此在实际应用中较少使用。

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 通常的实现是什么?

ApplicationContextSpring框架中的一个核心接口,它代表了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配置和组件扫描。

BeanFactorySpring框架中的一个核心接口,它提供了一种先进的配置机制,使得应用程序可以在运行时管理对象及其依赖关系。与 ApplicationContext相比,BeanFactory提供了一种更为基础、低级别的配置方式。

BeanFactory 通常的实现:

最常用的BeanFactory实现是XmlBeanFactory 类,它根据xml文件中的定义加载beans,该容器从xml 文件读取配置元数据并用它去创建一个完全配置的系统或应用。XmlBeanFactory类在Spring的后续版本中已被弃用,取而代之的是 DefaultListableBeanFactory,它是BeanFactory接口最常用和最重要的实现。

DefaultListableBeanFactory不仅实现了BeanFactory接口,还提供了更多的功能,如支持单例bean、原型bean、延迟加载bean等。此外,它还支持bean的别名注册、FactoryBean的处理等高级特性。

除了DefaultListableBeanFactoryBeanFactory还有其他的实现,如SimpleAliasRegistry,它提供了一个简单的别名注册机制。但是,在实际开发中,大多数情况下,开发者都是直接使用DefaultListableBeanFactory或其子类,如 XmlWebApplicationContextFileSystemXmlApplicationContext 等,这些类内部都使用了 DefaultListableBeanFactory作为 bean 工厂的实现。

总的来说,DefaultListableBeanFactoryBeanFactory接口最常用的实现,它提供了丰富的功能和灵活的配置方式,使得开发者可以轻松地管理和配置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对象。

@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:根据是否存在某个类作为判断依据来决定是否要执行某些配置。

@Configuration
@ConditionalOnClass(DataSource.class)
class MySQLAutoConfiguration {}

36)、@ConditionalOnBean@ConditionalOnMissingBean:根据是否存在某个对象作为依据来决定是否要执行某些配置方法。

@Bean
@ConditionalOnBean(name="dataSource")
LocalContainerEntityManagerFactoryBean entityManagerFactory(){}
@Bean
@ConditionalOnMissingBean
public MyBean myBean(){}

37)、@ConditionalOnProperty:根据Spring配置文件中的配置项是否满足配置要求,从而决定是否要执行被其标注的方法。

@Bean
@ConditionalOnProperty(name="alipay",havingValue="on")
Alipay alipay(){return new Alipay();}

38)、@ConditionalOnResource:用于检测当某个配置文件存在时,则触发被其标注的方法。

@ConditionalOnResource(resources = "classpath:website.properties")
Properties addWebsiteProperties(){}

39)、@ConditionalOnWebApplication@ConditionalOnNotWebApplication:用于判断当前的应用程序是否是Web应用程序。如果当前应用是Web应用程序,则使用Spring WebApplicationContext,并定义其会话的生命周期。

@ConditionalOnWebApplication
HealthCheckController healthCheckController(){}

40)、@ConditionalExpression:让我们控制更细粒度的基于表达式的配置条件限制。当表达式满足某个条件或者表达式为真的时候,将会执行被此注解标注的方法。

@Bean
@ConditionalException("${localstore} && ${local == 'true'}")
LocalFileStore store(){}

41)、@Conditional:可以控制更为复杂的配置条件,在Spring内置的条件控制注解不满足应用需求的时候,可以使用此注解定义自定义的控制条件,以达到自定义的要求。

@Conditioanl(CustomConditioanl.class)
CustomProperties addCustomProperties(){}

42)、Spring AOP注解:@Aspect@Before@After@Around@Pointcut

16、Spring框架中有哪些不同类型的事件?

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

1)、上下文更新事件:在调用ConfigurableApplicationContext接口中的refresh()方法时被触发。

2)、上下文开始事件:当容器调用ConfigurableApplicationContextstart()方法开始/重新开始容器时触发该事件。

3)、上下文停止事件:当容器调用ConfigurableApplicationContextstop()方法停止容器时触发该事件。

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使用工厂模式通过BeanFactoryApplicationContext创建bean对象,根据传入一个唯一的标识来获得Bean对象。

2)、代理设计模式:SpringAOP功能用到了JDK的动态代理和CGLIB字节码生成技术。

3)、单例设计模式:Spring中的bean默认都是单例的。Spring依赖注入Bean实例默认是单例的。Spring的依赖注入(包括lazy-init方式)都是发生在AbstractBeanFactorygetBean里。getBeandoGetBean方法调用getSingleton进行bean的创建。

4)、模板方法模式:Spring中的jdbcTemplatehibernateTemplate等以Template结尾的对数据库操作的类,它们就使用到了模板模式。可以将相同部分的代码放在父类中,而将不同的代码放入不同的子类中,用来解决代码重复的问题。

5)、包装器设计模式:我们的项目需要连接多个数据库,而且不同的客户在每次访问中根据需要会去访问不同的数据库。这种模式让我们可以根据客户的需求能够动态切换不同的数据源。

6)、观察者模式:Spring事件驱动模型就是观察者模式很经典的一个应用。Spring的事件驱动模型使用的是观察者模式,SpringObserver模式常用的地方是listener的实现。如:ApplicationContextEventApplicationListener

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,而是privatedefaultprotected的话,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,这种类型的传播特性不支持事务,如果有事务则会抛异常。

目前只有这三种传播特性才会创建新事务:REQUIREDREQUIRES_NEWNESTED

【2、自己吞了异常】

事务不会回滚,最常见的问题是:开发者在代码中手动try...catch了异常。这种情况下spring事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。如果想要spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。

【3、手动抛了别的异常】

即使开发者没有手动捕获异常,但如果抛的异常不正确,spring事务也不会回滚。

因为spring事务,默认情况下只会回滚RuntimeException(运行时异常)和Error(错误),对于普通的Exception(非运行时异常),它不会回滚。

【4、自定义了回滚异常】

在使用@Transactional注解声明事务时,有时我们想自定义回滚的异常,spring也是支持的。可以通过设置rollbackFor参数,来完成这个功能。但如果这个参数的值设置错了,就会引出一些莫名其妙的问题,例如:@Transactional(rollbackFor = BusinessException.class)。如果在执行代码,程序报错了,抛了SqlExceptionDuplicateKeyException等异常。而BusinessException是我们自定义的异常,报错的异常不属于BusinessException,所以事务也不会回滚。即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。

这是为什么呢?因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成:ExceptionThrowable

【5、嵌套事务回滚多了】
@Transactional 
public void add(UserModel userModel) throws Exception { 
	userMapper.insertUser(userModel); 
	roleService.doOtherThing(); 
} 

这种情况使用了嵌套的内部事务,原本是希望调用roleService.doOtherThing方法时,如果出现了异常,只回滚doOtherThing方法里的内容,不回滚 userMapper.insertUser 里的内容,即回滚保存点。但事实是,insertUser也回滚了为什么?因为doOtherThing方法出现了异常,没有手动捕获,会继续往上抛,到外层add方法的代理方法中捕获了异常。所以,这种情况是直接回滚了整个事务,不只回滚单个保存点。
怎么样才能只回滚保存点呢?

@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注解是通过springaop起作用的,但是如果使用不当,事务功能可能会失效。

2、@Transactional注解一般加在某个业务方法上,会导致整个业务方法都在同一个事务中,粒度太粗,不好控制事务范围,是出现大事务问题的最常见的原因。

那我们该怎么办呢?

可以使用编程式事务,在spring项目中使用TransactionTemplate类的对象,手动执行事务。部分代码如下:

transactionTemplate.execute((status) => {doSameThing...return Boolean.TRUE;})

使用TransactionTemplate的编程式事务功能自己灵活控制事务的范围,是避免大事务问题的首选办法。

建议在项目中少使用@Transactional注解开启事务,并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用@Transactional注解开启事务开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。

上面聊的这些内容都是基于@Transactional注解的,主要说的是它的事务问题,我们把这种事务叫做:声明式事务。其实,spring还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种事务叫做:编程式事务。在spring中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的execute方法中,就实现了事务的功能。

相较于@Transactional注解声明式事务,我更建议大家使用,基于TransactionTemplate的编程式事务。主要原因如下:

  • 避免由于spring aop问题,导致事务失效的问题。
  • 能够更小力度地控制事务的范围,更直观。
【2、将查询(select)方法放到事务外】

如果出现大事务,可以将查询(select)方法放到事务外,也是比较常用的做法,因为一般情况下这类方法是不需要事务的。这样就能有效的减少事务的粒度。

【3、事务中避免远程调用】

我们在接口中调用其他系统的接口是不能避免的,由于网络不稳定,这种远程调的响应时间可能比较长,如果远程调用的代码放在某个事物中,这个事物就可能是大事务。当然,远程调用不仅仅是指调用接口,还有包括:发MQ消息,或者连接redismongodb保存数据等。

远程调用的代码可能耗时较长,切记一定要放在事务之外。使用编程式事务更好控制一些。

有些朋友可能会问,远程调用的代码不放在事务中如何保证数据一致性呢?这就需要建立:重试+补偿机制,达到数据最终一致性了。

【4、事务中避免一次性处理太多数据】

如果一个事务中需要处理的数据太多,也会造成大事务问题。比如为了操作方便,你可能会一次批量更新1000条数据,这样会导致大量数据锁等待,特别在高并发的系统中问题尤为明显。

解决办法是分页处理,1000条数据,分50页,一次只处理20条数据,这样可以大大减少大事务的出现。

【5、非事务执行】

在使用事务之前,我们都应该思考一下,是不是所有的数据库操作都需要在事务中执行?

其实有些方法是可以不在事务中执行的,比如操作日志和统计数量这种业务允许少量数据不一致的情况。

当然大事务中要鉴别出哪些方法可以非事务执行,其实没那么容易,需要对整个业务梳理一遍,才能找出最合理的答案。

【6、异步处理】

还有一点也非常重要,是不是事务中的所有方法都需要同步执行?我们都知道,方法同步执行需要等待方法返回,如果一个事务中同步执行的方法太多了,势必会造成等待时间过长,出现大事务问题。例如:order方法用于下单,delivery方法用于发货,是不是下单后就一定要马上发货呢?答案是否定的。这里发货功能其实可以走MQ异步处理逻辑。

22、CGLIB动态代理和JDK动态代理的区别?

1)、实现机制JDK动态代理主要基于Java的反射机制,它要求目标对象必须实现至少一个接口,然后代理对象会实现这些接口,并将方法调用委托给InvocationHandler处理。CGLIB动态代理则基于字节码操作,通过继承目标类(而不是实现接口)来创建代理对象。它使用第三方库ASMCGLIB来动态生成目标类的子类,并重写其中的方法。

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 文件发现:

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之后,调用该beansetter方法,即实现了基于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的容器。

BeanFactorySpring里面最底层的接口,包含了各种bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。

相同点:BeanFactoryApplicationContextSpring的两大核心接口,都可以当做Spring的容器。

不同点:

1)、BeanFactory使用懒加载,ApplicationContext使用即时加载。BeanFactory只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFactory加载后,直至第一次使用调用getBean方法才会抛出异常。ApplicationContext它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。

2)、BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建。BeanFactory不支持基于依赖的注解,ApplicationContext支持基于依赖的注解。(声明即注解)

3)、BeanFactoryApplicationContext都支持BeanPostProcessorBeanFactoryPostProcessor的注册使用,但两者之间的区别是:BeanFactory需要手动注册,而ApplicationContext则是自动注册。

4)、BeanFactory使用语法显式提供资源对象,ApplicationContext自己创建和管理资源对象。

5)、当应用程序配置Bean较多时,ApplicationContext占用内存空间较大,程序启动较慢。BeanFactory刚好相反。ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactoryApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢。

6)、BeanFactory不支持国际化,ApplicationContext支持国际化。

7)、功能支持,ApplicationContextBeanFactory的子接口,在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)、配置文件(jsonyaml)

33、Spring 使用Bean的注解?

1)、@Required:适用于bean属性setter方法。

2)、@Autowired:适用于bean属性setter方法,non-setter方法、构造函数和属性。依照类型进行装配。

3)、@Qualifier:用来消除多个bean混乱来保证唯一的bean注入。

4)、@Resource:是J2EE的标准,Spring兼容,依照名称进行装配。

5)、@InjectJSR330中的规范,需要导入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

public class Customer{
	@Autowired 
	@Qualifier("personA") 
	private Person person;
}

4)、@Resource注解:javax.annotation.Resource

用于setter方法和属性

@Resource默认使用byName进行注入

@Resourcename(名称)和type(类型)两个属性

如果既不指定Name,也不指定Type,这时将使用反射机制使用byName进行注入。

@Resource装配顺序:

  • 同时执行了NameType,查找唯一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-methoddestroy-method),用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct@PreDestroy)。

40、Spring延迟依赖注入的方式?

1、使用@Lazy注解

@Lazy注解是Spring提供的一种简单而直接的方式来实现延迟依赖注入。当在字段或方法上使用@Lazy注解时,Spring容器不会在启动时立即创建该bean的实例,而是在首次访问该bean时再进行创建和注入。这种方式适用于那些创建和初始化过程比较耗时,或者不是立即需要的bean

2、在配置文件中设置lazy-init属性

对于基于XMLSpring配置,可以在<bean>标签中设置lazy-init属性为true来实现延迟初始化。这样,Spring容器在启动时不会立即创建该bean的实例,而是在首次请求时创建。

3、使用ObjectFactory或ObjectProvider

ObjectFactoryObjectProviderSpring提供的接口,它们提供了一种延迟获取bean实例的方式。与@Lazy注解不同,ObjectFactoryObjectProvider允许开发者在需要时显式地调用getObject()方法来获取bean实例,从而实现了更细粒度的控制。

ObjectFactory:这是一个简单的接口,只包含一个getObject()方法,用于获取bean实例。

ObjectProviderObjectProviderObjectFactory的扩展,提供了更多的功能,如getIfAvailable()(如果bean存在则获取,否则返回null)和getIfUnique()(如果容器中只有一个该类型的bean则获取,否则抛出异常)等方法。

@Autowired
private ObjectProvider<SomeDependency> someDependencyProvider; 
// 在需要时获取bean实例 
SomeDependency dependency = someDependencyProvider.getIfAvailable();

4、使用Spring AOP实现延迟代理

虽然这种方式不是直接用于延迟依赖注入,但SpringAOP(面向切面编程)功能可以用来实现类似的效果。通过定义一个切面,可以在访问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):包含连接点的对象,也被称作通知对象。

例子:

// 创建操作日志注解类OperLog
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperLog {}
// 创建切面类记录操作日志
@Aspect
@Component
public class OperLogAspect {}
// 设置操作日志切入点 记录操作日志 在注解的位置切入代码
@Pointcut("@annotation(com.aop.log.annotation.OperLog)")
public void operLogPoinCut() {}
// 设置操作异常切入点记录异常日志 扫描所有controller包下操作
@Pointcut("execution(* com.aop.log.controller..*.*(..))")
public void operExceptionLogPoinCut() {}
// 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
@AfterReturning(value = "operLogPoinCut()", returning = "keys")
public void saveOperLog(JoinPoint joinPoint, Object keys) {}
// 使用
@OperLog(operModul = "销售管理-订单新增", operType = OprLogConst.ADD, operDesc = "订单新增功能")
public RespBean addOrderInfo(OrderInfo orderInfo) {

42、Spring通知有哪些类型?

1)、前置通知(Before Advice):在目标方法执行之前执行执行的通知。这些类型的Advicejoinpoint方法之前执行,并使用@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 的优先级,来控制不同aspectadvice的执行顺序 ,有两种方式:

Aspect类添加注解:org.springframework.core.annotation.Order,使用注解value属性指定优先级。

Aspect类实现接口:org.springframework.core.Ordered,实现Ordered接口的getOrder()方法。

其中,数值越低,表明优先级越高,@Order默认为最低优先级,即最大数值。

最终,不同aspectadvice的执行顺序:

  • 入操作(Around(接入点执行前)、Before),优先级越高,越先执行。
  • 一个切面的入操作执行完,才轮到下一切面,所有切面入操作执行完,才开始执行接入点。
  • 出操作(Around(接入点执行后)、AfterAfterReturningAfterThrowing),优先级越低,越先执行。
  • 一个切面的出操作执行完,才轮到下一切面,直到返回到调用点。

同一个aspect,相同advice的执行顺序:

顺序不能确定,而且 @Orderadvice 上也无效。

可以将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已经集成了AspectJAspectJ应该算得上是Java生态系统中最完整的AOP框架了,AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。

如果切面比较少,两者性能差异不大。如果切面太多,最好选择AspectJ,它比SpringAOP快很多。