Spring常见面试题总结

news2024/11/25 22:25:05

什么是Spring

Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题,以提高开发效率。它是一个分层的JavaSE/JavaEE full-stack(一站式)轻量级开源框架,为开发Java应用程序提供全面的基础架构支持。Spring负责基础架构,因此Java开发者可以专注于应用程序的开发。
Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发,提高下发效率。

为什么要使用Spring

根据Spring官网所述,Spring可以实现更快更轻松更安全地编程。 Spring对速度简单性生产力的关注使其成为世界上最受欢迎的Java 开发框架。具体来说,主要体现在以下六个方面:
(1) Spring无处不在。Spring受到全世界开发人员的信任。各个领域,各个企业都在广泛使用。
(2) Spring灵活性强。Spring提供的扩展能力和启动的粘合第三方库的能力,可以使开发人员轻松构建几乎任何可以想象的应用程序。Spring框架的控制反转(IoC)能力,为广泛的功能集奠定了基础。
(3) Spring可以提高生产力。Spring Boot改变了Java编程的方式,从根本上简化了编程体验。Spring Cloud使微服务开发变得轻松。
(4) Spring快速。Spring可以快速启动,快速关闭并支持执行的优化。Spring项目也越来越多地支持反应式(非阻塞)编程模型,以提高效率。
(5) Spring注重安全。Spring代码提交者与安全专家合作,修补和测试所有报告的漏洞。Spring密切关注关联的第三方类库,并定期发布更新以帮助确保数据和应用程序尽可能安全。此外,Spring Security可以更轻松地与行业标准安全方案集成。
(6) Spring社区支持较好。Spring社区是一个庞大,全球化,多元的社区,涵盖了从初学者到经验丰富的职业人士的各个阶段需要内容,拥有带入新高度所需的支持和资源:快速入门,指南和教程,视频,聚会,支持等等。

Spring 框架简介

请添加图片描述
(1) Spring Core:提供 Spring 框架的基本功能。核心容器的主要组件是BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
(2) Spring AOP:提供 Spring 框架面向切面的编程能力:将面向切面的编程功能集成到 Spring 框架中,可以将 Spring 框架管理的任何对象实现 AOP;提供了事务管理服务:通过使用 Spring AOP,不用依赖 EJB 组件,就可以将声明性事务管理集成到应用程序中。
(3) Spring Context:向 Spring 框架提供上下文信息。Spring 上下文支持消息源和观察者模式。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
(4) Spring ORM:Spring 框架整合了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
(5) Spring DAO:提供数据访问能力(访问数据库);提供了有意义的异常层次结构:可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。
(6) Spring Web:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成;Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
(7) Spring MVC:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

Spring Bean

Bean创建的容器类

Spring 有两个容器类: BeanFactory 和 ApplicationContext。
BeanFactory:这是一个最简单的容器,它主要的功能是为依赖注入(DI)提供支持。
ApplicationContext:Application Context 是 Spring 中的高级容器。和 BeanFactory 类似,它可以加载和管理配置文件中定义的 Bean。 另外,它还增加了企业所需要的功能,比如,从属性文件中解析文本信息和将事件传递给所指定的监听器。

BeanFactory 和 ApplicationContext 的区别?

二者都是 Spring 框架的两大核心接口,都可以当做 Spring 的容器。其中 ApplicationContext 是 BeanFactory 的子接口。
BeanFactory 是 Spring 里面最底层的接口,包含了各种 Bean 的定义,读取配置文档,管理 Bean 的加载、实例化,控制 Bean 的生命周期,维护对象之间的依赖关系等功能。
ApplicationContext 接口作为 BeanFactory 的派生,除了提供 BeanFactory 所具有的功能外,还提供了更完整的框架功能:
(1) 继承 MessageSource,支持国际化。
(2) 统一的资源文件访问方式。
(3) 提供在监听器中注册 Bean 的事件。
(4) 支持同时加载多个配置文件。
(5) 载入多个(有继承关系)上下文,使得每一个上下文都专注于一个特定的层次,如应用的 Web 层。
具体区别体现在以下三个方面:
(a) 加载方式不同
BeanFactroy 采用的懒加载方式注入 Bean,即只有在使用到某个 Bean 时才对该 Bean 实例化。这样,我们就不能在程序启动时发现一些存在的 Spring 的配置问题。
ApplicationContext 是在启动时一次性创建了所有的 Bean。
(b) 创建方式不同
BeanFactory 通常以编程的方式被创建,ApplicationContext 还能以声明的方式创建,如使用 ContextLoader。
(c) 注册方式不同
二者都支持 BeanPostProcessor、BeanFactoryPostProcessor 的使用,但 BeanFactory 需要手动注册,而 ApplicationContext 则是自动注册。

Bean的创建过程

(1) Spring Bean的容器类通过解析注解类或者以其他方式定义的类,将Bean标签解析成BeanDefinition(Bean定义信息)
(2) BeanFactory通过获取到的BeanDefinition,利用反射创建Bean对象。
(3) 通过populateBean()方法对Bean对象进行属性填充。
(4) 通过invokeAwareMethods()方法对Bean对象进行赋值。
(5) 调用BeanPostProcessor的初始化前置方法。
(6) 调用init-method方法,进行初始化操作。
(7) 调用BeanPostProcessor的初始化后置方法(AOP在此处进行)。
(8) 将创建好的Bean对象放入BeanDefinitionMap容器中。
(9) 通过Context.getBean()方法获得Bean对象并使用。–正常是通过依赖注入的方式使用
(10) spring容器关闭时会调用DisposableBean的destory()方法销毁Bean对象(如果配置了destory-method属性,spring会自动调用指定的销毁方法)。
单例bean的初始化以及依赖注入一般都在容器初始化阶段进行,只有懒加载(lazy-init为true)的单例bean是在应用第一次调用getBean()时进行初始化和依赖注入。
多例bean 在容器启动时不实例化,即使设置 lazy-init 为 false 也没用,只有调用了getBean()才进行实例化。

