Spring Framework——掌握核心注解,优化你的应用程序

文摘   旅游   2023-12-04 00:58   广东  








《论语·卫灵公篇》

工欲善其事,必先利其器!

Dec

02



1

 · Spring IOC常用注解 ·  

1.1、Spring 框架的两大核心功能


Spring 的两大核心是 IOC(控制反转)和 AOP(面向切面编程)。


1.2、Spring IOC介绍


IOC是Inversion Of Control的简写,中文意思是控制反转。

IOC是一种程序设计思想,用于将对象的创建、初始化、销毁等操作交给Spring容器来管理。


1.3、Spring IOC常用注解


@Controller:用于标记一个控制器类,将其自动注册为Spring容器中的一个bean。
@Service:用于标记一个业务逻辑类,将其自动注册为Spring容器中的一个bean。
@Repository:用于标记一个数据访问对象(DAO),将其自动注册为Spring容器中的一个bean。
@Value:用于注入属性值,可以用来注入配置文件中定义的属性值。


在现在SpringBoot应用开发中一般使用@ConfigurationProperties结合prefix的前缀参数将配置文件中的属性与Java类中的字段进行统一的匹配绑定。



注入的属性只有一两个就用@Value很方便,如果非常多则应该使用@ConfigurationProperties结合prefix来简化开发。



1.4、Spring Bean介绍


Spring bean的生命周期有:实例化、属性注入、初始化和销毁这四个阶段。




@Component:用于标记一个类作为Spring组件,自动注册为Spring容器中的一个bean,不能用在控制器类上。





Spring框架支持5种作用域,作用域写在@Scope注解括号里面。


  1. Singleton:这是默认的作用域,Spring IOC容器仅存在一个Bean实例,Bean以单例方式存在,在创建容器时就同时自动创建了一个Bean对象。作用域范围是ApplicationContext中。
  2. Prototype:每次从容器中调用Bean时,都会返回一个新的实例,即每次调用getBean时。作用域返回的是getBean方法调用直至方法结束。
  3. Request:每次HTTP请求都会创建一个新的Bean,作用域范围是每次发起http请求直至拿到相应结果。
  4. Session:首次http请求创建一个实例,作用域是浏览器首次访问直至浏览器关闭。
  5. Global Session:也是每次HTTP请求都会创建一个新的Bean,但是它的范围是全局的,即在整个应用的生命周期内都存在。



@Lazy延迟初始化bean对象,需要用到bean对象时,才会创建bean对象并交给IOC容器管理。是否使用@Lazy,就看程序比较看重的是时间还是空间了。




1.5、Spring管理第三方Bean


@Configuration:用于标记一个配置类,该类可以使用Java配置的方式定义Spring容器中的bean。




@Bean:用于在配置类中定义一个bean,可以使用@Bean来指定bean的名称、依赖关系、初始化方法等。


在@Bean注解中没有指定bean的名称,所以Spring会默认使用方法名作为bean的名称。


在@Bean注解中指定bean的名称,需要注入的时候直接通过@Bean注解括号里面的名称进行注入即可。


1.6、Spring IOC两种实现方式


控制反转有两种实现方式,分别是依赖注入(Dependency Injection)和依赖查找(Dependency Lookup)。


1.6.1、DI依赖注入


@AutoWired:默认按照类型进行注入容器中的bean。是Spring框架提供的。




@Qualifier:和@AutoWired组合使用,当存在多个相同类型的bean时,可以用此注解指定具体注入哪一个bean。




登录框的图片验证码你是如何做的?





@Resource:默认按照名称进行注入容器中的bean。是JavaEE标准注解。





你项目中哪些地方使用到了多线程?



1.6.2、DL依赖查找


依赖查找的不同于依赖注入,他使用容器提供的方法,在运行时根据需要查找容器中的bean。


在Spring框架中可以通过ApplicationContext接口提供的getBean()方法来进行依赖查找。


DL依赖查找有3种查找方式:分别是根据名称、类型和名称及类型这三种方式进行查找。



 · Spring事务常用注解 ·  


事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体,一起向数据库提交或者是撤销操作请求。所以这组操作要么同时成功,要么同时失败。


2.1、@Transactional


@Transactional是Spring框架提供的一个用于声明式事务管理的注解。




使用位置 :业务(service)层的方法上、类上、接口上。

作用 :将当前方法交给spring进行事务管理,方法执行前,开启事务,成功执行完毕,提交事务,出现异常,回滚事务。




2.2、事务管理日志设置

