三天急速通关Spring6

devtools/2025/2/14 3:48:04/

三天急速通关Spring6

  • 0 文章介绍
  • 1 介绍
  • 1.1 背景
  • 1.2 Spring
  • 2 Spring6入门程序
    • 2.1 准备环境
    • 2.2 配置文件
    • 2.3 Tips
  • 3 IoC通过XML注入
    • 3.1 介绍
    • 3.2 Set注入
      • 3.2.1 简单类型注入
    • 3.2.2 注入外部bean与内部bean
    • 3.2.3 其他类型注入
    • 3.3 Constructor注入,P注入,C注入,Util注入
    • 3.4 XML自动化注入
  • 4 Bean的作用域与GoF的工厂模式
    • 4.1 作用域
    • 4.2 工厂模式
  • 5 Bean的实例化方式、生命周期、循环依赖问题
    • 5.1 实例化方式
    • 5.2 生命周期
    • 5.3 循环依赖问题
  • 6 IoC通过注解注入
    • 6.1 入门程序
    • 6.2 声明Bean的标签
    • 6.3 负责注入的标签
    • 6.4 全注解开发
  • 7 面向切面编程AOP
    • 7.1 代理模式
      • 7.1.1 代理模式介绍
      • 7.1.2 静态代理
      • 7.1.3 动态代理
    • 7.2 面向切面编程AOP
      • 7.2.1 AOP七大术语
      • 7.2.2 切点表达式
      • 7.2.3 使用Spring的AOP
  • 8 Spring6中的事务
    • 8.1 注解事务的使用
    • 8.2 事务的属性

0 文章介绍

在倍速观看动力节点杜老师的Spring6教程之后,根据视频内容以及课程笔记进行实践,经过自己的理解并总结后形成这篇学习笔记。文章总共分为九个章节,包括了原教程的十九个章节的大部分知识,学习本文的前置知识需要:JavaSEJDBCMySQLXMLMyBatis。本文所提供的信息和内容仅供参考,作者和发布者不保证其准确性和完整性。

1 介绍

1.1 背景

观察之前在学习MyBatisMVC架构实践的时候,会发现在Controller中使用了ServiceImplServiceImpl使用了DaoImpl,这个时候如果换了数据库,会发现需要在程序中需要新增另一个DaoImpl并修改ServiceImpl,增加可以,但修改不行,违背了OCP原则。同时ControllerDaoImpl这样上层逐层依赖下层的结构也违背了DIPDependence Inversion Principle,依赖倒置原则)。

  • OCPOpen-Closed Principle,开闭原则)的核心思想是对扩展开放,修改关闭。

  • DIPDependence Inversion Principle,依赖倒置原则),核心思想包括两点。

    • 高层模块不应该依赖低层模块,二者都应该依赖于抽象。
    • 抽象不应该依赖于细节,细节应该依赖于抽象。

    理解:ServiceImpl不实例化具体的DaoImpl,只声明一个DaoImpl接口。

IoCInversion of Control,控制反转)本质是一种面向对象的编程思想,利用这种编程思想可以降低程序耦合度,符合依赖倒置原则。

  • 核心思想:将对象的创建权交出去,将对象与对象之间关系的管理权交出去,由第三方容器来负责创建与维护。
  • 实现方式:DIDependency Injection,依赖注入)。
    • 依赖:一种关系,当某个类A需要使用到其他类B来完成任务时称A依赖B
    • 注入:一个过程,依赖关系传递给需要它的类的过程。
    • 实现方式:set方法注入,构造方法注入。

1.2 Spring

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架,为了解决EJB臃肿的设计,以及难以测试等问题,让程序员只需关注核心业务的实现,尽可能的不再关注非业务逻辑代码(事务控制,安全日志等)。

  • Spring的的八大模块:
    在这里插入图片描述

    模块名称功能描述主要用途
    Spring Core提供Spring框架的基础功能,包括依赖注入(DI)、控制反转(IoC)容器、Bean管理等。管理应用程序的依赖关系,实现松耦合的组件开发。
    Spring Context扩展了Spring Core,提供更高级的容器功能,如事件传播、国际化支持、资源加载等。提供应用程序的上下文环境,支持更复杂的场景,如Web应用。
    Spring AOP提供面向切面编程(AOP)的支持,允许将横切关注点(如日志、事务管理、安全)与业务逻辑分离。实现日志记录、事务管理、权限验证等功能,减少代码重复
    Spring DAO提供数据访问对象(DAO)的支持,包括异常转换和模板方法,用于简化数据访问层的开发。统一数据访问异常处理,支持多种数据访问技术(如JDBCHibernateJPA等)。
    Spring ORM集成各种对象关系映射(ORM)框架,如HibernateJPA等,提供统一的ORM支持。简化ORM框架的使用,支持多种ORM工具,方便开发持久层代码。
    Spring Web MVC提供基于ServletWeb开发框架,支持MVC模式,用于构建传统的同步Web应用程序。构建Web应用程序,处理HTTP请求和响应,支持表单处理、视图解析等功能。
    Spring WebFlux提供响应式编程支持,用于构建高性能的非阻塞Web应用程序。构建基于Reactive编程模型的Web应用,提高并发处理能力,支持响应式流规范。
  • Spring的的五大特点:

    • 轻量:完整的Spring框架只有1MB多的JAR,所需的处理开销也微不足道。
    • 控制反转:对象依赖的其它对象通过被动的方式传递进来,而不是对象自己创建或查找。
    • 面向切面:通过分离横切关注点(cross-cutting concerns)来提高代码的模块化程度。横切关注点是指那些与业务逻辑无关,但又需要在多个地方重复使用的功能,难以通过传统的面向对象编程(OOP)方式很好地分离。
    • 容器:管理应用对象的配置和生命周期,在这个意义上它是一种容器。
    • 框架:将简单的组件配置、组合成为复杂的应用。

2 Spring6入门程序

跟着做就好,0 创建Maven Java Web工程 -> 1 依赖导入 -> 2 配置log4j2.xml -> 3 配置Spring6beans.xml -> 4 编写Java代码测试IoC容器。

2.1 准备环境

暂时用不着TomcatMaven的打包方式设置为Jar

  • IntelliJ IDEA:2024.1.7

  • Navicat for MySQL:17.1.2

  • MySQL:8.0.26

  • JDK:17.0.2

  • Maven:3.9.1

2.2 配置文件

  • pom.xml依赖导入