Spring Bean 作用域

Spring 提供以下五种 Bean 的作用域:
(1) Singleton: Bean 在每个 Spring Ioc 容器中只有一个实例,也是 Spring 的默认配置。
(2) Prototype:一个 Bean 的定义可以有多个实例。
(3) Request:每次 Http 请求都会创建一个 Bean,故该作用域仅在基于 Web 的 Spring ApplicationContext情形下有效。
(4) Session:在一个 Http Session 中,一个 Bean 对应一个实例。该作用域同样仅在基于 Web 的 Spring ApplicationContext 情形下有效。
(5) Global-session:在一个全局的 Http Session 中,一个 Bean 定义对应一个实例。
值的注意的是:使用 Prototype 作用域时需要慎重的思考,因为频繁创建和销毁 Bean 会带来很大的性能开销。

Spring 自动装配

Spring的自动装配有三种模式:byType(根据类型),byName(根据名称)、constructor(根据构造函数)。

@Autowired是按类型匹配的(byType),如果需要按名称(byName)匹配的话,可以使用@Qualifier注解与@Autowired结合。
@Resource,默认按 byName模式自动注入。@Resource有两个中重要的属性:name和type。Spring容器对于@Resource注解的name属性解析为bean的名字,type属性则解析为bean的类型。因此使用name属性,则按byName模式的自动注入策略,如果使用type属性则按 byType模式自动注入策略。倘若既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入。
上述两种自动装配的依赖注入并不适合简单值类型,如int、boolean、long、String以及Enum等,对于这些类型,Spring容器也提供了@Value注入的方式。@Value接收一个String的值,该值指定了将要被注入到内置的java类型属性值,Spring 容器会做好类型转换。一般情况下@Value会与properties文件结合使用。

Spring 的单例是否线程安全?

Spring 中的单例 Bean 并不是线程安全的。
但我们日常使用时往往并未做多线程并发处理,那又是如何保证线程安全的呢?
实际上大部分时候我们定义的 Bean 是无状态的(如 dao 类),所有某种程度上来说 Bean 也是安全的,但如果 Bean 有状态的话(比如 model 对象),那就要开发者自己去保证线程安全了。
其中有状态就是有数据存储功能,无状态就是不会。

对于有状态的Bean,如何保证线程安全

使用ThreadLocal来保证。也可使用多例的模式,但是不推荐(在高并发场景下)。

Spring Bean 生命周期

Bean 在 Spring 容器中从创建到销毁经历了若干阶段,每一阶段都可以进行个性化定制。
请添加图片描述
(1)实例化:Spring 对 Bean 进行实例化;
(2)属性注入:Spring 将配置和 Bean 的引用注入到对应的属性中;
(3)如果 Bean 实现了 BeanNameAware 接口,Spring 将 Bean 的 ID 传递给 setBeanName() 方法;
(4)如果 Bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanFactory() 方法将 BeanFactory 容器实例传入;
(5)如果 Bean 实现了 ApplicationContextAware 接口,Spring 将调用 setApplicationContext() 方法将 Bean 所在的应用上下文的引用传入进来;
(6)BeanPostProcessor前置处理:如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的 postProcessBeforeInitialization() 方法;
(7)初始化:如果 Bean 实现了 InitializingBean 接口,Spring 将调用它们的 afterPropertiesSet() 方法。类似地,如果 Bean 使用 initmethod 声明了初始化方法,该方法也会被调用;
(8)BeanPostProcessor后置处理:如果 Bean 实现了 BeanPostProcessor 接口,Spring 将调用它们的postProcessAfterInitialization()方法;
(9)使用:此时,Bean 已经准备就绪,可以被应用程序使用了,它们将一直驻留在应用上下文中,直到该应用上下文被销毁;
(10)销毁:如果 Bean 实现了 DisposableBean 接口,Spring 将调用它的 destroy() 接口方法。同样,如果使用 destroymethod 声明了销毁方法,该方法也会被调用。

Spring 常用的Bean注解

(1) @Configuration、@Component、@Service、@Repository、@Controller:用于标识Bean的角色。
(2) @Autowired、@Qualifier、@Resource、@Value:用于自动装配Bean。
(3) @PostConstruct、@PreDestroy:用于指定初始化和销毁方法。
(4) @Transactional:用于声明式事务管理。
(5) @Scope:用于指定Bean作用域。
(6) @Async:用于实现异步编程。

@Bean和@Component有什么区别?

都是使用注解定义 Bean。@Bean 是使用 Java 代码装配 Bean,@Component 是自动装配 Bean。
@Component 注解用在类上,表明一个类会作为组件类,并告知Spring要为这个类创建bean,每个类对应一个 Bean。
@Bean 注解用在方法上,表示这个方法会返回一个 Bean。@Bean 需要在配置类中使用,即类上需要加上@Configuration注解。
@Bean 注解更加灵活。当需要将第三方类装配到 Spring 容器中,因为没办法源代码上添加@Component注解,只能使用@Bean 注解的方式。