可以在application.yml配置文件中开启事务管理日志,这样就可以在控制台看到和事务相关的日志信息了。





可以通过Grep Console插件对日志进行过滤筛选。





2.3、@Transactional的常见属性


@Transational注解中常见属性分别有rollbackFor(控制异常回滚)、propagation(控制事务传播行为)和isolation(事务隔离级别)。



2.3.1、rollbackFor异常回滚


rollbackFor在默认情况下,只有出现RuntimeException(运行时异常)才回滚异常,如果不是运行时异常不会回滚。





如果想设置出现某种异常回滚,或者出现所有的异常都回滚,可以通过配置@Transactional的rollbackFor属性来控制出现某种异常回滚事务



2.3.2、propagation事务传播行为


propagation事务传播行为:指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制。


propagation常用的属性就是REQUIRED和REQUIRED_NEW这两个属性。




propagation的默认传播行为的REQUIRED,A方法内部调用B方法,如果A方法存在事务,则B方法加入A方法的事务,如果A方法没有事务,则B方法按照自己的事务执行。



REQUIRED_NEW:A方法调用B方法,B方法被调用后不管调用者有没有事务都会创建一个新的事务执行。




大部分情况下都是用REQUIRED默认的传播行为即可。


REQUIRED_NEW应用场景:下订单的业务需要记录日志,不论订单保存成功与否,都需要保证日志记录能够记录成功。这样在记录日志方法的上面加上 @Transactional(propagation = Propagation.REQUIRES_NEW)就能保证记录日志方法不受订单方法的影响。



应用示例:解散部门时,无论是成功还是失败,都要记录日志。



实现步骤:
1.解散部门时,删除部门下的所有员工。
2.记录日志到数据库表中。







REQUIRED_NEW注意点使用Propagation.REQUIRES_NEW可能会导致资源的浪费或并发问题,因为每次调用都会创建一个新的事务。



Propagation其他五种不太常用的属性:


  1. SUPPORTS:A调用B方法时,如果A方法存在一个事务,则B方法加入该事务;否则以非事务的方式执行。
  2. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
  3. MANDATORY:A调用B方法时,A方法必须有事务,B方法加入A事务;否则B方法抛出异常。
  4. NEVER:A调用B方法时,A方法不能有事务;否则B方法抛出异常。
  5. NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。

2.3.3、isolation事务隔离级别


事务的隔离级别有四种,分别是读未提交、读已提交、可重复读和串行化:


  1. TRANSACTION_READ_UNCOMMITTED:读未提交是最低的隔离级别,允许一个事务读取另一个未提交的事务的数据。可能导致脏读、不可重复读和幻读问题。
  2. TRANSACTION_READ_COMMITTED:读已提交是指允许一个事务读取另一个已经提交的事务的数据。可以避免脏读问题,但仍可能出现不可重复读和幻读问题。
  3. TRANSACTION_REPEATABLE_READ:可重复读能保证在同一个事务中多次读取同一数据时,结果始终一致。通过在读取时对数据加锁,避免脏读和不可重复读问题,但仍可能出现幻读问题。
  4. TRANSACTION_SERIALIZABLE:串行化是最高的隔离级别,确保事务串行执行。通过对数据加锁,避免脏读、不可重复读和幻读问题,但可能导致性能问题。



@Transational默认的隔离级别是RANSACTION_REPEATABLE_READ。


2.4、事务失效的场景


  1. @Transational使用在静态方法上会失效:在Spring框架中,@Transational的底层是基于AOP实现的,而AOP的底层是基于动态代理实现的。因此,如果事务被应用在一个静态方法上,事务代理将无法工作,而导致事务失效。
  2. @Transational使用在非public修饰的方法会失效:如果事务使用在非public修饰的方法,事务代理将无法访问该方法,导致事务失效。因此,为了确保事务的有效性,需要确保事务方法是public修饰的类或者接口。
  3. 如果在事务方法中捕获了异常,Spring框架无法感知异常的存在,会导致事务失效。因此,为了确保事务的有效性,应该避免在事务方法中捕获异常,而应该让事务传递给Spring框架进行处理。
  4. @Transational使用在final修饰的方法上会导致事务失效:如果包含事务的方法被final修饰,那么Spring框架将无法对方法进行增强。因此,如果要在Spring框架中管理事务,应该避免在方法上使用final修饰符。
  5. 当一个事务方法调用另一个事务时,如果被调用事务方法的传播行为是Propagation.REQUIRES_NEW,那么当被调用的B事务方法已经提交时,A方法出现异常,B方法的事务将无法回滚。