<dependencies><!--spring context依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.1.14</version></dependency><!--junit依赖--><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.12.0-M1</version><scope>test</scope></dependency><!--log4j2的依赖--><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-core</artifactId><version>2.19.0</version></dependency><dependency><groupId>org.apache.logging.log4j</groupId><artifactId>log4j-slf4j2-impl</artifactId><version>2.19.0</version></dependency>
</dependencies>
  • 配置log4j2.xml,在类目录下创建,名字不能修改,配置之后Spring6也会打印详细过程。

    <?xml version="1.0" encoding="UTF-8"?><configuration><loggers><!--level指定日志级别,从低到高的优先级:ALL < TRACE < DEBUG < INFO < WARN < ERROR < FATAL < OFF--><root level="DEBUG"><appender-ref ref="spring6log"/></root></loggers><appenders><!--输出日志信息到控制台--><console name="spring6log" target="SYSTEM_OUT"><!--控制日志输出的格式--><PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss SSS} [%t] %-3level %logger{1024} - %msg%n"/></console></appenders></configuration>
    
  • 配置beans.xml文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="user" class="com.cut.spring6.bean.User"/>
    </beans>
    
  • 创建beans.xmlbean标签指向的com.cut.spring6.bean.User

    java">package com.cut.spring6.bean;/*** 用户类** @author tang* @version 1.0.0* @since 1.0.0*/
    public class User {public User() {System.out.println("User无参构造方法执行了");}@Overridepublic String toString() {return "User{}";}
    }
    
  • 测试程序src/test/java/com/cut/spring6/BeanTest.java

    java">package com.cut.spring6;import com.cut.spring6.bean.User;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.junit.jupiter.api.Test;/*** 测试类* @author tang* @version 1.0.0* @since 1.0.0*/
    public class BeanTest {@Testpublic void testBean() {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");User user = context.getBean("user", User.class);System.out.println(user);}
    }
    
  • 测试输出

    2025-02-09 20:23:53 872 [main] DEBUG org.springframework.context.support.ClassPathXmlApplicationContext - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@142269f2
    2025-02-09 20:23:53 942 [main] DEBUG org.springframework.beans.factory.xml.XmlBeanDefinitionReader - Loaded 1 bean definitions from class path resource [beans.xml]
    2025-02-09 20:23:53 955 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'user'
    User无参构造方法执行了
    User{}Process finished with exit code 0
    

2.3 Tips

  • bean标签的id不能重复,若重复会在spring6启动时报错。
  • spring6底层是通过解析xml中bean的全限定类型,再通过反射动态创建对象。
  • 创建完的对象放在一个Map中,keyidvalue是对象。
  • beans.xml可以创建多个,并且命名不限定,同时ClassPathXmlApplicationContext()可以传递多个xml文件,如果文件路径没在类路径,需要使用FileSystemXmlApplicationContext()
  • spring6除了可以创建自定义类,也可以创建JDK中的类,只要类非抽象并提供了无参构造。
  • getBean()如果传递不存在的id会报错。
  • ApplicationContext还有个超级父接口BeanFactory,在后续还会再提到。

3 IoC通过XML注入

3.1 介绍

IoCInverse of Control,控制反转)是一种思想,降低程序耦合度,提高扩展力,达到OCPOpen-Closed Principle,开闭原则),DIPDependence Inversion Principle,依赖倒置原则)。将对象的创建以及对象与对象之间的维护交给容器管理,通过DIDependency Injection,依赖注入)实现,其中DI又分为两种:

  • Set注入:通过反射机制调用set方法来给属性赋值,让对象之间产生关系。
  • Constructor注入:调用构造方法来给属性赋值,让对象之间产生关系。

3.2 Set注入