Spring Bean 循环依赖及解决办法

循环依赖指两个或多个Bean之间相互引用,形成了一个无限循环调用的情况。在Spring容器中,如果存在循环依赖,但是都是单例模式的Bean,则可以通过Spring容器提前暴露正在创建的Bean,从而避免循环依赖问题。如果存在循环依赖且其中一方是原型模式的Bean,则Spring无法处理这种情况。
临时规避:显式配置参数。

循环依赖问题在Spring中主要有三种情况:
(1)通过构造方法进行依赖注入时产生的循环依赖问题。
(2)通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题。
(3)通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题。
在Spring中,只有第(3)种方式的循环依赖问题被解决了,其他两种方式在遇到循环依赖问题时都会产生异常。这是因为:
第一种构造方法注入的情况下,在new对象的时候就会堵塞住了,其实也就是”先有鸡还是先有蛋“的历史难题。
第二种setter方法(多例)的情况下,每一次getBean()时,都会产生一个新的Bean,如此反复下去就会有无穷无尽的Bean产生了,最终就会导致OOM问题的出现。
Spring在单例模式下的setter方法依赖注入引起的循环依赖问题,主要是通过二级缓存和三级缓存来解决的,其中三级缓存是主要功臣。解决的核心原理就是:在对象实例化之后,依赖注入之前,Spring提前暴露的Bean实例的引用在第三级缓存中进行存储

解释一下 Spring IoC

IoC简介

IoC(Inversion of Control,即"控制反转"),不是一种技术,而是一种设计思想。传统程序设计,直接在对象内部通过new的方式创建对象。而这种方式会导致程序间的耦合。假如类A依赖于接口IB,且在其内部创建了类B(实现接口IB)的一个实例。这种方式会将类B的实例的创建耦合到了类A中,当需要使用接口IB的另一个实现时,就需要更改代码。而IoC则反转依赖对象的获取方式。IoC提供一个容器来创建这些对象,即由Ioc容器来控制对象的创建。这样,当类A需要依赖接口IB时,仅需声明对IB的引用,具体的实例化由IoC容器负责。

DI简介

DI(Dependency Injection,依赖注入),就是让容器去决定依赖关系,即容器全权负责的组件的装配,它会把符合依赖关系的对象通过属性或者构造函数传递给需要的对象。常见的依赖注入实现有三种:构造方法注入、setter方法注入、接口注入(从 Spring4 开始已被废弃)等。
(1) 构造方法注入:即被注入对象可以通过在其构造方法中声明依赖对象的参数列表,让外部(通常是IoC容器)知道它需要哪些依赖对象,然后外部会检查被注入对象的构造方法,取得其所需要的依赖对象列表,进而为其注入相应对象。
(2) setter方法注入:即当前对象只需要为其依赖对象所对应的属性添加setter方法,IoC容器通过此setter方法将相应的依赖对象设置到被注入对象的方式即setter方法注入。
(3) 接口注入:被注入对象如果想要IoC容器为其注入依赖对象,就必须实现某个接口,这个接口提供一个方法,用来为被注入对象注入依赖对象,IoC容器通过接口方法将依赖对象注入到被注入对象中去。相对于前两种注入方式,接口注入比繁琐和死板,被注入对象必须声明和实现另外的接口。

解释一下AOP

AOP(Aspect-Oriented Programming,面向切面编程),可以看成OOP(Object-Oriented Programing,面向对象编程)的补充和完善。面向对象编程时,建立了一种对象层次结构,可以定义从上到下的关系,但不适合定义从左到右的关系。如日志功能。日志代码水平地散布在所有的对象层次中,也就是说,多个对象,使用相似或相同的日志代码。对于其他类型的代码,如安全性、异常处理等也是如此。这种散布在各个对象中的业务逻辑无关的代码被称为横切(cross-cutting)代码,在面向对象设计中,这种代码导致了大量的代码重复,不利于模块的重用。
简单来说,面向对象编程的自上而下的关系,不适合自左向右的关系,会带来横切代码的重复。而AOP技术主要针对这个问题进行优化。
AOP技术可以将那些影响了多个类的公共行为封装到一个可重用模块,并将其名为“Aspect”(即切面)。所谓“切面”,就是将那些与业务无关但业务模块所共同调用的逻辑或责任封装起来,从而减少系统的重复代码,降低模块间的耦合度。
AOP把软件系统分为两个部分:核心关注点和横切关注点。核心关注点主要指业务处理的主要流程,横切关注点则指与业务处理的主要流程关系不大的部分。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处都基本相似。比如权限认证、日志、事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离。正如Avanade公司的高级方案构架师Adam Magee所说,AOP的核心思想就是“将应用程序中的商业逻辑同对其提供支持的通用服务进行分离。”
请添加图片描述
(1) 切面(Aspect):切面是通知和切入点的结合。通知和切入点共同定义了切面的全部内容。
(2) 连接点(Join point):指方法,在Spring AOP中,一个连接点总是代表一个方法的执行。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
(3) 通知(Advice):在AOP术语中,切面的工作被称为通知。
(4) 切入点(Pointcut):切入点的定义会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点,最经典的使用方式是使用注解。
(5) 引入(Introduction):引入允许我们向现有类添加新方法或属性。
(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。
(7) 织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有以下时间点可以进行织入:
(a) 编译期:切面在目标类编译时被织入。AspectJ的织入编译器是以这种方式织入切面的。
(b) 类加载期:切面在目标类加载到JVM时被织入。需要特殊的类加载器,它可以在目标类被引入应用之前增强该目标类的字节码。AspectJ5的加载时织入就支持以这种方式织入切面。
(c) 运行期:切面在应用运行的某个时刻被织入。一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象。Spring AOP就是以这种方式织入切面。

Spring AOP应用场景

AOP常见于以下场景:
(1) 日志记录
(2) 权限校验和管理
(3) 缓存
(4) 事务

Spring AOP 实现原理

Spring AOP是基于动态代理实现。通过动态代理,可以对被代理对象的方法进行增强。Spring AOP用到了两种动态代理技术:JDK动态代理、CGLIB库。
JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
CGLIB(Code Generation Library) 是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)。
Spring AOP默认策略是如果目标类是接口,则使用 JDK 动态代理技术,否则使用 Cglib 来生成代理。
更多动态代理的介绍可参考笔者之前的文章。

