Spring 常见面试题

1、Spring概述

1.1、Spring是什么?

  1. Spring是一个轻量级Java开发框架,目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题
  2. Spring最根本的使命是解决企业级应用开发的复杂性,即简化Java开发。
  3. 这些功能的底层都依赖于它的两个核心特性,也就是依赖注入(dependency injection,DI)和面向切面编程(aspect-oriented programming,AOP)
1.2 Spring的优缺点

优点:

  1. 方便解耦,简化开发 :Spring就是一个大工厂,可以将所有对象的创建和依赖关系的维护,交给Spring管理。
  2. AOP编程的支持: Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
  3. 声明式事务的支持:只需要通过配置就可以完成对事务的管理,而无需手动编程。
  4. 方便程序的测试: Spring对Junit4支持,可以通过注解方便的测试Spring程序。
  5. 方便集成各种优秀框架: Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架的直接支持(如:Struts、Hibernate、MyBatis等)。
  6. 降低JavaEE API的使用难度:Spring对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低

缺点

  1. Spring明明一个很轻量级的框架,却给人感觉大而全
  2. Spring依赖反射,反射影响性能
  3. 使用门槛升高,入门Spring需要较长时间
1.3 Spring的组成模块

Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。 而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)和设备支持(Instrmentation) 、数据访问与集成(Data Access/Integeration) 、 Web、 消息(Messaging) 、 Test等 6 个模块中。 以下是 Spring 5 的模块结构图:
img

  • **spring core:**提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
  • **spring beans:**提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。
  • **spring context:**构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。
  • **spring jdbc:**提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。
  • **spring aop:**提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
  • **spring Web:**提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。
  • **spring test:**主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。
1.4 Spring中应用的设计模式
  • 工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;
  • 单例模式:Bean默认为单例模式。
  • 代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;
  • 模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。
  • 观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现–ApplicationListener。

2、Spring IOC

2.1、什么是IOC

IOC,Inversion of Control,控制反转,指将对象的控制权转移给Spring框架,由 Spring 来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。

​ 最直观的表达就是,以前创建对象的时机和主动权都是由自己把控的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁(比如Connection等),对象始终会和其他接口或类耦合起来。而 IOC 则是由专门的容器来帮忙创建对象,将所有的类都在 Spring 容器中登记,当需要某个对象时,不再需要自己主动去 new 了,只需告诉 Spring 容器,然后 Spring 就会在系统运行到适当的时机,把你想要的对象主动给你。也就是说,对于某个具体的对象而言,以前是由自己控制它所引用对象的生命周期,而在IOC中,所有的对象都被 Spring 控制,控制对象生命周期的不再是引用它的对象,而是Spring容器,由 Spring 容器帮我们创建、查找及注入依赖对象,而引用对象只是被动的接受依赖对象,所以这叫控制反转。

看不懂,没有关系,帅哥公主请看代码:

一般情况:

public class Test {public static void main(String[] args) {A a = new A();//a.b = new B();  !!! 如果我们没有这句代码,运行是会报错的。a.b.shout();}
}
class A{B b; //这相当于是一个指针,但是没有指向任何对象
}
class B{void shout(){System.out.println("我是B");}
}

Spring情况

@Component
public class A {@Autowiredpublic B b;
}
@Component
public class B {public void shout(){System.out.println("我是B");}
}
public class MyTest {@Testpublic void test1(){ApplicationContext context =  new ClassPathXmlApplicationContext("beans.xml");A a = context.getBean("a", A.class);//这个地方,我们 new B() 了吗,没有吧,但是可以直接使用//因为 spring 容器帮我们创建了,也处理了二者之间的依赖关系。我们直接用就行了a.b.shout();}
}

2.2、什么是DI

IoC 的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection,依赖注入)来实现的,即应用程序在运行时依赖 IoC 容器来动态注入对象所需要的外部依赖。而 Spring 的 DI 具体就是通过反射实现注入的,反射允许程序在运行的时候动态的生成对象、执行对象的方法、改变对象的属性

能注入的数据类型