‍‍‍‍‍

· Spring AOP ·


3.1、AOP介绍


AOP(Aspect Oriented Programming),中文名称叫面向切面编程,它是一种程序设计思想,注重的是方法共性代码的提取,可以看成是OOP的补充,可以在不改动原有代码的前提下,实现方法功能的升级扩展,的底层是基于动态代理实现的。

3.2、AOP的应用场景


权限认证:在方法执行前后进行权限校验,防止非法用户的访问。
日志记录:在方法执行前后进行日志记录,方便后续的分析和调试。
事务处理:通过使用AOP切面编程,可以在方法执行前后进行事务处理,例如在方法执行前开启事务,方法执行后提交事务。这样可以避免在业务逻辑代码中混入事务处理的代码,使代码更加清晰和易于维护。
数据脱敏:政府及事业单位的网站需要通过等保密评才能投入使用,而等保密评中很重要的一项就是对用户的个人信息进行脱敏处理,保护用户的隐私和数据安全。Spring AOP可以通过切面编程的方式,统一对涉及用户敏感信息接口方法进行加密和解密处理,以此保证数据的隐私和安全。

3.3、AOP的核心概念


  1. 连接点:JoinPoint,所有可以被AOP控制的方法都是连接点。
  2. 通知:advice,切面类中要对目标对象进行横向切入增强的代码。
  3. 切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用。在通知类型的注解里面可以通过excution属性值或者@annotation注解这两种方法来定位要增强的方法。
  4. 切面:Aspect,当通知和切入点结合在一起,在类上打上@Aspect注解,就形成了一个切面。通过切面就能描述当前aop程序需要针对哪些原始方法,在什么时候执行什么样的操作。
  5. 目标对象:Target,通知所应用的对象,就是被横向切入增强的对象。


3.4、AOP的五种通知类型


  1. @Around :环绕通知,此注解标注的方法在目标方法前、后都执行一遍。

  2. @Before :前置通知,此注解标注的通知方法在目标方法前被执行。

  3. @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行。

  4. @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行。

  5. @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行。



@Around环绕通知的补充:



@Around环绕通知需要自己调用 ProceedingJoinPoint.proceed() 来让原始方法执行,其他通知不需要考虑目标方法执行。

@Around环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值,否则原始方法执行完毕,是获取不到返回值的。



3.5、@Pointcut抽取重复的表达式



当我们代码中存在大量重复的切入点表达式就可以通过@Pointcut将相同的表达式抽取出来,需要用到时引用该切入点表达式即可。




当切入点方法使用private修饰时,仅能在当前切面类中引用该表达式, 当外部其他切面类中也要引用当前类中的切入点表达式,就需要把private改为public,而在引用的时候,具体的语法为:全类名.方法名()。


3.6、@Order(n)控制通知的执行顺序


在项目开发中,我们定义了多个切面类,而多个切面类中多个切入点都匹配到同一个目标方法。此时,目标方法在运行的时候,这多个切面类当中的这些通知方法都会运行,都会执行就涉及执行顺序先后的问题。





在不同的切面类中,默认按照切面类字母排序执行:


1.目标方法前的通知方法:字母排名靠前的先执行。
2.目标方法后的通知方法:字母排名靠前的后执行。




如果我们想控制通知的执行顺序有两种方式:

1.修改切面类的类名(这种方式非常繁琐、而且不便管理)。
2.使用Spring提供的@Order注解。




使用@Order注解,控制通知的执行顺序:



切面类的执行顺序:
前置通知:数字越小先执行; 
后置通知:数字越小越后执行。










执行结果:



前置通知执行结果:




后置通知执行结果:


3.7、切入点表达式的两种写法


3.7.1、execution根据方法签名来匹配


execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:



访问修饰符?,包名.类名.?和异常? 可以省略。




可以使用通配符描述切入点:




  1. * :单个独立的任意符号,可以通配任意返回值、包名、类名、方法名、任意类型的一个参数,也可以通配包、类、方法名的一部分。

  2. .. :多个连续的任意符号,可以通配任意层级的包,或任意类型、任意个数的参数。




切入点表达式示例:










根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。


3.7.2、@annotation根据注解来匹配


1.自定义注解








@Target声明自定义注解作用于哪里。

ElementType是一个枚举类型,用于指定注解的作用什么目标。


  1. TYPE(用于类、接口或注解类型);

  2. FIELD(用于字段);

  3. METHOD(用于方法);

  4. PARAMETER(用于参数);

  5. CONSTRUCTOR(用于构造函数)。