Spring AOP 使用方式

Spring Boot简化了AOP的使用。这里仅介绍如何在Spring Boot中使用AOP。
(1) 引入Maven依赖

<!--引用AOP注解功能开始-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--引用AOP注解功能结束-->

(2) 自定义注解

/**
 * @Author: courage007
 * @Date: 2021/02/05/下午3:54
 * @Description:
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomAnnotation {
    /**
     *
     * @return 指定值
     */
    String value() default "";

    /**
     *
     * @return 指定名称
     */
    String name() default "";

}

(3) 自定义切面并声明切入点并实现AOP通知

/**
 * @Author: courage007
 * @Date: 2021/02/05/下午3:47
 * @Description:
 */
@Aspect
@Component
public class CustomAspect implements Ordered  {
    /**
     * * 切入点
     */
    @Pointcut("@annotation(com.github.courage007.springdemo.annotation.CustomAnnotation)")
    public void pointCutCustomAnnotation() {
        // 无需内容
        System.out.println("Test Pointcut Method");
    }

    @Before("pointCutCustomAnnotation()")
    public void before(JoinPoint joinPoint) {
        try {
            Signature signature  = joinPoint.getSignature();
            //获取方法参数
            Object[] arguments = joinPoint.getArgs();
            //获取目标类
            Class<?> aClass = joinPoint.getTarget().getClass();
            //获取类名
            String targetName = aClass.getName();
            //获取当前方法名insert
            String methodName = signature.getName();

            //获取方法数组
            Method[] methods = aClass.getMethods();
            String operation = "";

            //获取当前方法1
            Class<?>[] argTypes = new Class[arguments.length];
            for (int i = 0; i < arguments.length; i++) {
                argTypes[i] = arguments[i] == null ? null : arguments[i].getClass();
            }
            Method method = null;
            try {
                method = aClass.getMethod(methodName, argTypes);
                operation = method.getAnnotation(CustomAnnotation.class).value();
            } catch (NoSuchMethodException e) {
            }
            //获取当前方法2
            MethodSignature signature1 = (MethodSignature)joinPoint.getSignature();
            Method method1 = signature1.getMethod();
            //获取方法参数类型数组
            Class<?>[] parameterTypes = method1.getParameterTypes();
            //获取当前方法3
            for (Method mm : methods) {
                if (mm.getName().equals(methodName)) {
                    Class<?>[] clazzs = mm.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        //获取代理方法上注解的operation参数的值
                        operation = mm.getAnnotation(CustomAnnotation.class).value();
                        break;
                    }
                }
            }
            //通过方法获取注解参数值
            CustomAnnotation serviceLog= method.getAnnotation(CustomAnnotation.class);
            String item= serviceLog.value();
            String biz = serviceLog.name();
            StringBuilder paramsBuf = new StringBuilder();
            for (Object arg : arguments) {
                paramsBuf.append(arg);
                paramsBuf.append("&");
            }

        } catch (Throwable e) {

        }
    }

    @After("pointCutCustomAnnotation()")
    public void after(JoinPoint joinPoint) {
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class<?> targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operation = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class<?>[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operation = method.getAnnotation(CustomAnnotation.class).value();
                        break;
                    }
                }
            }
            StringBuilder paramsBuf = new StringBuilder();
            for (Object arg : arguments) {
                paramsBuf.append(arg);
                paramsBuf.append("&");
            }
        } catch (Throwable e) {

        }
    }

    /**
     * 环绕通知处理处理
     *
     * @param
     * @throws Throwable
     */
    @Around("pointCutCustomAnnotation()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 先执行业务,注意:业务这样写业务发生异常不会拦截日志。
        Object result = point.proceed();
        try {
            handleAround(point);// 处理日志
        } catch (Exception e) {

        }
        return result;
    }

    @AfterReturning(pointcut = "pointCutCustomAnnotation()")
    public void doAfterReturning(JoinPoint joinPoint) {
        System.out.println("TTEESSTT");
    }

    /**
     * around日志记录
     *
     * @param point
     * @throws SecurityException
     * @throws NoSuchMethodException
     */
    public void handleAround(ProceedingJoinPoint point) throws Exception {
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        // 方法名称
        String methodName = currentMethod.getName();
        // 获取注解对象
        CustomAnnotation aLog = currentMethod.getAnnotation(CustomAnnotation.class);
        // 类名
        String className = point.getTarget().getClass().getName();
        // 方法的参数
        Object[] params = point.getArgs();

        StringBuilder paramsBuf = new StringBuilder();
        for (Object arg : params) {
            paramsBuf.append(arg);
            paramsBuf.append("&");
        }

    }

    @AfterThrowing(pointcut = "pointCutCustomAnnotation()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class<?> targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operation = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class<?>[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operation = method.getAnnotation(CustomAnnotation.class).value();
                        break;
                    }
                }
            }

            StringBuilder paramsBuf = new StringBuilder();
            for (Object arg : arguments) {
                paramsBuf.append(arg);
                paramsBuf.append("&");
            }
        } catch (Exception ex) {
        }
    }
}