3.2.1 简单类型注入

  • 创建com.cut.spring6.bean.UserComplete

    java">package com.cut.spring6.bean;import java.net.URI;
    import java.net.URL;
    import java.time.LocalDate;
    import java.util.Date;
    import java.util.Locale;/*** 包括所有基本数据类型的User类** @author tang* @version 1.0.0* @since 1.0.0*/
    public class UserComplete {private byte age;private short height;private int userId;private long phoneNumber;private float balance;private double salary;private boolean married;private char gender;private Byte ageWrapper;private Short heightWrapper;private Integer userIdWrapper;private Long phoneNumberWrapper;private Float balanceWrapper;private Double salaryWrapper;private Boolean marriedWrapper;private Character genderWrapper;private String name;private Date birthDate;private Season favoriteSeason;private URI personalWebsite;private URL profilePictureUrl;private LocalDate joinDate;private Locale locale;private Class<?> userClassType;... ...
    
  • resource目录下创建user.property文件用于beans.xml里属性赋值

    # Basic Information
    age=30
    height=175
    userId=1001
    phoneNumber=1234567890
    balance=1000.50
    salary=5000.75
    isMarried=true
    gender=M# Wrapper Classes
    ageWrapper=31
    heightWrapper=176
    userIdWrapper=1002
    phoneNumberWrapper=9876543210
    balanceWrapper=2000.25
    salaryWrapper=6000.50
    isMarriedWrapper=false
    genderWrapper=F# String and Date
    name=tang
    # 日期字符串格式不能随便写,必须是Date对象toString()方法执行的结果。
    birthDate=Fri Sep 30 15:26:38 CST 2022
    favoriteSeason=SUMMER
    personalWebsite=https://www.baidu.com
    # spring6之后,会自动检查url是否有效,如果无效会报错。
    profilePictureUrl=https://www.baidu.com
    joinDate=EPOCH
    # java.util.Locale 主要在软件的本地化时使用。
    locale=CHINESE
    userClassType=java.lang.String
    
  • beans.xml的根标签中增加命名context命名空间用于加载user.properties文件

    <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
    "><context:property-placeholder location="user.properties"/>
    
  • 注入

    <!-- 通过外部属性文件给简单类型注入 -->
    <bean id="userCompleteBean" class="com.cut.spring6.bean.UserComplete"><property name="age" value="${age}"/><property name="height" value="${height}"/><property name="userId" value="${userId}"/><property name="phoneNumber" value="${phoneNumber}"/><property name="balance" value="${balance}"/><property name="salary" value="${salary}"/><property name="married" value="${isMarried}"/><property name="gender" value="${gender}"/><property name="ageWrapper" value="${ageWrapper}"/><property name="heightWrapper" value="${heightWrapper}"/><property name="userIdWrapper" value="${userIdWrapper}"/><property name="phoneNumberWrapper" value="${phoneNumberWrapper}"/><property name="balanceWrapper" value="${balanceWrapper}"/><property name="salaryWrapper" value="${salaryWrapper}"/><property name="marriedWrapper" value="${isMarriedWrapper}"/><property name="genderWrapper" value="${genderWrapper}"/><property name="name" value="${name}"/><property name="birthDate" value="${birthDate}"/><property name="favoriteSeason" value="${favoriteSeason}"/><property name="personalWebsite" value="${personalWebsite}"/><property name="profilePictureUrl" value="${profilePictureUrl}"/><property name="joinDate" value="${joinDate}"/><property name="locale" value="${locale}"/><property name="userClassType" value="${userClassType}"/>
    </bean>
    
  • 测试代码

    java">@Test
    public void testBeanBaseType() {System.out.println("-------------testBeanBaseType-------------");ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");UserComplete user = context.getBean("userCompleteBean", UserComplete.class);System.out.println(user);
    }
    
  • 测试结果

    -------------testBeanBaseType-------------
    ... log4j2日志输出 ...
    UserComplete{
    age=30, 
    height=175, 
    userId=1001, 
    phoneNumber=1234567890, 
    balance=1000.5, 
    salary=5000.75, 
    married=true, 
    gender=M, 
    ageWrapper=31, 
    heightWrapper=176, 
    userIdWrapper=1002, 
    phoneNumberWrapper=9876543210, 
    balanceWrapper=2000.25, 
    salaryWrapper=6000.5, 
    marriedWrapper=false, 
    genderWrapper=F, 
    name='tang', 
    birthDate=Sat Oct 01 05:26:38 CST 2022, 
    favoriteSeason=SUMMER, 
    personalWebsite=https://www.baidu.com, 
    profilePictureUrl=https://www.baidu.com, 
    joinDate=1970-01-01, 
    locale=chinese, 
    userClassType=class java.lang.String
    }Process finished with exit code 0
    

3.2.2 注入外部bean与内部bean

  • 创建com.cut.spring6.dao.UserDaocom.cut.spring6.dao.impl.UserDaoImpl

    java">package com.cut.spring6.dao;/*** @author tang* @version 1.0.0* @since 1.0.0*/
    public interface UserDao {
    }
    
    java">package com.cut.spring6.dao.impl;import com.cut.spring6.dao.UserDao;/*** @author tang* @version 1.0.0* @since 1.0.0*/
    public class UserDaoImpl implements UserDao {
    }
    
  • 创建com.cut.spring6.service.UserServicecom.cut.spring6.service.impl.UserServiceImpl

    java">package com.cut.spring6.service;import com.cut.spring6.dao.UserDao;/*** @author tang* @version 1.0.0* @since 1.0.0*/
    public interface UserService {}
    
    java">package com.cut.spring6.service.impl;import com.cut.spring6.dao.UserDao;
    import com.cut.spring6.service.UserService;/*** @author tang* @version 1.0.0* @since 1.0.0*/
    public class UserServiceImpl implements UserService {private UserDao userDao;public UserDao getUserDao() {return userDao;}public void setUserDao(UserDao userDao) {this.userDao = userDao;}@Overridepublic String toString() {return "UserServiceImpl{" +"userDao=" + userDao +'}';}
    }
    
  • beans.xml配置

    <!-- 外部注入 -->
    <bean id="userDaoBean" class="com.cut.spring6.dao.impl.UserDaoImpl"/>
    <bean id="userServiceBeanOuter" class="com.cut.spring6.service.impl.UserServiceImpl"><property name="userDao" ref="userDaoBean"/>
    </bean><!-- 内部注入 -->
    <bean id="userServiceBeanInner" class="com.cut.spring6.service.impl.UserServiceImpl"><property name="userDao"><bean class="com.cut.spring6.dao.impl.UserDaoImpl"/></property>
    </bean>
    
  • 测试

    java">@Test
    public void testBeanOuter() {System.out.println("-------------testBeanOuter-------------");ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");UserServiceImpl userServiceImpl = context.getBean("userServiceBeanOuter", UserServiceImpl.class);System.out.println(userServiceImpl);
    }@Test
    public void testBeanInner() {System.out.println("-------------testBeanInner-------------");ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");UserServiceImpl userServiceImpl = context.getBean("userServiceBeanInner", UserServiceImpl.class);System.out.println(userServiceImpl);
    }
    
  • 测试结果

    -------------testBeanInner-------------
    ... log4j输出 ... 
    UserServiceImpl{userDao=com.cut.spring6.dao.impl.UserDaoImpl@388ffbc2}
    -------------testBeanOuter-------------
    ... log4j输出 ... 
    UserServiceImpl{userDao=com.cut.spring6.dao.impl.UserDaoImpl@7e094740}
    

3.2.3 其他类型注入

  • 级联属性

    <bean id="student" class="com.powernode.spring6.beans.Student"><property name="name" value="张三"/><!--要点1:以下两行配置的顺序不能颠倒--><property name="clazz" ref="clazzBean"/><!--要点2:clazz属性必须有getter方法--><property name="clazz.name" value="高三一班"/>
    </bean>
    
  • 数组,ListSet注入,简单类型用标签,非简单类型用标签外部注入

    <bean id="person" class="com.powernode.spring6.beans.Person"><property name="array"><array><value>鸡排</value><value>汉堡</value></array></property><property name="list"><list><value>鸡排</value><value>汉堡</value></list></property><property name="set"><set><value>鸡排</value><value>汉堡</value><!-- <ref bean="otherBean"/> --></set></property>
    </bean>
    
  • map注入,keyvalue如果是简单类型分别用keyvalue属性,非简单类型分别用key-refvalue-ref属性

    <bean id="peopleBean" class="com.powernode.spring6.beans.People"><property name="addrs"><map><!--如果key不是简单类型,使用 key-ref 属性--><!--如果value不是简单类型,使用 value-ref 属性--><entry key="1" value="北京大兴区"/><entry key="2" value="上海浦东区"/><entry key="3" value="深圳宝安区"/></map></property>
    </bean>
    
  • properties注入

    <bean id="peopleBean" class="com.powernode.spring6.beans.People"><property name="properties"><props><prop key="driver">com.mysql.cj.jdbc.Driver</prop><prop key="url">jdbc:mysql://localhost:3306/spring</prop><prop key="username">root</prop><prop key="password">123456</prop></props></property>
    </bean>
    
  • 空字符串和null注入的处理

    <bean id="vipBean" class="com.powernode.spring6.beans.Vip"><!--空串的第一种方式--><!--<property name="email" value=""/>--><!--空串的第二种方式--><property name="email"><value/></property>
    </bean>
    <!-- null的第一种方式,不给属性赋值 -->
    <bean id="vipBean" class="com.powernode.spring6.beans.Vip" />
    <!-- null的第二种方式 -->
    <bean id="vipBean" class="com.powernode.spring6.beans.Vip"><property name="email"><null/></property>
    </bean>
    
  • 特殊字符的注入,使用<![CDATA[]]>,这个只能使用value标签,不能用value属性。

    <bean id="mathBean" class="com.powernode.spring6.beans.Math"><property name="result"><!--只能使用value标签--><value><![CDATA[2 < 3]]></value></property>
    </bean>
    

3.3 Constructor注入,P注入,C注入,Util注入

  • Constructor注入,可以用index,可以用name,也可以不指定。

    <bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService"><!--第一个参数下标是0--><constructor-arg index="0" ref="orderDaoBean"/><!--第二个参数下标是1--><constructor-arg index="1" ref="userDaoBean"/>
    </bean><bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService"><!--这里使用了构造方法上参数的名字--><constructor-arg name="orderDao" ref="orderDaoBean"/><constructor-arg name="userDao" ref="userDaoBean"/>
    </bean><bean id="orderServiceBean" class="com.powernode.spring6.service.OrderService"><!--没有指定下标,也没有指定参数名字--><constructor-arg ref="orderDaoBean"/><constructor-arg ref="userDaoBean"/>
    </bean>
    
  • P命名空间注入,基于setter方法,beans标签属性增加xmlns:p="http://www.springframework.org/schema/p"

    <bean id="customerBean" class="com.powernode.spring6.beans.Customer" p:name="zhangsan" p:age="20"/>
    
  • C命名空间注入,基于构造方法,beans标签属性增加xmlns:c="http://www.springframework.org/schema/c"

    <bean id="myTimeBean" class="com.powernode.spring6.beans.MyTime" c:_0="2008" c:_1="8" c:_2="8"/>
    
  • Util命名空间注入,让配置复用,可以结合context命名空间读取外部属性配置文件一起使用。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:util="http://www.springframework.org/schema/util"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd"><util:properties id="prop"><prop key="driver">com.mysql.cj.jdbc.Driver</prop><prop key="url">jdbc:mysql://localhost:3306/spring</prop><prop key="username">root</prop><prop key="password">123456</prop></util:properties><bean id="dataSource1" class="com.powernode.spring6.beans.MyDataSource1"><property name="properties" ref="prop"/></bean><bean id="dataSource2" class="com.powernode.spring6.beans.MyDataSource2"><property name="properties" ref="prop"/></bean>
    </beans>
    

3.4 XML自动化注入

自动化注入指的是通过Spring容器自动注入依赖关系,XML自动化注入可以通过名字或者类型进行自动化注入,自动注入以外的其他属性会是默认值。

  • ByName通过名称注入,bean标签添加autowire="byName"属性,实际为set注入。

    <bean id="userService" class="com.powernode.spring6.service.UserService" autowire="byName"/>
    <bean id="userDao" class="com.powernode.spring6.dao.UserDao"/>
    

    iduserDaobeanUserService有相同名称的字段,并且有对应的set方法,在生成iduserServicebean的时候会自动生成UserDao对象赋值给userDao属性。

  • ByType通过类型注入,bean标签添加autowire="byType"属性,实际为set注入。

    <bean id="userService" class="com.powernode.spring6.service.UserService" autowire="byType"/>
    <bean id="userDao" class="com.powernode.spring6.dao.UserDao"/>
    

    只需要UserServiceUserDao类型的字段即可自动注入,多个相同类型会提示报错。

4 Bean的作用域与GoF的工厂模式

这一节主要简单介绍一下bean的作用域,以及三种工厂模式:简单工厂,工厂方法,抽象工厂。

4.1 作用域

Spring框架中,singletonprototype是两种最常见的Bean作用域,通过设置Bean标签的scope属性修改作用域。

  • singleton作用域:整个Spring容器中只有一个实例,无论何时请求该BeanSpring都会返回同一个实例,默认值。
  • prototype作用域:每次调用getBean()方法或注入该Bean时,Spring都会返回一个新的实例。
  • 其他常见作用域:
    • request:一个请求对应一个BeanWEB应用限定。
    • session:一个会话对应一个BeanWEB应用限定。
    • global sessionportlet应用中专用的。如果在ServletWEB应用中使用global session的话,和session一个效果。
    • application:一个应用对应一个BeanWEB应用限定。
    • websocket:一个websocket生命周期对应一个BeanWEB应用限定。
    • 自定义scope:很少使用。

Spring 根据Bean的作用域来选择管理方式:

  • singleton:精确地知道该Bean何时被创建,何时初始化完成,以及何时被销毁;
  • prototype:创建了 Bean 的实例后不再跟踪其生命周期。

如果想要让spring管理自己创建的对象

java">User user = new User();
// 创建 默认可列表BeanFactory 对象
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
// 注册Bean
factory.registerSingleton("userBean", user);
// 从spring容器中获取bean
User userBean = factory.getBean("userBean", User.class);

4.2 工厂模式

工厂模式通常有三种形态:

  • 简单工厂模式(Simple Factory):不属于GOF,又叫静态工厂方法模式,是工厂方法模式的一种特殊实现。

    核心思想: 工厂类可以根据参数的不同通过静态方法返回不同类的实例,被创建的实例通常都具有共同的父类。

    理解: 一个工厂类,一个抽象产品类,多个具体产品类继承于抽象产品类,使用的时候传入参数到工厂类的静态方法,根据参数的不同返回不同的具体产品类对象,可以用同一个抽象产品类接收。

    优点: 不管怎么创建,需要哪个对象时,只需要向工厂索要即可,生产消费分离。

    缺点: 工厂类很难维护,不符合OCP

  • 工厂方法模式(Factory Method):是23种设计模式之一。

    核心思想: 定义一个通用的工厂接口,客户端代码依赖于工厂接口而不是具体的工厂类。这种方式支持多态性,客户端代码可以通过工厂接口调用不同的具体工厂类,从而实现灵活的扩展。

    理解: 为了符合OCP,在简单工厂的基础上再增加一个通用工厂接口,多个具体工厂,具体工厂各自负责生产自己的具体产品,使用的时候,创建不同的具体工厂类对象用同一个工厂接口接收,同时具体工厂类创建的不同具体产品类对象可以用同一个抽象产品类接收。

    优点: 创建对象只需要之道类名称,扩展性高,增加产品只需要增加对应工厂类即可,屏蔽产品的具体实现,调用者只关心产品的接口。

    缺点: 类的数量暴增。
    在这里插入图片描述

    java">public class Client {public static void main(String[] args) {// 使用 ConcreteFactoryA 创建产品Factory factoryA = new ConcreteFactoryA();Product productA = factoryA.factoryMethod();productA.usefulFunction();// 使用 ConcreteFactoryB 创建产品Factory factoryB = new ConcreteFactoryB();Product productB = factoryB.factoryMethod();productB.usefulFunction();}
    }
    
  • 抽象工厂模式(Abstract Factory):是23种设计模式之一。

    核心思想: 一个抽象工厂接口定义了一组创建方法,用于创建多个产品族中的产品对象。具体工厂类实现了这些接口,负责创建特定主题或风格的产品族。

    理解: 通过抽象工厂接口获取具体工厂类对象,再通过具体工厂类创建具体的产品对象。

    优点: 当一个产品族中的多个对象被设计成一起工作时,它能保证客户端始终只使用同一个产品族中的对象。

    缺点: 产品族扩展非常困难,要增加一个系列的某一产品,既要在AbstractFactory里加代码,又要在具体的里面加代码。
    在这里插入图片描述

    java">public class Client {public static void main(String[] args) {// 使用 ConcreteFactory1 创建产品族AbstractFactory factory1 = new ConcreteFactory1();ProductA productA1 = factory1.createProductA();ProductB productB1 = factory1.createProductB();productA1.usefulFunctionA();productB1.usefulFunctionB();// 使用 ConcreteFactory2 创建产品族AbstractFactory factory2 = new ConcreteFactory2();ProductA productA2 = factory2.createProductA();ProductB productB2 = factory2.createProductB();productA2.usefulFunctionA();productB2.usefulFunctionB();}
    }
    