@Retention声明自定义注解的生命周期保留到什么时候。

RetentionPolicy也是一个枚举类型,用于指定注解的生命周期。


  1. SOURCE(仅在源代码中可用);

  2. CLASS(编译时可用);

  3. RUNTIME(运行时可用)。




2.业务类






3.切面类




3.8、AOP面向切面编程应用案例


1.需求




当访问部门管理和员工管理当中的增、删、改相关功能接口时,将操作日志记录到数据库表中,便于后期数据追踪。


操作日志信息包含:操作人、操作时间、执行方法的全类名、执行方法名、方法运行时参数、返回值、方法执行时长。




2.分析



1.操作日志信息包含返回值和方法执行时长,因此要用环绕通知。
2.业务接口所有的增删改方法名没有共同的前缀或者后缀,使用execution切入点表达式,需要改动的地方有点大。因此当遇到增删改的方法名没有规律时,应该使用@annotation切入点表达式。



3.准备工作



3.1.引入AOP的起步依赖



3.2.创建操作日志表


3.3.创建实体类


3.4.创建Mapper接口




4.编码实现



4.1.创建自定义注解


4.2修改业务实现类,在所有要记录类的增删改业务方法上添加@Log注解。


4.3定义切面类,完成记录操作日志的逻辑



· Spring 框架常见的面试题 ·


4.1、Spring框架的声明周期有哪些?


Spring bean的生命周期包括实例化、属性注入、初始化和销毁四个阶段。


在实例化阶段,Spring通过构造器或反射创建bean的实例对象。


在属性注入阶段,Spring检查实例化的Bean是否存在其他依赖项,如果存在依赖项,则通过读取@AutoWired、@Setter等依赖注入的配置将属性值和依赖项注入到实例中。


在初始化阶段,如果bean实现了InitializingBean接口,Spring容器将调用afterPropertiesSet()方法;如果bean配置中声明了init-method属性,则Spring容器将调用指定的方法。这一阶段标志着bean已经准备就绪并可以供其他bean使用。


在销毁阶段,如果bean实现了DisposableBean接口,Spring容器将在关闭应用程序上下文时调用destroy()方法;如果bean配置中声明了destroy-method属性,则Spring容器将调用指定的方法。这一阶段标志着bean的生命周期结束。


总之,Spring bean的生命周期是指从创建到销毁的整个过程,这个过程包括实例化、属性注入、初始化和销毁四个阶段。在了解这个生命周期的基础上,可以更好地管理和使用Spring bean。



4.2、Spring框架是如何解决循环依赖的问题?


Spring框架通过三级缓存解决了循环依赖的问题。在Spring中,当一个Bean需要注入另一个Bean时,如果存在循环依赖,Spring会尝试使用三级缓存来解决问题。


首先,Spring会尝试从三级缓存中获取已经初始化的Bean,如果找到了合适的Bean,就会直接使用它,而不会重新初始化。


其次,如果三级缓存中没有找到合适的Bean,Spring会尝试从二级缓存中获取原始的Bean对象。如果找到了原始的Bean对象,就会使用它,并对其进行依赖注入。


最后,如果二级缓存中也没有找到原始的Bean对象,Spring会尝试从一级缓存中获取已经初始化并注入依赖的Bean对象。如果找到了这样的Bean对象,就会使用它,并对其进行依赖注入。


通过将Bean的实例化和属性注入这两个过程分离开来,Spring能够解决单循环依赖的问题。但是,对于多实例的注入,需要人工干预处理。例如,可以使用setter注入或构造函数注入等方式来避免循环依赖的问题。


总之,Spring通过三级缓存和分离实例化与属性注入的过程来解决循环依赖的问题。但是,对于多实例的注入问题,仍然需要开发人员自行处理。


 · 往期回顾 ·  

“互联网+”金奖与“挑战杯”特等奖——青春的舞台,美好的回忆

Mysql数据库系统学习笔记

Postman接口测试项目实战

工作中常用的Linux命令——以阿里云ECS服务器为例

Jmeter非GUI压测项目实战笔记

站在龙顶山之巅——观赏深圳湾日落的壮丽奇景

登顶深圳最高峰——梧桐山

中秋国庆长假——探寻故乡的记忆与风景

时光信封——致未来的另一半



吴灿锦

泰伯一百零一世孙,毕业于吉林财经大学。


远方的音讯
梧桐长成凤凰至,人伴贤良品行高!
 最新文章