(4) 使用自定义注解

/**
 * @Author: courage007
 * @Date: 2021/02/05/下午3:15
 */
public class CustomController {
    @Autowired
    private ICustomService customService;

    @GetMapping("/xxx")
    @CustomAnnotation(value = "测试", name = "test")
    public Integer getXXX() {
        return customService.getXXX();
    }
}

Spring AOP通知顺序

AOP支持的通知类型有:Before、After、Around、AfterReturning、AfterThrowing等。接下来详细说明下:
(1) Before:在目标方法被调用之前做增强处理,@Before只需要指定切入点表达式即可。
(2) AfterReturning:在目标方法正常完成之后做增强,@AfterReturning除了指定切入点表达式后,还可以指定一个返回值形参名returning,代表目标方法的返回值。
(3) AfterThrowing:主要用来处理程序中未处理的异常,@AfterThrowing除了指定切入点表达式后,还可以指定一个throwing的返回值形参名,可以通过该形参名来访问目标方法中所抛出的异常对象。
(4) After:在目标方法完成之后做增强,无论目标方法时候成功完成。@After可以指定一个切入点表达式。
(5) Around:环绕通知,在目标方法完成前后做增强处理,环绕通知是最重要的通知类型,像事务、日志等都是环绕通知,注意编程中核心是一个ProceedingJoinPoint。
注意,spring aop在方法正常返回和抛出异常时,各个通知的执行顺序不同。此外,Spring版本不一样,通知执行顺序也会存在差异。
(1) Spring4.0
正常情况:环绕前置=====@Before目标方法执行=环绕返回=环绕最终===@After=====@AfterReturning
异常情况:环绕前置=====@Before目标方法执行=环绕异常=环绕最终===@After=====@AfterThrowing
(2) Spring5.28
正常情况:环绕前置=====@Before=目标方法执行=@AfterReturning=====@After=环绕返回=环绕最终
异常情况:环绕前置=====@Before=目标方法执行=@AfterThrowing=====@After=环绕异常=环绕最终
可见,Spring 5 提前了@AfterXXX的执行位置。

多个切面的执行顺序

上面的例子仅描述单个切面的执行顺序,如果在同一个方法有多个AOP,其执行不会存在任何顺序。也就是说,这些代码会随机生成。如果需要按照指定的顺序运行,还需手动设置优先级。
请添加图片描述
Spring aop就是一个同心圆,要执行的方法为圆心,最外层的order最小。从最外层按照AOP1、AOP2的顺序依次执行doAround方法,doBefore方法。然后执行method方法,最后按照AOP2、AOP1的顺序依次执行doAfterReturn、doAfter方法。也就是说对多个AOP来说,先before的,一定后after

配置多个切面的执行顺序的方式

Spring支持多种方式配置AOP执行顺序,如实现实现org.springframework.core.Ordered接口、使用@Ordered注解、配置文件添加配置等。
(1) 实现org.springframework.core.Ordered接口

@Aspect  
@Component  
public class CustomAspectWithOrder implements Ordered {
    @Override  
    public int getOrder() {  
        return 1;  
    }  
      
}  

(2) 使用@Ordered注解

@Aspect  
@Component  
@Order(1)  
public class CustomAspectWithOrder {

}  

(3) 配置文件添加配置

<aop:config expose-proxy="true">  
    <aop:aspect ref="aopBean" order="0">    
        <aop:pointcut id="testPointcut"  expression="@annotation(xxx.xxx.xxx.annotation.xxx)"/>    
        <aop:around pointcut-ref="testPointcut" method="doAround" />    
    </aop:aspect>    
</aop:config> 

@Pointcut注解

@Pointcut注解用于声明切入点。在@Pointcut注解中,通过书写切入点表达式来描述需要处理的连接点集合。@Pointcut注解定义如下:

 /**
 * Pointcut declaration
 *
 * @author Alexandre Vasseur
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Pointcut {
    /**
     * @return the pointcut expression
     * We allow "" as default for abstract pointcut
     */
    String value() default "";
    
    /**
     * When compiling without debug info, or when interpreting pointcuts at runtime,
     * the names of any arguments used in the pointcut are not available.
     * Under these circumstances only, it is necessary to provide the arg names in 
     * the annotation - these MUST duplicate the names used in the annotated method.
     * Format is a simple comma-separated list.
     * 
     * @return argNames the argument names (should match those in the annotated method)
     */
    String argNames() default "";
}

@DeclareParents注解