5 Bean的实例化方式、生命周期、循环依赖问题

5.1 实例化方式

Bean提供了多种实例化方式,通常包括4种方式,构造方法,简单工厂,factory-beanFactoryBean。之前一直使用的都是构造方法实例化。

  • 简单工厂:声明一个工厂类,添加get方法,返回Vip对象,再给bean标签添加factory-method属性即可。

    <bean id="vipBean" class="com.powernode.spring6.bean.VipFactory" factory-method="get"/>
    
  • factory-bean:本质还是通过工厂方法模式进行实例化。

    <bean id="orderFactory" class="com.powernode.spring6.bean.OrderFactory"/>
    <bean id="orderBean" factory-bean="orderFactory" factory-method="get"/>
    
  • FactoryBean接口:类实现FactoryBean接口之后,factory-beanfactory-method就不需要指定了。

    java">public class PersonFactoryBean implements FactoryBean<Person> {@Overridepublic Person getObject() throws Exception {return new Person();}@Overridepublic Class<?> getObjectType() {return null;}@Overridepublic boolean isSingleton() {// true表示单例// false表示原型return true;}
    }
    
    <bean id="personBean" class="com.powernode.spring6.bean.PersonFactoryBean"/>
    

    注意FactoryBeanBeanFactory的区别,前者是工厂Bean,用于实例化其它Bean对象的一个Bean,后者是Bean工厂,负责创建Bean对象。

    理解:工厂Bean是给Bean工厂帮忙的,工厂Bean相当于一个特殊车间生产Bean,可以在里面自定义生产逻辑。

5.2 生命周期

生命周期指的是对象从创建开始到最终销毁的整个过程。

  • 五步生命周期

    常见的是五个步骤,
    在这里插入图片描述
    其中:

    • 实例化:反射机制调用 Bean 的无参构造函数来创建 Bean 实例,作用是分配内存并创建对象的基本结构。
    • 属性注入:反射机制调用 Beansetter 方法完成属性值注入。
    • 初始化:完成加载配置、分配资源、执行初始化逻辑等任务,确保 Bean 在被使用之前完全可用。
    • 使用:getBean()使用。
    • 销毁: Spring 容器关闭时,会调用 Bean 的销毁方法,释放资源,作用是清理资源,避免内存泄漏。
  • 七步生命周期

    在初始化前和初始化后添加代码,可以实现Bean后处理器BeanPostProcessor接口,重写beforeafter方法。
    在这里插入图片描述

  • 十步生命周期

    属性注入后,初始化之前还会检查Aware相关接口以及InitializingBean接口,同时在销毁之前还会检查DisposableBean接口。
    在这里插入图片描述
    在这里插入图片描述

5.3 循环依赖问题

循环依赖是指两个或多个 Bean 之间相互依赖,形成了一个闭环,导致 Spring 容器无法正确地实例化对象。