  • 基本类型和String。
  • 其他bean类型(在配置文件中或者注解配置过的bean)。
  • 复杂类型/集合类型。

注入的方式

  • 使用构造函数提供。
  • 使用set方法提供。
  • 使用注解提供。
  • 接口注入(不推荐)

2.2.1 构造函数注入

顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。
标签中的属性:
type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型。
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置是从0开始。
name:用于指定给构造函数中指定名称的参数赋值。
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象
优势
在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功。
弊端
改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。

<!--默认构造器方式-->
<bean id="user" class="com.kuang.pojo.User"><property name="name" value="张三"/>
</bean><!--通过有参构造创建对象。方式一:下标赋值-->
<bean id="user" class="com.kuang.pojo.User"><constructor-arg index="0" value="jerry"/>
</bean><!--通过有参构造创建对象。方式二:类型创建,不建议使用-->
<bean id="user" class="com.kuang.pojo.User"><constructor-arg type="java.lang.String" value="jarry"/>
</bean><!--通过有参构造创建对象。方式三:通过参数名,推荐使用-->
<bean id="user" class="com.kuang.pojo.User"><constructor-arg name="name" value="jarry"/><constructor-arg name="birthday" ref="now"/>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>

2.2.2 set方法注入

顾名思义,就是在类中提供需要注入成员的 set 方法。

set方法注入【常用】
标签的属性
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在spring的Ioc核心容器中出现过的bean对象。
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数。
弊端:
如果有某个成员必须有值,则获取对象是有可能set方法没有执行。

<bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2"><property name="name" value="tom" ></property><property name="age" value="23"></property><property name="birthday" ref="now"></property>
</bean>

2.2.3 注解注入

//等价于 <bean id="user" class="com.x.pojo.User"/>
@Component //组件
@Scope("prototype")
public class User {//等价于 <property name="name" value="嘿嘿"/>
//    @Value("嘿嘿")public String name;//放set方法也行@Value("嘿嘿")public void setName(String name) {this.name = name;}
}

@Component注解用来将类声明为容器的组件,当容器启动后,通过扫描,会生成对应的对象交给容器托管。

@Value注解用来给属性赋值,可以直接放在属性上,也可以放在对应的set方法上

@Scope注解声明 类的作用域

2.3 自动装配

自动装配针对的是引入型对象

自动装配和依赖注入有什么联系与区别???

让我们来看看 Gpt 的回答

自动装配(Autowired)和依赖注入(Dependency Injection)在Spring框架中是相关概念,它们实现了相同的目标,即将一个对象的依赖关系注入到另一个对象中。然而,它们之间存在一些联系和区别:

联系:

  1. 实现方式: 自动装配是一种依赖注入的方式,它可以通过@Autowired注解(基于注解的自动装配)或者在XML配置文件中使用<property>元素(基于XML的自动装配)来实现。
  2. 目标: 自动装配和依赖注入的共同目标是减少硬编码,提高代码的灵活性和可维护性,同时降低了对象之间的耦合度。
  3. 由Spring容器管理: 在两种情况下,Spring容器负责管理Bean之间的关系,确保依赖的Bean在需要的时候能够被正确注入到目标Bean中。

区别:

  1. 显式配置 vs 隐式配置: 依赖注入可以通过显式配置(在XML配置文件或Java配置类中明确指定依赖关系)或者隐式配置(使用@Autowired注解进行自动装配)来实现。自动装配是一种依赖注入的方式,它是一种自动化的、隐式的依赖注入。
  2. 灵活性: 自动装配提供了更大的灵活性,因为它不需要在配置文件中显式地指定每个Bean的依赖关系。相反,Spring容器会根据自动装配规则自动地完成依赖注入,这样就减少了配置的工作量,同时也更容易适应变化。
  3. 粒度: 依赖注入的粒度更细,可以对一个类的特定属性进行注入,也可以对构造方法进行注入,而自动装配通常是针对整个类进行的,它会自动装配类中所有符合条件的属性。

综上所述,自动装配是依赖注入的一种实现方式,它通过隐式的方式完成依赖注入,提高了配置的便利性和灵活性。依赖注入是一种更广泛的概念,它包括了显式配置和隐式配置两种方式,而自动装配则是依赖注入的一种特殊形式。

public class People {private Dog dog; //用来测试 xml 文件private Cat cat; // 用来测试 注解
}
public class Cat {String name;
}
public class Dog {private String name;
}

放在此处,下面要用到

2.3.1 基于XML配置文件

自动装配是spring满足bean依赖的一种方式。spring会通过在上下文寻找匹配,实现bean的自动装配。

测试文件

public void test1(){ApplicationContext context =  new ClassPathXmlApplicationContext("beans.xml");People people = context.getBean("people", People.class);System.out.println(people.getDog().getName());
}
ByName:

使用 byName 它会根据 people对应的属性名去匹配 id 名与之相同的 bean

(注意:它匹配id之后会进行一个类型的检查,总不能给 cat属性装配一个 dog 对象吧)

<bean id="dog" class="com.x.pojo.Dog"><property name="name" value="dog"/>
</bean>
<bean id="dog2" class="com.x.pojo.Dog"><property name="name" value="dong2"/>
</bean>
<bean id="people" class="com.x.pojo.People" autowire="byName"/>

输出结果为 : dog ;

总结: byName 根据 name 去匹配 ,如果 没有匹配到 就会报错

ByType :

使用 byType 它会根据 people对应的属性的类型名去匹配 class属性对应与之对应的 bean

<bean id="dog1" class="com.x.pojo.Dog"><property name="name" value="dog"/>
</bean>
<bean id="people" class="com.x.pojo.People" autowire="byType"/>

输出结果: dog

总结:byType的根据属性去匹配,如果有多个 匹配 的 bean 则会报错

2.3.2 基于注解

xml文件的形式 是在 标签使用 autowired属性

注解则有两种常用的: @Autowired@Resource

使用方法很简单:我们直接再对应的属性上或者 set方法上添加注解就行

public class People {@Autowired //注意 Resource 不能用在 构造方法上People(){}@Autowired//@Resourceprivate Cat cat;private String name;public Cat getCat() {return cat;}//@Resource@Autowiredpublic void setCat(Cat cat) {this.cat = cat;}

二者还是有区别的

1、来源不同:

  • @Autowired 是 Spring 定义的注解
  • @Resource 是 Java 定义的注解(记得导入相应的依赖)

2、依赖查找顺序不同

  • @Autowired 是先根据类型(byType)查找,如果存在多个 Bean 再根据名称(byName)进行查找
  • @Resource 是先根据名称查找,如果(根据名称)查找不到,再根据类型进行查找

3、参数不同

  • @Autowired 只支持设置一个 required 的参数
    • 可以搭配@Qualifier注解,但是一旦指定名字且没有对应的id,也不会进行type匹配了
  • @Resource支持很多个

4、用法不同