在面向切面的编程中,对方法的增强叫做Wearing(织入),而对类的增强叫introduction(引入)。Introduction Advice(引入增强)就是对已有类添加方法,它也是面向切面编程提供的一种特殊增强。
在Spring AOP中,使用@DeclareParents注解实现引入增强。
(1) 定义增强方法及对应类

/**
 * @Author: courage007
 * @Date: 2021/02/05/下午8:49
 * @Description:
 */
public interface ICustomServiceProxy {
    Integer getYYY();
}
/**
 * @Author: courage007
 * @Date: 2021/02/05/下午8:50
 * @Description:
 */
public class CustomServiceProxyImpl implements ICustomServiceProxy {
    @Override
    public Integer getYYY() {
        return 222;
    }
}

(2) 切面中声明@DeclareParents注解

/**
 * @Author: courage007
 * @Date: 2021/02/05/下午3:47
 * @Description:
 */
@Order(1)
@Aspect
@Component
public class CustomAspect {
    @DeclareParents(value = "com.github.courage007.springdemo.service.impl.CustomService*", defaultImpl = CustomServiceProxyImpl.class)
    private ICustomServiceProxy customServiceProxy;
}

(3) 使用引入增强的方法

/**
 * @Author: courage007
 * @Date: 2021/02/05/下午3:15
 * @Description:
 */
@RestController
@RequestMapping("custom")
public class CustomController {
    @Autowired
    private ICustomService customService;

    @GetMapping("/xxx")
    @CustomAnnotation(value = "测试", name = "test")
    public Integer getXXX() {
        // 使用强制类型转换
        ICustomServiceProxy customServiceProxy = (ICustomServiceProxy) customService;
        customServiceProxy.getYYY();
        return customService.getXXX();
    }
}

Spring 事务

Spring事务的本质就是数据库对事务的支持。Spring框架提供统一的事务抽象,通过统一的编程模型使得应用程序可以很容易地在不同的事务框架之间进行切换。
无论是JTA、JDBC、Hibernate/JPA、Mybatis/Mybatis-Plus,Spring都使用统一的编程模型,使得应用程序可以很容易地在不同的事务框架之间进行切换。这也符合面向接口编程思想。Spring事务框架的代码在org.springframework:spring-tx中。Spring事务抽象的核心类图如下:
请添加图片描述
Spring事务管理的核心接口是PlatformTransactionManager。接口PlatformTransactionManager定义事务操作的行为,PlatformTransactionManager依赖TransactionDefinition和TransactionStatus接口。TransactionDefinition接口定义与Spring兼容的事务属性(如隔离级别、事务传播行为等)。TransactionStatus接口则定义事务的状态(如是否回滚、是否完成、是否包含安全点(Save Point)、将基础会话刷新到数据存储区(如果适用)等)。

PlatformTransactionManager简介

PlatformTransactionManager是Spring事务框架的核心接口。应用程序可以直接使用PlatformTransactionManager,但它并不是主要用于API:应用程序将借助事务模板(TransactionTemplate)或声明式事务(Declarative Transaction)。
对于需要实现PlatformTransactionManager接口的应用程序,可通过继承AbstractPlatformTransactionManager抽象类的方式实现。AbstractPlatformTransactionManager类已实现事务传播行为和事务同步处理。子类需要实现针对事务特定状态(如:begin,suspend,resume,commit)的模板方法。Spring事务框架已经实现了JtaTransactionManager(JPA)和DataSourceTransactionManager(JDBC)。应用程序可以参考以上方法实现事务管理器。PlatformTransactionManager事务继承示例如下:
请添加图片描述

Spring事务隔离级别和传播级别

TransactionDefinition接口中定义了Spring事务隔离级别和Spring事务传播级别。隔离级别主要控制事务并发访问时隔离程度。Spring支持的隔离级别如下:
请添加图片描述
除了使用ISOLATION_DEFAULT表示使用数据库默认的隔离级别外,其余四个隔离级别与数据库规范的隔离级别一致。
需要注意的是,隔离级别越高,意味着数据库事务并发执行性能越差。JDBC规范虽然定义了事务支持的以上行为,但是各个JDBC驱动、数据库厂商对事务的支持程度可能各不相同。出于性能的考虑我们一般设置READ_COMMITTED级别。针对READ_COMMITTED隔离级别无法避免的脏读,通常使用数据库的锁来处理。
传播级别主要控制含事务方法的调用(如一个事务方法调用另一个事务方法)时,Spring对事务的处理方式。Spring事务传播级别共七类。它们是:
(1)PROPAGATION_REQUIRED:支持当前事务,如果当前有事务则加入,如果当前没有事务则新建一个。这种方式是默认的事务传播方式。(一般直接使用,不需要调整)
(2)PROPAGATION_SUPPORTS:支持当前事务,如果当前有事务则加入,如果当前没有事务则以非事务方式执行。
(3)PROPAGATION_MANDATORY:支持当前事务,如果当前有事务则加入,如果当前没有事务则抛出异常。(当前必须有事务)
(4)PROPAGATION_REQUIRES_NEW:不支持当前事务,如果当前有事务则挂起当前事务,然后新创建一个事务,如果当前没有事务则自己创建一个事务。
(5)PROPAGATION_NOT_SUPPORTED:不支持当前事务,如果当前有事务则把当前事务挂起,执行完后恢复事务(忽略当前事务)。
(6)PROPAGATION_NEVER:不支持当前事务,如果当前存在事务,则抛出异常。(当前必须不能有事务)
(7)PROPAGATION_NESTED:如果当前存在事务,则嵌套在当前事务中。如果当前没有事务,则新建一个事务自己执行。对嵌套事务来说,内部事务回滚时不会影响外部事务的提交;但是外部事务回滚会把内部事务一起回滚。(这个和新建一个事务的区别)