Spring 使用三级缓存来解决循环依赖问题,具体包括:

  • 一级缓存(singletonObjects:存储完全初始化完成的单例 Bean
  • 二级缓存(earlySingletonObjects:存储已经实例化但尚未完成初始化的 Bean
  • 三级缓存(singletonFactories:存储 Bean 的工厂对象,用于提前暴露 Bean 的早期引用。

创建一个 Bean 时,会在实例化后立即将其放入三级缓存中,如果在属性注入阶段发现循环依赖,Spring 会从三级缓存中获取早期引用,从而打破循环。

Spring只能解决setter方法注入的单例bean之间的循环依赖。Spring在创建ClassA对象后,不需要等给属性赋值,直接将其曝光到bean缓存当中。在解析ClassA的属性时,又发现依赖于ClassB,再次去获取ClassB,当解析ClassB的属性时,又发现需要ClassA的属性,但此时的ClassA已经被提前曝光加入了正在创建的bean的缓存中,则无需创建新的的ClassA的实例,直接从缓存中获取即可。从而解决循环依赖问题。

总结:singletonprototype作用域,

  • 对于 构造注入Spring 无法解决任何作用域的循环依赖。
  • 对于 属性注入,只要至少有一个 Beansingleton 作用域,Spring 就可以解决循环依赖问题。

6 IoC通过注解注入

注解的存在主要是为了简化XML的配置,Spring6倡导全注解开发。

6.1 入门程序

  • beans.xml添加context命名空间

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><context:component-scan base-package="com.cut.spring6"/><!-- 多个包的情况下可以用逗号隔开或者直接用公共包 --><!-- <context:component-scan base-package="com.cut.spring6.bean,com.cut.spring6.dao"/> --><!-- <context:component-scan base-package="com.cut.spring6"/> -->
    </beans>
    
  • 编写User

    java">package com.cut.spring6.bean;import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Component;/*** User类** @author tang* @version 1.0.0* @since 1.0.0*/
    @Component("userBean")
    public class User {@Value("Tang")private String name;@Value("18")private Integer age;private String sex;@Value("男")public void setSex() {}public User(@Value("TangCon") String name, @Value("20") Integer age, @Value("公") String sex) {this.name = name;this.age = age;this.sex = sex;}... ...
    
  • 执行结果

    User{name='Tang', age=18, sex='公'}
    

    会发现,先执的有参构造注入,再执行的set注入。

6.2 声明Bean的标签

常见的有四个,其中@Controller、@Service、@Repository这三个注解都是@Component注解的别名。

  • @Componentbean层上使用
  • @Controller:控制器类上使用
  • @Serviceservice类上使用
  • @Repositorydao类上使用

这么多名字是为了可读性以及控制实例化的对象:

<context:component-scan base-package="com.powernode.spring6.bean3" use-default-filters="false"><context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

use-default-filters="true" 表示只要有Component、Controller、Service、Repository中的任意一个注解标注,则进行实例化,反之则不进行实例化,true为默认值。context:include-filter添加例外,上面的代码表示Controller能被实例化,如果上面改为true,那么只有Controller不能实例化。

6.3 负责注入的标签

Bean属性赋值常见的有四个:

  • @Value

    简单数据类型注解,可以出现在属性上、setter方法上、以及构造方法的形参上。但一般推荐不写setter方法,直接在属性上注解。

  • @Autowired@Qualifier

    可以用在属性上、构造方法上、构造方法的参数上、setter方法上,当带参数的构造方法只有一个的时候,这个注解可以省略,默认根据类型注入。如果要根据名称注入的话,需要配合@Qualifier注解一起使用。

    java">// 当UserDao有多个实现类,而Autowired又默认是ByType,所以会查找出错
    // 使用@Qualifier("userDaoForOracle")指明实现类的BeanId,即ByName
    @Autowired
    @Qualifier("userDaoForOracle")
    public void setUserDao(UserDao userDao) {this.userDao = userDao;
    }
    
  • @Resource

    @ResourceJDK扩展包中标准注解,默认根据byName装配,未指定name时使用属性名作为namename找不到则启动byType装配。JDK8以后使用需要引入依赖,

    <dependency><groupId>jakarta.annotation</groupId><artifactId>jakarta.annotation-api</artifactId><version>2.1.1</version>
    </dependency>
    

    @Resource(name="beanId")

6.4 全注解开发

所谓全注解开发,就是不再需要XML参与,声明一个类,替代XML

java">import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScans;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan({"com.powernode.spring6.dao", "com.powernode.spring6.service"})
public class Spring6Configuration {
}

这个时候获得bean需要使用AnnotationConfigApplicationContext()

java">ApplicationContext applicationContext = new AnnotationConfigApplicationContext(Spring6Configuration.class);
UserService userService = applicationContext.getBean("userService", UserService.class);
userService.save();

7 面向切面编程AOP

主要包括了代理模式的介绍以及Spring6中利用代理实现的AOP

7.1 代理模式

7.1.1 代理模式介绍

GoF23种设计模式之一,属于结构型设计模式。主要的作用是为其他对象提供一种代理以控制对这个对象的访问,或保护或隐藏目标对象。

uses
implements
implements
has a
Client
+void main()
Subject
+void request()
RealSubject
+void execRequest()
Proxy
+void beforeRequest()
+void request()

7.1.2 静态代理

很简单,声明一个代理类,实现与目标对象实现的相同接口,将目标对象作为自己的属性。

java">package com.cut.spring6.service.impl;import com.cut.spring6.service.OrderService;/*** 代理的目标对象** @author tang* @version 1.0.0* @since 1.0.0*/
public class OrderServiceImpl implements OrderService {@Overridepublic void generate() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("订单已生成");}@Overridepublic void detail() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("订单信息如下:******");}@Overridepublic String modify() {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("订单信息如下:******");return "modified return";}
}
java">package com.cut.spring6.service;/*** 代理对象** @author tang* @version 1.0.0* @since 1.0.0*/
public class OrderServiceProxy implements OrderService {private final OrderService orderService;public OrderServiceProxy(OrderService orderService) {this.orderService = orderService;}@Overridepublic void generate() {long begin = System.currentTimeMillis();orderService.generate();long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "毫秒");}@Overridepublic void detail() {long begin = System.currentTimeMillis();orderService.detail();long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "毫秒");}@Overridepublic String modify() {long begin = System.currentTimeMillis();orderService.modify();long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "毫秒");return "modified return";}
}
订单已生成
耗时1014毫秒
订单信息如下:******
耗时1008毫秒
订单信息如下:******
耗时1014毫秒
modified returnProcess finished with exit code 0

7.1.3 动态代理