  • @Autowired 可以用在 属性,set方法,构造器方法
  • @Resource不能用在构造方法

2.4 使用注解实现bean的托管

我们发现前面我们每声明一个类都要在 xml 文件中添加一个对应的标签,当类比较多时,这个工作量就会很大。有没有别的方法呢?

这就要提到我们的@Component注解了。使用这个注解,我们要在配置文件中进行声明

<!--指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.x"/>
<!--开启注解配置-->
<context:annotation-config/> 
<context:component-scan base-package="com.x"/>

当spring容器启动后,他就会自动去扫描 com.x包下所有添加了

@Component、@controller、@service、@repository注解的类,将其看成bean交给容器托管。

我们在类上添加一个 @Component 注解,就可以将类标记为Spring中的bean了

@Component //组件 
public class User {//等价于 <property name="name" value="嘿嘿"/>public String name;@Value("嘿嘿")public void setName(String name) {this.name = name;}
}

基于 @Component 扩展的注解还有三个: @controller、@service、@repository

1@controller:   controller控制器层(注入服务)
2@service :      service服务层(注入dao)
3@repository :  dao持久层(实现dao访问)
4@component:  标注一个类为Spring容器的Bean,(把普通pojo实例化到spring容器中,相当于配置文件中的<bean id="" class=""/>

这里不具体讲四者有什么区别,只是希望大家简单的知道 @service 标记的类,说说这个类是service层的实现就够了

2.5 配置类实现

JavaConfig是Spring的一个子项目 ,在Spring4之后,它变成了核心功能(SpringBoot中推荐使用这种方法)

配置类

myConfig2.class

@Configuration
@ComponentScan(basePackages = "com.x.pojo")
public class myConfig2 {}

myConfig.class

package com.x.config;
@Configuration //它也会被spring托管,它的本质也是一个组件
// @Configuration 代表他是一个配置类,就和beans.xml一样
@Import(myConfig2.class)
public class myConfig {//注册一个bean,就相当于之前写的一个bean标签//这个方法的名字,就相当于id//返回值,就相当于 class属性@BeanUser getUser(){return new User();//返回要注入bean的对象}
}

@Configration 注解:声明当前类是一个配置类,相当于 Spring 中的一个 XML 文件
@Bean 注解:作用在方法上,声明当前方法的返回值是一个 Bean
配置类的加载

我们发现此处用到了一个新标签 @Bean,我们对比最开始用到的

@Component

2.4.1 @Bean 和 @Component 的区别

@Component(和@Service和@Repository)用于自动检测和使用类路径扫描自动配置bean。注释类和bean之间存在隐式的一对一映射(即每个类一个bean)。
@Bean用于显式声明单个bean,而不是让Spring像上面那样自动执行它。它将bean的声明与类定义分离,并允许您精确地创建和配置bean

该怎么理解这段话呢?

首先 我们知道 @Component 是放在类上的,这样就可以将这个类标记为bean,并交给spring托管。我们完完全全就是在 这个类的 “源码”上进行的配置。那如果我们引入的是第三方的类,不知道源码,那我们怎么用@Component注释呢?这个时候就要用 @Bean了。也就是所谓的声明与类定义分离。我们不需要知道类是怎么定义的。我们只需要声明一个这个类的对象?

那可能又有疑问了,既然是声明对象为什么不这样写呢?

@Configuration
public class AppConfig {@BeanUser user;
}

现在我们去看@Bean的源码:这是源码的第一句话

Indicates that a method produces a bean to be managed by the Spring container.

中文意思是:指示方法生成要由 Spring 容器管理的 Bean。这样就应该能明白为啥放在方法上了吧。

3、Spring Bean

3.1、Spring Bean是什么?

Bean作为Spring框架面试中不可或缺的概念,其本质上是指代任何被Spring加载生成出来的对象。(本质上区别于Java Bean,Java Bean是对于Java类的一种规范定义。)Spring Bean代表着Spring中最小的执行单位,其加载、作用域、生命周期的管理都由Spring操作。可见Spring Bean在整个Spring框架中的重要地位。

什么是Java Bean

并非所有的类都是 Java Bean,其是一种特殊的类,具有以下特征:

  • 提供一个默认的无参构造函数。
  • 需要被序列化并且实现了 Serializable 接口。
  • 可能有一系列可读写属性,并且一般是 private 的。
  • 可能有一系列的 getter 或 setter 方法。

3.2、Spring Bean的作用域

1、singleton:默认作用域,单例bean,每个容器中只有一个bean的实例。

2、prototype:为每一个bean请求创建一个实例。

3、request:为每一个request请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

4、session:与request范围类似,同一个session会话共享一个实例,不同会话使用不同的实例。

5、global-session:全局作用域,所有会话共享一个实例。如果想要声明让所有会话共享的存储变量的话,那么这全局变量需要存储在global-session中。

3.3、Spring 的容器启动流程

详细见此文章 Spring Ioc 容器启动流程-CSDN博客

3.4、Spring Bean的生命周期

正在创作中。。。

3.5、Spring Bean的线程安全问题

Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论。

(1)对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题。

(2)对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的。比如Controller类、Service类和Dao等,这些Bean大多是无状态的,只关注于方法本身。

有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的。

无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的。

对于有状态的bean(比如Model和View),就需要自行保证线程安全,最浅显的解决办法就是将有状态的bean的作用域由“singleton”改为“prototype”。

也可以采用ThreadLocal解决线程安全问题,为每个线程提供一个独立的变量副本,不同线程只操作自己线程的副本变量。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。

3.6、Spring Bean的循环依赖问题

正在创作中

3.7、BeanFactory 和 ApplicationContext 区别

BeanFactory是Spring里面最底层的接口,是IoC的核心,定义了IoC的基本功能,包含了各种Bean的定义、加载、实例化,依赖注入和生命周期管理

ApplicationContext接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

  • 继承MessageSource,因此支持国际化。
  • 资源文件访问,如URL和文件(ResourceLoader)。
  • 载入多个(有继承关系)上下文(即同时加载多个配置文件) ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。
  • 提供在监听器中注册bean的事件。

区别:

1、

BeanFactroy采用的是延迟加载形式来注入Bean的,只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能提前发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。

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

2、

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

3、

BeanFactory通常以编程的方式被创建,ApplicationContext还能以声明的方式创建,如使用ContextLoader。

4、Spring Aop

4.1、什么是 Aop

OOP面向对象,允许开发者定义纵向的关系,但并不适用于定义横向的关系,会导致大量代码的重复,而不利于各个模块的重用。

AOP,一般称为面向切面,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。

4.2、Spring AOP里面的几个名词的概念:

(1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。

(2)切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。

在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。

(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。

切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。

(4)通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。

(5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。

(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。

(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

几个概念的关系图可以参考下图:

(1)连接点(Join point):指程序运行过程中所执行的方法。在Spring AOP中,一个连接点总代表一个方法的执行。

(2)切面(Aspect):被抽取出来的公共模块,可以用来会横切多个对象。Aspect切面可以看成 Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。

在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。

(3)切点(Pointcut):切点用于定义 要对哪些Join point进行拦截。

切点分为execution方式和annotation方式。execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截add*、search*。annotation方式可以指定被哪些注解修饰的代码进行拦截。

(4)通知(Advice):指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。

(5)目标对象(Target):包含连接点的对象,也称作被通知(Advice)的对象。 由于Spring AOP是通过动态代理实现的,所以这个对象永远是一个代理对象。

(6)织入(Weaving):通过动态代理,在目标对象(Target)的方法(即连接点Join point)中执行增强逻辑(Advice)的过程。

(7)引入(Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

几个概念的关系图可以参考下图:

img

4.3 通知

Advice的类型:

(1)前置通知(Before Advice):在连接点(Join point)之前执行的通知。

(2)后置通知(After Advice):当连接点退出的时候执行的通知(不论是正常返回还是异常退出)。

(3)环绕通知(Around Advice):包围一个连接点的通知,这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也可以选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

(4)返回后通知(AfterReturning Advice):在连接点正常完成后执行的通知(如果连接点抛出异常,则不执行)

(5)抛出异常后通知(AfterThrowing advice):在方法抛出异常退出时执行的通知

Advice的执行顺序:

没有异常情况下的执行顺序:

around before advice
before advice
target method 执行
after advice
around after advice
after Returning advice

出现异常情况下的执行顺序:

around before advice
before advice
target method 执行
after advice
around after advice
afterThrowing advice
java.lang.RuntimeException:异常发生

4.4、使用方法

public void test(){ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");//动态代理代理的是接口UserService userService = context.getBean("userService", UserService.class);userService.add();
}

用来测试的代码

第一种:原生的AOP

<bean id="userService" class="com.x.service.UserServiceImpl"/>
<bean id="log" class="com.x.Log.Log"/>
<bean id="afterLog" class="com.x.Log.AfterLog"/>
<!--配置AOP:需要导入AOP的约束-->
<aop:config><!--切入点,我们需要在哪执行我们的方法--><aop:pointcut id="pointcut" expression="execution(* com.x.service.UserServiceImpl.*(..))"/><!--执行环绕增加--><aop:advisor advice-ref="log" pointcut-ref="pointcut"/><aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
</aop:config>

自定义的log类,要实现相应的接口

public class AfterLog implements AfterReturningAdvice {//返回后的//returnValue : 返回值@Overridepublic void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);}
}
public class Log implements MethodBeforeAdvice {//method:要执行对象的目标方法//objects:参数//target:目标对象@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {System.out.println(target.getClass().getName()+"的"+method.getName()+"被执行了");}
}

输出结果为:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第二种:自定义方法

 <bean id="diy" class="com.x.diy.DiyPointCut"/><aop:config><!--自定义切面,ref要引用的类--><aop:aspect ref="diy"><!--切入点--><aop:pointcut id="point" expression="execution(* com.x.service.UserServiceImpl.*(..))"/><!--通知--><aop:before method="before" pointcut-ref="point"/><aop:after method="after" pointcut-ref="point"/></aop:aspect></aop:config>
public class DiyPointCut {public void before(){System.out.println("==========方法执行前==========");}public void after(){System.out.println("==========方法执行后==========");}
}

执行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第三种:使用注解

 <!--方式三--><bean id="apc" class="com.x.diy.AnnotationPointCut"/><!--开启注解支持  JDK(默认 proxy-target-class="false")  cglib(proxy-target-class="true")--><aop:aspectj-autoproxy />
@Aspect//标注这个类是一个切面
public class AnnotationPointCut {@Before("execution(* com.x.service.UserServiceImpl.*(..))")public void before(){System.out.println("==========方法执行前==========");}@After("execution(* com.x.service.UserServiceImpl.*(..))")public void after(){System.out.println("==========方法执行后==========");}//在就环绕增强中,我们可以给定一个参数,待定我们要获取的处理切入的点@Around("execution(* com.x.service.UserServiceImpl.*(..))")public void around(ProceedingJoinPoint jp) throws Throwable {System.out.println("环绕前");Signature signature = jp.getSignature();//获得签名System.out.println("signature:"+signature);//执行方法jp.proceed();//方法执行System.out.println("环绕后");}
}

执行结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

5、Spring 事务

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。Spring只提供统一事务管理接口,具体实现都是由各数据库自己实现,数据库事务的提交和回滚是通过 redo log 和 undo log实现的。Spring会在事务开始时,根据当前环境中设置的隔离级别,调整数据库隔离级别,由此保持一致。

5.1、Spring事务的种类:

spring支持编程式事务管理和声明式事务管理两种方式:

①编程式事务管理使用TransactionTemplate。

②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前启动一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中,减少业务代码的污染。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

5.2、spring的事务传播机制:

pring事务的传播机制说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。事务传播机制实际上是使用简单的ThreadLocal实现的,所以,如果调用的方法是在新线程调用的,事务传播实际上是会失效的。

PROPAGATION_REQUIRED:(默认传播行为)如果当前没有事务,就创建一个新事务;如果当前存在事务,就加入该事务。

② PROPAGATION_REQUIRES_NEW:无论当前存不存在事务,都创建新事务进行执行。

③ PROPAGATION_SUPPORTS:如果当前存在事务,就加入该事务;如果当前不存在事务,就以非事务执行。‘

④ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑤ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则按REQUIRED属性执行。

⑥ PROPAGATION_MANDATORY:如果当前存在事务,就加入该事务;如果当前不存在事务,就抛出异常。

⑦ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

5.3、Spring中的隔离级别:

① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。

② ISOLATION_READ_UNCOMMITTED:读未提交,允许事务在执行过程中,读取其他事务未提交的数据。

③ ISOLATION_READ_COMMITTED:读已提交,允许事务在执行过程中,读取其他事务已经提交的数据。

④ ISOLATION_REPEATABLE_READ:可重复读,在同一个事务内,任意时刻的查询结果都是一致的。

⑤ ISOLATION_SERIALIZABLE:所有事务逐个依次执行。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

新能源汽车高压线束是如何快速连接到测试设备上进行电性能测试的

快速连接形成稳定的电测试在新能源行业里面是很常见的测试场景&#xff0c;比如说在新能源汽车行业的电池包、电机、电控制器的电性能测试中会有很多高压线束&#xff0c;需要将这些线束和电池包、电控制器、电机与测试设备快速连接在一起进行相关的EOL/DCR测试。 新能源汽车高…

PHP的curl会话

介绍: Curl&#xff08;Client for URLs&#xff09;在PHP中是一个强大而灵活的工具&#xff0c;用于进行各种网络请求。PHP中的Curl库允许开发者通过代码模拟HTTP请求、与API交互、进行数据传输等。在这里&#xff0c;我们将详细解析PHP中Curl会话的各个方面&#xff0c;涵盖…

《UML和模式应用(原书第3版)》2024新修订译本部分截图

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 机械工业出版社即将在2024春节前后推出《UML和模式应用&#xff08;原书第3版&#xff09;》的典藏版。 受出版社委托&#xff0c;UMLChina审校了原中译本并做了一些修订。同比来说&a…

Deepsort从入门到精通

1 &#xff0c;sort和Deepsort算法 在目标检测领域&#xff0c;sort&#xff08;Simple Online and Realtime Tracking&#xff09;算法和 DeepSORT&#xff08;Deep Learning for Multi-Object Tracking&#xff09;算法是两种常用的目标追踪算法&#xff0c;它们通常与目标检…

数据结构-堆

一、什么是堆 先了解两种特别的二叉树 满二叉树 除最后一层无任何子节点外&#xff0c;每一层上的所有结点都有两个子结点的二叉树 完全二叉树 完全二叉树相对于满二叉树来说&#xff0c;最后一层叶子节点从左到右中间没有空缺的&#xff0c;像这样&#xff1a; 计算机科学…

Netty第三部

继续Netty第二部的内容 一、ChannelHandler 1、ChannelHandler接口 ChannelHandler是Netty的主要组件&#xff0c;处理所有的入站和出站数据的应用程序逻辑的容器&#xff0c;可以应用在数据的格式转换、异常处理、数据报文统计等 继承ChannelHandler的两个子接口&#xff…

049-第三代软件开发-软件部署脚本(一)

第三代软件开发-软件部署脚本(一) 文章目录 第三代软件开发-软件部署脚本(一)项目介绍软件部署脚本(一)其他方式 关键字&#xff1a; Qt、 Qml、 bash、 shell、 脚本 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&#xff08;Qt Meta-Object…

​软考-高级-系统架构设计师教程(清华第2版)【第1章-绪论-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第1章-绪论-思维导图】 课本里章节里所有蓝色字体的思维导图

软文推广中如何搭建媒体矩阵

媒体矩阵简单理解就是在不同的媒体平台上&#xff0c;根据运营目标和需求&#xff0c;建立起全面系统的媒体布局&#xff0c;进行多平台同步运营。接下来媒介盒子就来和大家聊聊&#xff0c;企业在软文推广过程中为什么需要搭建媒体矩阵&#xff0c;又该如何搭建媒体矩阵。 一、…

统信UOS Linux操作系统下怎么删除某个程序在开始菜单或桌面的快捷方式

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 引言 统信操作系统的开始菜单包罗万象&#xff0c;将所有应用的快捷方式都放在了开始菜单内。 虽然提供了分类展示的能力&#xff0c;但无论是分类方式还是未分类方式&#xff0c;都不能像windows一样将这…

Java之SpringCloud Alibaba【八】【Spring Cloud微服务Gateway整合sentinel限流】

一、Gateway整合sentinel限流 网关作为内部系统外的一层屏障,对内起到-定的保护作用&#xff0c;限流便是其中之- - .网关层的限流可以简单地针对不同路由进行限流,也可针对业务的接口进行限流,或者根据接口的特征分组限流。 1、添加依赖 <dependency><groupId>c…

CSDN每日一题学习训练——Java版(克隆图、最接近的三数之和、求公式的值)

版本说明 当前版本号[20231109]。 版本修改说明20231109初版 目录 文章目录 版本说明目录克隆图题目解题思路代码思路参考代码 最接近的三数之和题目解题思路代码思路参考代码 求公式的值题目解题思路代码思路参考代码 克隆图 题目 给你无向 连通(https://baike.baidu.com…

Python算法:动态规划解决0-1背包问题

动态规划&#xff08;Dynamic Programming&#xff0c;简称DP&#xff09;是一种在数学、计算机科学和经济学中使用的&#xff0c;通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题&#xff0c;它能够将问题…

Spark 读取ES采坑系列

目录 一、使用的插件 二、ES集群和Elasticsearch-hadoop版本问题 三、Elasticsearch-hadoop 和Scala版本以及Spark版本&#xff08;版本不匹配会有各种异常信息 一、使用的插件 <dependency><groupId>org.elasticsearch</groupId><artifactId>elas…

java入坑之类加载器

一、类加载机制 1.1类加载过程 类加载是Java虚拟机将类的字节码数据从磁盘或网络中读入内存&#xff0c;并转换成在JVM中可以被执行的Java类型的过程。类加载器是Java虚拟机的重要组成部分&#xff0c;负责加载和解析类的字节码&#xff0c;将其转换成Java虚拟机中的类对象&am…

nav2 调节纯追踪算法

纯追踪算法 纯追踪基础 The core idea is to find a point on the path in front of the robot and find the linear and angular velocity to help drive towards it. 核心思想是在机器人前方的路径上找到一个点&#xff0c;并找到一个合适的线速度和角速度&#xff0c;以驱…

[量化投资-学习笔记007]Python+TDengine从零开始搭建量化分析平台-布林带

布林带&#xff08;Bollinger Bands&#xff09;也称为布林通道、保力加通道&#xff0c;是由约翰布林格&#xff08;John Bollinger&#xff09;发明的技术分析指标。布林通道通常被用来确认资产价格波动范围。 布林通道是由三条平滑的曲线组成的趋势线图表&#xff0c;中线为…

leetcode刷题 - SQL - 中等

1. 176. 第二高的薪水 筛选出第二大 查询并返回 Employee 表中第二高的薪水 。如果不存在第二高的薪水&#xff0c;查询应该返回 null(Pandas 则返回 None) 。查询结果如下例所示。 666中等的第一题就上强度 强行解法 select max(salary) as SecondHighestSalary from Emp…

深入解析 Redis 分布式锁原理

一、实现原理 1.1 基本原理 JDK 原生的锁可以让不同线程之间以互斥的方式来访问共享资源&#xff0c;但如果想要在不同进程之间以互斥的方式来访问共享资源&#xff0c;JDK 原生的锁就无能为力了。此时可以使用 Redis 来实现分布式锁。 Redis 实现分布式锁的核心命令如下&am…

【Git】如何安装git,项目中使用git上传到远程仓库,使用git中对多人使用出现的版本问题的解决

前言&#xff1a; 一&#xff0c;Git的介绍&#xff0c;安装&#xff0c;与SVN的对比 1.1Git的介绍 Git 是一个开源的分布式版本控制系统&#xff0c;用于敏捷高效地处理任何或小或大的项目。 Git 是 Linus Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控…