Spring事务失效

常见的事务失效场景有:
(1) 异常类型不对:默认支持回滚的是 Runtime 异常,或异常被业务捕获。
(2) 数据源不支持事务:如 MySQL 未开启事务或使用 MyISAM 存储引擎。
(3) 非 Public 方法不支持事务。
(4) Spring传播类型不支持事务。
(5) 事务未被 Spring 接管。

Spring事务有优势

(1) 为不同的事务API 如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。
(2) 为编程式事务管理提供了一套简单的API而不是一些复杂的事务API
(3) 支持声明式事务管理。
(4) 和Spring各种数据访问抽象层很好得集成。

参考

https://blog.csdn.net/adminpd/article/details/123016872 常见Java后端面试题系列——Spring篇
https://blog.csdn.net/qq_57434877/article/details/123714044 spring创建Bean的流程以及Bean的生命周期
https://thinkwon.blog.csdn.net/article/details/104397516 Spring面试题
https://blog.csdn.net/adminpd/article/details/123016872 Java后端面试题系列——Spring篇
https://zhuanlan.zhihu.com/p/623502268 Spring面试题详解—从基础到进阶
https://zhuanlan.zhihu.com/p/493343355 Spring面试题
https://blog.csdn.net/q982151756/article/details/80513340 细说Spring——AOP详解(AOP概览)
https://blog.51cto.com/5914679/2092253#h10 Spring AOP 切点(pointcut)表达式
https://blog.csdn.net/weixin_46009162/article/details/113333311 Spring boot AOP结合注解的使用
https://cloud.tencent.com/developer/article/1441626 spring aop概念、使用、动态代理原理
https://www.jianshu.com/p/5b9a0d77f95f spring aop 及实现方式
https://www.cnblogs.com/orzjiangxiaoyu/p/13869747.html Spring-AOP-基于注解的AOP通知执行顺序
https://blog.csdn.net/hxpjava1/article/details/55504513 spring多个AOP执行先后顺序
https://www.jianshu.com/p/f7238613c877 Spring AOP注解@DeclareParents的使用
https://www.cnblogs.com/chihirotan/p/7365890.html Spring AOP中引入增强
https://www.cnblogs.com/wangshen31/p/9383828.html 用注解@DelcareParents实现引用增强
https://blog.csdn.net/a745233700/article/details/80959716 Spring常见面试题总结

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1020287.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

iPhone升级iOS17后待机模式不能用、没反应?这7个方法快速解决!

iPhone待机模式是苹果为iOS17版本加入的新功能之一&#xff0c;当我们不用iPhone 时&#xff0c;能将它随手放在一旁&#xff0c;并以横向全屏的方式观看时钟与App小工具资讯等。 不过有些果粉发现他们的iPhone待机模式不能用、没反应&#xff0c;照着步骤操作也无法进入iPhon…

MX6LL控制LED设备

注&#xff1a;本篇基于野火IMX6LL PRO开发板 一.什么是驱动程序 驱动程序&#xff08;Driver&#xff09;是一种软件&#xff0c;用于充当操作系统与硬件设备之间的桥梁&#xff0c;使它们能够互相通信和交互。驱动程序的主要功能是提供一个标准化的接口&#xff0c;使操作系…

从0搭建夜莺v6基础监控告警系统(一):基础服务安装

文章目录 1. 写在前面1.1. 官方文档传送门1.2. 部署环境 2. 服务安装2.1. 基础设置2.2. 安装中间件2.3. 安装 nightingale-v62.4. 安装 VictoriaMetrics2.5. 安装 Categraf 3. 部署总结3.1. 安装总结 1. 写在前面 1.1. 官方文档传送门 项目介绍 架构介绍 仪表盘 黄埔营培训计…

来袭!SOLIDWORKS 2024 主要增强功能

在SOLIDWORKS软件使用过程中&#xff0c;我们知道您创建了出色的设计&#xff0c;您的出色设计也会得到构建。为了简化和加快从概念到制造产品的产品开发流程&#xff0c;SOLIDWORKS 2024 包含用户驱动的全新增强功能&#xff0c;重点关注&#xff1a; • 提高工作智能化程度。…

怎么在便携式手持嵌入式设备中实现安全的数字数据传输

为了实施附加的安全性&#xff0c;一些密码算法也可以指定一组不应从设备公开的常数值。这些存储在设备中的&#xff0c;需要防止未经授权暴露的秘密密钥和秘密值在一系列文章中被称为“秘密密钥”。 秘密密钥存储在设备内部&#xff0c;甚至在设备的整个生命周期中都存在。设…

浅谈双十一背后的支付宝LDC架构和其CAP分析

本人汤波&#xff0c;superthem.com 圆领超级个体创始人&#xff0c;Github page地址&#xff1a;https://tbwork.github.io/ 看到很多人在盗用我的文章&#xff0c;还标记成原创&#xff0c;进行收费&#xff0c;非常令人作呕。 我的所有技术文章全部免费阅读&#xff0c;大家…

在Python中 作用域与命名空间的坑