上面的方法虽然能行,但是每个类都声明一个代理类会造成类爆炸的问题,那么有没有那种运行的时候,哪个类需要代理,再创建对应的代理类的方法呢,有的兄弟,有的,像这样的方法有三个:

  • JDK动态代理

    java.lang.reflect.Proxy这个类实现,Proxy.newProxyInstance(类加载器,接口类型,调用处理器对象),三个参数:

    • 类加载器:在内存中生成字节码之后需要加载到内存当中的,所以要指定类加载器,一般用目标对象的类加载器。
    • 接口类型:需要告诉代理对象目标对象实现了哪些接口,直接通过目标对象获取即可。
    • 调用处理器对象:需要一个实现了InvocationHandler接口的对象,这个对象有个方法invoke,这个方法里面可以写处理目标对象本身的逻辑之外的逻辑。
    java">package com.cut.spring6.service;import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;/*** @author tang* @version 1.0.0* @since 1.0.0*/
    public class TimerInvocationHandler implements InvocationHandler {private final Object target;public TimerInvocationHandler(Object target) {this.target = target;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {long begin = System.currentTimeMillis();Object retValue = method.invoke(target, args);long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "毫秒");// 这里需要返回return retValue;}
    }
    
    java">public void testJDKProxy() {OrderService orderService = new OrderServiceImpl();// 三个参数TimerInvocationHandler timerInvocationHandler = new TimerInvocationHandler(orderService);ClassLoader classLoader = orderService.getClass().getClassLoader();Class<?>[] interfaces = orderService.getClass().getInterfaces();OrderService orderServiceProxy = (OrderService) Proxy.newProxyInstance(classLoader, interfaces, timerInvocationHandler);// 调用动态代理类增强之后的方法orderServiceProxy.generate();orderServiceProxy.detail();System.out.println(orderServiceProxy.modify());
    }
    
    订单已生成
    耗时1015毫秒
    订单信息如下:******
    耗时1004毫秒
    订单信息如下:******
    耗时1012毫秒
    modified returnProcess finished with exit code 0
    
  • CGLIB动态代理

    CGLIB既可以代理接口,又可以代理类,底层采用继承的方式实现,所以被代理的目标类不能使用final修饰。

    注意这里需要用到TimerMethodInterceptor而不是上面的TimerInvocationHandler

    需要引入依赖

    <dependency><groupId>cglib</groupId><artifactId>cglib</artifactId><version>3.3.0</version>
    </dependency>
    

    没实现接口的代理目标类

    java">package com.cut.spring6.service;/*** 没实现接口的代理目标对象** @author tang* @version 1.0.0* @since 1.0.0*/
    public class UserService {public void login() {System.out.println("用户正在登录系统....");}public String modify() {System.out.println("用户正在修改密码....");return "modified return";}public void logout() {System.out.println("用户正在退出系统....");}
    }
    
    java">package com.cut.spring6.service;import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;import java.lang.reflect.Method;/*** @author tang* @version 1.0.0* @since 1.0.0*/
    public class TimerMethodInterceptor implements MethodInterceptor {@Overridepublic Object intercept(Object target, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {long begin = System.currentTimeMillis();Object retValue = methodProxy.invokeSuper(target, objects);long end = System.currentTimeMillis();System.out.println("耗时" + (end - begin) + "毫秒");// 一定要返回return retValue;}
    }
    
    java">public void testCGLIBProxy() {// 创建字节码增强器Enhancer enhancer = new Enhancer();// 告诉cglib要继承哪个类enhancer.setSuperclass(UserService.class);// 设置回调接口enhancer.setCallback(new TimerMethodInterceptor());// 生成源码,编译class,加载到JVM,并创建代理对象UserService userServiceProxy = (UserService)enhancer.create();userServiceProxy.login();System.out.println(userServiceProxy.modify());userServiceProxy.logout();
    }
    
    用户正在登录系统....
    耗时6毫秒
    用户正在修改密码....
    耗时0毫秒
    modified return
    用户正在退出系统....
    耗时0毫秒Process finished with exit code 0
    

    这些API只能熟能生巧了。

  • Javassist动态代理

    Javassist是一个开源的分析、编辑和创建Java字节码的类库,之前MyBatis的学习中用到过。

7.2 面向切面编程AOP

将与核心业务无关的代码独立的抽取出来(日志、事务管理、安全等系统服务,被称为交叉业务),形成一个独立的组件,然后以横向交叉的方式应用到业务流程当中的过程被称为AOPSpring6中的实现方式为:JDK动态代理 + CGLIB动态代理技术。Spring在这两种动态代理中灵活切换,如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB

7.2.1 AOP七大术语

contains
contains
matches
uses
applies to
wraps
Aspect
-Advice advice
-Pointcut pointcut
+void applyAdvice()
«interface»
JoinPoint
+void execute()
Pointcut
+String expression
+List matchJoinPoints()
«interface»
Advice
+void apply()
BeforeAdvice
-void before(JoinPoint joinPoint)
AfterAdvice
-void after(JoinPoint joinPoint)
AfterReturningAdvice
-void afterReturning(JoinPoint joinPoint)
AfterThrowingAdvice
-void afterThrowing(JoinPoint joinPoint)
AroundAdvice
-Object around(ProceedingJoinPoint joinPoint)
TargetObject
+void businessLogic()
Proxy
+void proxyMethod()
Weaving
+void weave(Aspect aspect, TargetObject target)
  • 连接点(JoinPoint:可以织入切面的位置,定义了可以被增强的具体位置。

  • 切点(Pointcut:真正织入切面的方法,通过表达式匹配连接点,确定哪些连接点需要被增强。

  • 通知(Advice:又叫增强,是具体要织入的代码,定义了在连接点上执行的具体逻辑。

    通知类型定义
    前置通知(BeforeAdvice目标方法执行之前执行。
    后置通知(AfterAdvice目标方法执行之后执行的逻辑,无论方法是否正常返回或抛出异常,实际就是finally。
    环绕通知(AroundAdvice包裹目标方法的执行,可以在方法执行前后添加逻辑,甚至可以控制方法是否执行。
    异常通知(AfterThrowingAdvice目标方法抛出异常后执行的逻辑。只在方法抛出异常时触发。
    返回通知(AfterReturningAdvice目标方法成功返回后执行的逻辑。只在方法正常返回时触发。
  • 切面(Aspect:将切点和通知组合起来,形成一个完整的增强逻辑模块。

  • 织入(Weaving:通知应用到目标对象上的过程,将切面逻辑织入到目标对象中,从而实现代码的动态增强。

  • 代理对象(Proxy:目标对象被织入通知后产生的新对象,拦截对目标对象方法的调用,执行切面逻辑。

  • 目标对象(TargetObject:被织入通知的对象,目标对象是实际执行业务逻辑的对象,AOP通过代理机制增强其行为。

7.2.2 切点表达式

execution([访问控制权限修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])

  • **访问控制权限修饰符:**可选项,没写就是四个都匹配,写了就只匹配写的。
  • **返回值类型:**必填项,如String,int等类型,`*`代表所有类型。
  • **全限定类名:**可选项,省略就匹配所有类,com.cut..表示匹配com.cut包下所有的类
  • **方法名:**必填项,*表示所有方法,set\*表示所有set开头的方法。
  • **形式参数列表:**必填项,() 表示没有参数,(..) 参数类型和个数随意,(\*) 只有一个参数,(\*, String) 第一个参数类型随意,第二个参数是String。
  • **异常:**可选项,省略时表示任意异常类型。

练习:

  • 1 匹配 com.example.service 包下所有类中以 find 开头的方法。

    java">execution(* com.example.service.*.find*(..))
    
  • 2 匹配 com.example.service 包下所有类中返回值类型为 String 的方法。

    java">execution(String com.example.service.*.*(..))
    
  • 3 匹配 com.example.service 包下所有类中包含 String 类型参数的方法。

    java">execution(* com.example.service.*.*(.., java.lang.String, ..))
    
  • 4 匹配 com.example.service 包下所有类中可能抛出 RuntimeException 的方法。

    java">execution(* com.example.service.*.*(..) throws java.lang.RuntimeException)
    
  • 5 匹配 com.example.service 包下所有类中 public 访问权限的方法。

    java">execution(public * com.example.service.*.*(..))
    
  • 6 匹配 com.example.service 包下所有类中名为 findUser,且参数为 Stringint 的方法。

    java">execution(* com.example.service.*.findUser(String, int))
    
  • 匹配 com.example.service 包及其子包下所有类中以 find 开头的方法,或者 com.example.dao 包及其子包下所有类中以 find 开头的方法。

    java">execution(* com.example.service..*.find*(..)) || execution(* com.example.dao..*.find*(..))
    
  • 匹配 com.example.service 包下所有类中返回值类型为 String 且可能抛出 RuntimeException 的方法。

    java">execution(String com.example.service.*.*(..) throws java.lang.RuntimeException)
    
  • 匹配 com.example.service 包下所有类中 public 访问权限且返回值类型为 String 的方法。

    java">execution(public String com.example.service.*.*(..))
    
  • 匹配 com.example.service 包下所有类中 public 访问权限且包含 String 类型参数的方法。

  • java">execution(public * com.example.service.*.*(.., java.lang.String, ..))
    

7.2.3 使用Spring的AOP

主要有下面两种方式:

  • Spring框架结合AspectJ框架实现的AOP,基于注解方式。
  • Spring框架结合AspectJ框架实现的AOP,基于XML方式。

增加pom.xml依赖,spring-context已经包含了spring-aop,所以只需再引入spring-aspects即可。

<!--spring aspects依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.1.14</version>
</dependency>

beans.xml增加AOP命名空间并开启自动代理,带有@Aspect注解的bean都会生成代理对象。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><context:component-scan base-package="com.cut.spring6"/><!-- 多个包的情况下可以用逗号隔开或者直接用公共包 --><!-- <context:component-scan base-package="com.cut.spring6.bean,com.cut.spring6.dao"/> --><!-- <context:component-scan base-package="com.cut.spring6"/> --><!--开启自动代理--><!--proxy-target-class默认值是false,表示采用jdk动态代理,当没有接口的时候,会自动选择cglib--><aop:aspectj-autoproxy proxy-target-class="false"/></beans>

当然在使用全注解开发也是可以的

java">@Configuration
@ComponentScan("com.powernode.spring6.service")
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Spring6Configuration {
}

切面类的编写,用了个@Pointcut简化了代码,不用每个方法都写一遍切点表达式,@Order()注解中的值越小,切面的同志越优先被执行。

java">package com.cut.spring6.service;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;import java.util.Arrays;/*** @author tang* @version 1.0.0* @since 1.0.0*/
@Component
@Aspect
@Order(2)
public class MyAspect {@Pointcut("execution(* com.cut.spring6.service.UserService.*(..))")public void pointcut() {}@Around("pointcut()")public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {System.out.println("环绕通知开始");Object proceed = proceedingJoinPoint.proceed();System.out.println("环绕通知里的方法执行结果:\t" + proceed);System.out.println("环绕通知结束");return proceed;}@Before("pointcut()")public void beforeAdvice() {System.out.println("前置通知");}@AfterReturning("pointcut()")public void afterReturningAdvice() {System.out.println("后置通知");}@AfterThrowing("pointcut()")public void afterThrowingAdvice() {System.out.println("异常通知");}@After("pointcut()")public void afterAdvice() {System.out.println("最终通知");}}

测试

java">public void testAOP() {ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");UserService userService = context.getBean("userService", UserService.class);System.out.println("---------login start-----------");userService.login();System.out.println("---------login end-----------");System.out.println("---------modify start-----------");System.out.println(userService.modify());System.out.println("---------modify end-----------");System.out.println("---------logout start-----------");userService.logout();System.out.println("---------logout end-----------");
}

输出

---------login start-----------
环绕通知开始
前置通知
用户正在登录系统....
后置通知
最终通知
环绕通知里的方法执行结果:	null
环绕通知结束
---------login end-----------
---------modify start-----------
环绕通知开始
前置通知
用户正在修改密码....
后置通知
最终通知
环绕通知里的方法执行结果:	modified return
环绕通知结束
modified return
---------modify end-----------
---------logout start-----------
环绕通知开始
前置通知
用户正在退出系统....
后置通知
最终通知
环绕通知里的方法执行结果:	null
环绕通知结束
---------logout end-----------Process finished with exit code 0

8 Spring6中的事务

8.1 注解事务的使用

Spring对事务的管理底层实现方式是基于AOP实现的,采用AOP的方式进行了封装,其中

PlatformTransactionManager接口:spring事务管理器的核心接口,spring6中有两个实现:

  • DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。
  • JtaTransactionManager:支持分布式事务管理。

引入命名空间并配置事务管理器,并配置事务注解驱动器

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:tx="http://www.springframework.org/schema/tx"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"><!-- 配置事务管理器 --><bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/></bean><!-- 配置“事务注解驱动器”,开始注解的方式控制事务。 --><tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

service类上或方法上添加@Transactional注解,类上添加该注解,该类中所有的方法都有事务,在某个方法上添加该注解,表示只有这个方法使用事务。

8.2 事务的属性

比较重要的属性:

  • 事务传播行为

    事务的传播行为(Propagation Behavior)定义了在一个事务上下文中调用另一个事务方法时,事务应该如何进行传播。Spring6里一共有七中传播行为。

    java">@Transactional(propagation = Propagation.REQUIRED)
    
    传播行为定义行为适用场景
    REQUIRED加入当前事务,无则新建事务。- 有事务:加入当前事务。
    - 无事务:新建事务。
    通用场景,需事务保证。
    SUPPORTS加入当前事务,无则非事务执行。- 有事务:加入当前事务。
    - 无事务:非事务执行。
    不需事务,但可受益于事务上下文。
    MANDATORY必须加入当前事务,无则抛异常。- 有事务:加入当前事务。
    - 无事务:抛异常。
    必须在事务中执行。
    REQUIRES_NEW新建事务,挂起当前事务。- 有事务:新建事务,挂起当前事务。
    - 无事务:新建事务。
    需独立事务,如数据一致性要求高。
    NOT_SUPPORTED非事务执行,挂起当前事务。- 有事务:挂起当前事务,非事务执行。
    - 无事务:非事务执行。
    不需事务,如读取配置、日志记录。
    NEVER非事务执行,有事务则抛异常。- 有事务:抛异常。
    - 无事务:非事务执行。
    绝对不能在事务中执行。
    NESTED嵌套事务,无则新建事务。- 有事务:启动嵌套事务。
    - 无事务:新建事务。
    需嵌套事务支持,如部分操作独立提交或回滚。
  • 事务隔离级别

    java">@Transactional(isolation = Isolation.READ_COMMITTED)
    
    隔离级别脏读不可重复读幻读
    读未提交
    读提交
    可重复读
    序列化

    读未提交(Read Uncommitted

    • 含义:这是最低的隔离级别。在一个事务读取数据时,其他事务即使未提交的修改也可以被读取到。也就是说,一个事务可以读取到另一个事务尚未提交的数据,这种数据有可能会被回滚,所以可能会导致脏读。
    • 举例:比如事务 A 修改了一条数据但未提交,事务 B 在这个隔离级别下可以读取到事务 A 修改后的数据。如果事务 A 最终回滚了修改,那么事务 B 读取到的数据就是不准确的。

    读提交(Read Committed

    • 含义:一个事务只能读取到其他事务已经提交的数据。避免了脏读,但可能会出现不可重复读的情况。即在一个事务的两次查询之间,其他事务提交了对数据的修改,导致同一个事务读取到的数据前后不一致。
    • 举例:事务 A 在查询某个数据后,事务 B 提交了对该数据的修改。当事务 A 再次查询同一数据时,就会得到修改后的不同结果。

    可重复读(Repeatable Read

    • 含义:保证在一个事务的多次读取过程中,看到的数据是一致的。它避免了脏读和不可重复读,但可能会出现幻读。幻读是指在一个事务中,两次执行相同的查询语句,由于其他事务的插入或删除操作,导致返回的结果集中新增或减少了行。
    • 举例:事务 A 在查询某个范围的数据后,事务 B 插入了一条符合该范围条件的数据。当事务 A 再次查询同一范围的数据时,就会多出一条数据,这就是幻读。

    序列化(Serializable

    • 含义:这是最高的隔离级别。事务的执行顺序是严格按照时间顺序进行的,就像事务是依次串行执行一样,完全避免了脏读、不可重复读和幻读。但是这种隔离级别下并发性能很低,因为事务之间相互等待对方执行完毕才能执行。
    • 举例:如果有多个事务需要操作同一组数据,它们会按照先后顺序排队执行,一个事务执行完所有操作后,下一个事务才能开始,就像人们排队办事一样。

    脏读(Dirty Read

    • 含义:一个事务读取了另一个事务未提交的数据,如果对方事务回滚,那么这个事务读取的数据就是无效的。这种现象在读未提交隔离级别下可能会出现。

    不可重复读(Non - Repeatable Read

    • 含义:在一个事务的两次查询之间,其他事务提交了对数据的修改,导致同一个事务读取到的数据前后不一致。这种现象在读提交隔离级别下可能会出现。

    幻读(Phantom Read

    • 含义:在一个事务中,两次执行相同的查询语句,由于其他事务的插入或删除操作,导致返回的结果集中新增或减少了行。这种现象在可重复读隔离级别下可能会出现。
  • 事务超时

    java">@Transactional(timeout = 10)
    

    表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。

    如果想让整个方法的所有代码都计入超时时间的话,可以在方法最后一行添加一行无关紧要的DML语句。

  • 只读事务

    java">@Transactional(readOnly = true)
    

    只允许select语句执行,delete insert update均不可执行,启动spring的优化策略,提高select语句执行效率。

  • 设置出现哪些异常回滚事务

    java">@Transactional(rollbackFor = RuntimeException.class)
    

    发生RuntimeException异常或该异常的子类异常才回滚。

  • 设置出现哪些异常不回滚事务

    java">@Transactional(noRollbackFor = NullPointerException.class)
    

    发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。

    | 可重复读 | 无 | 无 | |
    | 序列化 | 无 | 无 | 无 |

    读未提交(Read Uncommitted

    • 含义:这是最低的隔离级别。在一个事务读取数据时,其他事务即使未提交的修改也可以被读取到。也就是说,一个事务可以读取到另一个事务尚未提交的数据,这种数据有可能会被回滚,所以可能会导致脏读。
    • 举例:比如事务 A 修改了一条数据但未提交,事务 B 在这个隔离级别下可以读取到事务 A 修改后的数据。如果事务 A 最终回滚了修改,那么事务 B 读取到的数据就是不准确的。

    读提交(Read Committed

    • 含义:一个事务只能读取到其他事务已经提交的数据。避免了脏读,但可能会出现不可重复读的情况。即在一个事务的两次查询之间,其他事务提交了对数据的修改,导致同一个事务读取到的数据前后不一致。
    • 举例:事务 A 在查询某个数据后,事务 B 提交了对该数据的修改。当事务 A 再次查询同一数据时,就会得到修改后的不同结果。

    可重复读(Repeatable Read

    • 含义:保证在一个事务的多次读取过程中,看到的数据是一致的。它避免了脏读和不可重复读,但可能会出现幻读。幻读是指在一个事务中,两次执行相同的查询语句,由于其他事务的插入或删除操作,导致返回的结果集中新增或减少了行。
    • 举例:事务 A 在查询某个范围的数据后,事务 B 插入了一条符合该范围条件的数据。当事务 A 再次查询同一范围的数据时,就会多出一条数据,这就是幻读。

    序列化(Serializable

    • 含义:这是最高的隔离级别。事务的执行顺序是严格按照时间顺序进行的,就像事务是依次串行执行一样,完全避免了脏读、不可重复读和幻读。但是这种隔离级别下并发性能很低,因为事务之间相互等待对方执行完毕才能执行。
    • 举例:如果有多个事务需要操作同一组数据,它们会按照先后顺序排队执行,一个事务执行完所有操作后,下一个事务才能开始,就像人们排队办事一样。

    脏读(Dirty Read

    • 含义:一个事务读取了另一个事务未提交的数据,如果对方事务回滚,那么这个事务读取的数据就是无效的。这种现象在读未提交隔离级别下可能会出现。

    不可重复读(Non - Repeatable Read

    • 含义:在一个事务的两次查询之间,其他事务提交了对数据的修改,导致同一个事务读取到的数据前后不一致。这种现象在读提交隔离级别下可能会出现。

    幻读(Phantom Read

    • 含义:在一个事务中,两次执行相同的查询语句,由于其他事务的插入或删除操作,导致返回的结果集中新增或减少了行。这种现象在可重复读隔离级别下可能会出现。
  • 事务超时

    java">@Transactional(timeout = 10)
    

    表示超过10秒如果该事务中所有的DML语句还没有执行完毕的话,最终结果会选择回滚。

    如果想让整个方法的所有代码都计入超时时间的话,可以在方法最后一行添加一行无关紧要的DML语句。

  • 只读事务

    java">@Transactional(readOnly = true)
    

    只允许select语句执行,delete insert update均不可执行,启动spring的优化策略,提高select语句执行效率。

  • 设置出现哪些异常回滚事务

    java">@Transactional(rollbackFor = RuntimeException.class)
    

    发生RuntimeException异常或该异常的子类异常才回滚。

  • 设置出现哪些异常不回滚事务

    java">@Transactional(noRollbackFor = NullPointerException.class)
    

    发生NullPointerException或该异常的子类异常不回滚,其他异常则回滚。


http://www.ppmy.cn/devtools/158665.html

相关文章

2024主流Web框架横向对比:Gin、Laravel、ThinkPHP、Spring Boot及更多框架的选型指南

引言 随着Web开发的多样化,开发者需要根据项目需求选择合适的框架。本文从性能、开发效率、生态支持、学习曲线等维度,对比Gin(Go)、Laravel(PHP)、ThinkPHP(PHP)、Spring Boot(Java)、Django(Python)、Express.js(Node.js)和ASP.NET Core(C#)七大框架的核心优…

redis 缓存击穿问题与解决方案

前言1. 什么是缓存击穿?2. 如何解决缓存击穿?怎么做?方案1: 定时刷新方案2: 自动续期方案3: 定时续期 如何选? 前言 当我们使用redis做缓存的时候,查询流程一般是先查询redis,如果redis未命中,再查询MySQL,将MySQL查询的数据同步到redis(回源),最后返回数据 流程图 为什…

RocketMQ、RabbitMQ、Kafka 的底层实现、功能异同、应用场景及技术选型分析

1️⃣ 引言 在现代分布式系统架构中&#xff0c;&#x1f4e9;消息队列&#xff08;MQ&#xff09;是不可或缺的组件。它在系统&#x1f517;解耦、&#x1f4c9;流量削峰、⏳异步处理等方面发挥着重要作用。目前&#xff0c;主流的消息队列系统包括 &#x1f680;RocketMQ、&…

网络安全产品架构图 网络安全相关产品

一、信息安全产品分类 背景 美国将网络和信息安全产品分了9类&#xff1a;鉴别、访问控制、入侵检测、防火墙、公钥基础设施、恶意程序代码防护、漏洞扫描、取证、介质清理或擦除。中国公安部将网络和信息安全产品分了7类&#xff1a;操作系统安全、数据库安全、网络安全、病毒…

滑动窗口算法笔记(C++)

滑动窗口算法是一种基于双指针技巧的高效算法, 常用于解决数组或字符串上的一些特定问题. 算法讲解 基本概念 滑动窗口算法可以想象成在一个数组或字符串上有一个固定大小或者可变大小的窗口, 该窗口在数组或字符串上从左到右滑动. 在滑动的过程中, 根据具体问题的要求, 对窗…

深度学习-与OCR结合

光学字符识别&#xff08;OCR&#xff09;旨在将图像中的文本信息转换为计算机可编辑的文本&#xff0c;深度学习技术能够显著提升OCR的准确性和泛化能力。下面为你介绍如何将深度学习与OCR结合&#xff0c;同时给出使用Python和相关库实现的代码示例。 整体思路 结合深度学习…

Qt:Qt窗口

目录 概念 菜单栏 创建菜单栏 在菜单栏中添加菜单 创建菜单项 在菜单项之间添加分割线 工具栏 创建工具栏 设置停靠位置 设置浮动属性 设置移动属性 状态栏 状态栏的创建 显示实时消息 显示永久消息 浮动窗口 浮动窗口的创建 设置停靠位置 对话框 对话框介…

【自学笔记】AIGC基础知识点总览-持续更新

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 AIGC基础知识点总览一、AIGC概述二、AIGC的核心要素三、AIGC的关键技术1. 深度学习算法2. 自然语言处理&#xff08;NLP&#xff09;3. 计算机视觉&#xff08;CV&a…