前言&#xff1a; 嗨喽~大家好呀&#xff0c;这里是魔王呐 ❤ ~! python更多源码/资料/解答/教程等 点击此处跳转文末名片免费获取 1. 命名空间 1.1 什么是命名空间 Namespace命名空间&#xff0c;也称名字空间&#xff0c;是从名字到对象的映射。 Python中&#xff0c;大…

VMware17 不可恢复错误mks解决方案

用的虚拟机VMware17版本&#xff0c;然后运行带HDR的unity程序&#xff0c;结果报错 网上找了很多解决方案&#xff0c;都没用。毕竟需要在不放弃虚拟机3D加速的情况下运行。 最终皇天不负有心人&#xff0c;亲测有效的方法&#xff1a; 在虚拟机名字.vmx文件里添加以下2行&a…

【整理】难得的中文开源数据集

搞大模型训练&#xff0c;最重要的就是高质量的数据集。 得数据者得天下。全球最大的AI开源社区Huggingface上&#xff0c;已经有5万多的开源数据集了&#xff0c;其中涉及中文的数据集只有区区可怜的151个。中国的AI产业要迎头赶上&#xff0c;中文的数据集是最大的短板之一。…

IOTE2023物联网展最新快讯|央企入驻,找物联网平台这一家就够了

IOTE 2023第20届国际物联网展深圳站即将于9月20-22日在深圳国际会展中心&#xff08;宝安&#xff09;启幕&#xff01;航天科技控股集团股份有限公司旗下AIRIOT物联网平台亮相【工业物联网展区9B31-1展位】。 AIRIOT物联网平台定位于通用型物联网技术框架产品&#xff0c;以软…

oracle创建数据库以及用户,并导入dmp格式数据

oracle创建数据库以及用户&#xff0c;并导入dmp格式数据 安装可参考之前的文章https://blog.csdn.net/qq_43421954/article/details/132717546?spm1001.2014.3001.5501 首先创建表空间&#xff08;也就是其他数据库所谓的数据库&#xff09; 使用的是navicat,连接配置可以参…

python 异常

1.捕获异常 2.密码爆破 3.

Feign远程调用丢失请求头

前言 我们在写服务端项目的时候&#xff0c;总会限制对某些资源的访问&#xff0c;最常见的就是要求用户先登录才能访问资源&#xff0c;当用户登录后就会将此次会话信息保存进session&#xff0c;同时返回给浏览器指定的cookie键值&#xff0c;下次浏览器再次访问&#xff0c…

【虚拟现实】2023年VR技术的10个应用行业

1.医疗保健 现代医疗保健的培训方式离不开VR虚拟现实。。由于医疗行业的特殊性&#xff0c;不允许拿大量的病人来练手&#xff0c;但医疗又非常注重实践&#xff0c;一些新手医生就缺乏锻炼的机会&#xff0c;而VR虚拟现实技术很好的解决了这一问题。医生可以在高清晰、低延时…

【C++】搜索二叉树底层实现

目录 一&#xff0c;概念 二&#xff0c;实现分析 1. 插入 &#xff08;1.&#xff09;非递归版本 &#xff08;2.&#xff09;递归版本 2. 打印搜索二叉树 3.查找函数 &#xff08;1.&#xff09;非递归版本 &#xff08;2.&#xff09;递归版本 4. 删除函数&#x…

【Linux-Day13-生产者消费者模型】

生产者消费者模型 生产者消费者问题概述 生产者/消费者问题&#xff0c;也被称作有限缓冲问题。可以描述为&#xff1a;两个或者更多的线程共享同一个缓冲 区&#xff0c;其中一个或多个线程作为“生产者”会不断地向缓冲区中添加数据&#xff0c;另一个或者多个线程作为“消…

基于CNN-LSTM的时序预测MATLAB实战

一、算法原理 1.1 CNN原理 卷积神经网络具有局部连接、权值共享和空间相关等特性。卷积神经网络结构包含卷积层、激活层和池化层。 &#xff08;a&#xff09;二维卷积层将滑动卷积滤波器应用于输入。该层通过沿输入垂直和水平方向 移动滤波器对输入进行卷积&#xff0c;并计…

阿里云无影电脑:免费体验无影云电脑3个月

阿里云无影云电脑免费领取流程&#xff0c;免费无影云电脑配置为4核8G&#xff0c;可以免费使用3个月&#xff0c;阿里云百科分享阿里云无影云电脑&#xff08;云桌面&#xff09;免费申请入口、申请流程及免费使用限制条件说明&#xff1a; 目录 阿里云无影云电脑免费申请入…

汉威科技亮相上海传感器展并发表主题演讲,智能传感器大有可为

9月15日&#xff0c;第8届中国&#xff08;上海&#xff09;国际传感器技术与应用展览会圆满落幕&#xff0c;该展会吸引了逾400家传感领域国内外的企业、100余家专业传感应用单位、500余位传感大咖共同参与&#xff0c;展会观众达30000人。作为全球三大传感器展之一的盛会&…

2023年最热门的编程语言:前进的趋势和机会

2023年最热门的编程语言&#xff1a;前进的趋势和机会 2023年最热门的编程语言&#xff1a;前进的趋势和机会摘要引言1. 编程语言的热门趋势1.1 新兴编程语言的崛起1.2 编程语言的可持续性发展1.3 跨平台编程语言的兴起1.4 人工智能和机器学习编程语言的需求 2. 编程语言职业机…