Spring框架入门:IOC与AOP实战

目录

Spring

Spring介绍

Spring的基本使用

[一、控制反转 IOC](#一、控制反转 IOC)

容器创建对象的方式

1.通过构造方法创建对象

2.通过工厂类创建对象

3.通过注解创建对象

Spring容器对象的创建

1.BeanFactory

2.ApplicationContext

getBean方法获取对象

Spring实例的其他功能使用

1.Bean实例的作用域

2.Bean实例的延迟加载

3.Bean实例的生命周期方法

使用注解方式(推荐)

[使用 XML 标签方式](#使用 XML 标签方式)

Spring测试操作(单元测试)

Spring连接数据库操作

[二、面向切面 AOP](#二、面向切面 AOP)

代理模式

AOP相关专业术语

Spring中AOP的使用

引入AOP资源

方式1.基于XML形式

方式2.基于注解

[AOP 典型使用场景](#AOP 典型使用场景)

三、Spring事务

事务操作

引入相关资源

方式1.基于XML文件

方式2.基于注解形式

[Spring 事务的传播特性](#Spring 事务的传播特性)


Spring

Spring介绍

Spring简介,Spring框架**是一个控制反转(ioc)和面向切面(aop)**的容器框架。

Spring框架的作用

1.降低模块之间的耦合性

2.提供许多工具类,可以简化开发

3.提供面向切面编程

4.提供声明式事务,简化事务的使用

5.可以融合市面上几乎所有的流行框架

Spring的基本使用

1.导入资源jar spring-context

复制代码
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>5.2.25.RELEASE</version>
</dependency>

2.配置spring的核心配置文件

复制代码
<?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="stu" class="com.itdemo.domain.Student">
        <property name="id" value="1"/>
        <property name="name" value="张三"/>
        <property name="sex" value="男"/>
    </bean>
​
​
</beans>

3.在Java类中进行容器的启用 ClassPathXmlApplicationContext

复制代码
public class App {
    public static void main( String[] args ) {
        // 引用Spring容器  ClassPathXmlApplicationContext
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        // 获取对象
        Object object = context.getBean("stu");
        Student student = (Student)object;
​
        System.out.println(student.toString());
​
    }
}

一、控制反转 IOC

**什么是IOC:**应用程序本身不再创建维护对象,依赖外部的spring容器创建和维护对象

**依赖注入DI:**依赖容器往应用程序中注入对象

容器创建对象的方式

1.通过构造方法创建对象

配置文件中使用 <property > 标签创建对象时,默认使用的是无参构造方法,通过setter方法给属性赋值

复制代码
Bean property 'id' is not writable or has an invalid setter method.
​
<bean id="beanName" class="包名.类名">
    <property name="属性名" value="属性值"/>
    <property name="属性名" value="属性值"/>
</bean>

配置文件中使用 <constructor-arg> 标签创建对象时,使用有参构造方法,通过有参构造方法的形参接收值,根据配置的形参个数和形参的名称 匹配 构造方法

复制代码
这里面的 index 对应的是有参构造方法中的参数名
​
<bean id="beanName" class="包名.类名">
    <constructor-arg index="0" value=""/>
    <constructor-arg index="1" value=""/>
    <constructor-arg index="2" value=""/>
</bean>

对象类型的属性 值的注入

复制代码
<!--****************************给一个实体类类型的属性赋值****************************-->
<!--第一种情况,如果容器中存在这种实体的bean,那么可以直接使用ref属性引用-->
    <bean id="stu" class="com.itdemo.domain.Student">
        <property name="id" value="1"/>
        <property name="name" value="张三"/>
        <property name="sex" value="男"/>
        <property name="room" ref="classRoom"/>
    </bean>
    <bean id="classRoom" class="com.itdemo.domain.ClassRoom">
       <property name="id" value="1"/>
       <property name="name" value="java"/>
    </bean>
<!-- 第二种情况,如果容器中不存在这种实体的bean,那么可以直接在property标签内添加-->
    <bean id="stu2" class="com.itdemo.domain.Student">
        <property name="id" value="2"/>
        <property name="name" value="李四"/>
        <property name="sex" value="男"/>
        <property name="room">
            <bean class="com.itdemo.domain.ClassRoom">
                <property name="id" value="2"/>
                <property name="name" value="Python"/>
            </bean>
        </property>
    </bean>

数组类型的属性 值的注入

复制代码
<!--**************************** 给一个数组类型的属性赋值 ****************************-->
    <bean id="classRoomArray" class="com.itdemo.domain.ClassRoom">
        <property name="id" value="3"/>
        <property name="name" value="C++"/>
        <property name="students">
            <array>
                <!--如果容器中已经有数组中存放数据类型的bean,直接ref引用-->
                <ref bean="stu"/>
                <ref bean="stu2"/>
                <!--如果容器中没有数组中存放数据类型的bean可以直接创建-->
                <bean class="com.itdemo.domain.Student">
                    <property name="id" value="3"/>
                    <property name="name" value="王五"/>
                    <property name="sex" value="男"/>
                </bean>
            </array>
        </property>
    </bean>

集合类型的属性 值的注入

List

复制代码
<bean id="tea1" class="com.itdemo.domain.Teacher">
    <property name="id" value="1"/>
    <property name="name" value="Mrs1"/>
    <property name="gender" value="女"/>
    <property name="classRooms">
        <list>
            <!--如果容器中已经有集合中存放数据类型的bean,直接ref引用-->
            <ref bean="cla0"/>
            <!--如果容器中没有集合中存放数据类型的bean,可以嵌套bean标签直接创建-->
            <bean class="com.itdemo.domain.ClassRoom">
                <property name="id" value="4"/>
                <property name="name" value="C#"/>
            </bean>
        </list>
    </property>
</bean>

Set

复制代码
<bean id="tea2" class="com.itdemo.domain.Teacher">
    <property name="id" value="2"/>
    <property name="name" value="Mr2"/>
    <property name="gender" value="男"/>
    <property name="students">
        <set>
            <!--如果容器中已经有集合中存放数据类型的bean,直接ref引用-->
            <ref bean="stu0"/>
            <ref bean="stu1"/>
            <ref bean="stu2"/>
            <!--如果容器中没有集合中存放数据类型的bean,可以嵌套bean标签直接创建-->
            <bean class="com.itdemo.domain.Student">
                <property name="id" value="4"/>
                <property name="name" value="赵六"/>
                <property name="sex" value="男"/>
            </bean>
        </set>
    </property>
</bean>

Map

复制代码
<bean id="tea3" class="com.itdemo.domain.Teacher">
    <property name="id" value="3"/>
    <property name="name" value="Mrs3"/>
    <property name="gender" value="女"/>
    <property name="classRoomMap">
        <map>
            <!--key-value  如果 键-值 对容器中存在,引用,key-ref=""  value-ref=""-->
            <entry key="key1" value-ref="cla0"/>
            <!--key-value  如果键容器中不存在,则嵌套bean标签 key=""  value=""-->
            <entry key="key2">
                <bean class="com.itdemo.domain.ClassRoom">
                    <property name="id" value="5"/>
                    <property name="name" value="Vue"/>
                </bean>
            </entry>
        </map>
    </property>
</bean>

P命名空间注入

p名称空间出现的目地为了简化注入方式,通过无参构造方法,通过属性的setter方法赋值

引入p名称空间约束头

复制代码
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:p="http://www.springframework.org/schema/p"
       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">
​
复制代码
<!--p名称空间属性注入-->
    <bean id="stu3" class="com.itdemo.domain.Student" p:id="5" p:name="张三丰" p:sex="男" p:room-ref="cla1">
    </bean>
​
    <bean id="cla1" class="com.itdemo.domain.ClassRoom">
        <property name="id" value="6"/>
        <property name="name" value="javaEE"/>
    </bean>

使用spring实例化各层对象

dao

Service

controller & app

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="Dao" class="com.itdemo.dao.impl.DaoTestImpl"/>
​
    <!--创建一个业务层对象-->
    <bean id="Service" class="com.itdemo.service.impl.ServiceTestImpl">
        <!--    private DaoTest daoTest;    -->
        <property name="daoTest" ref="Dao"/>
    </bean>
​
    <!--创建一个控制层对象-->
    <bean id="Controller" class="com.itdemo.controller.ControllerTest">
        <!--   private ServiceTest serviceTest;   -->
        <property name="serviceTest" ref="Service"/>
    </bean>
​
</beans>

如果需要新增业务功能,直接修改xml配置文件,ref新的业务实现类就可以

2.通过工厂类创建对象
  • 实例化工厂对象

StudentFactory 学生工厂,用于生产学生对象

复制代码
public class StudentFactory {
    // 创建一个Map集合,用于存放学生信息
    static HashMap<String,Student> studentHashMap = new HashMap<String, Student>();
    // 静态方法,用于创建Student对象
    static {
       Student s1 = new Student("S01", "张三");
       studentHashMap.put("stu1", s1);
​
       Student s2 = new Student("S02", "李四");
       studentHashMap.put("stu2", s2);
​
       Student s3 = new Student("S03", "王五");
       studentHashMap.put("stu3", s3);
​
       Student s4 = new Student("S04", "赵六");
       studentHashMap.put("stu4", s4);
    }
​
    // 创建一个方法,返回 Student 对象
    public Student createStudent(String key) {
       return studentHashMap.get(key);
    }
    
}

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="StuFactory" class="com.itdemo.factory.StudentFactory"/>
​
    <!--  StuFactory.createStudent("stu1")  -->
    <bean id="S1" factory-bean="StuFactory" factory-method="createStudent">
        <constructor-arg name="key" value="stu1"/>
    </bean>
​
</beans>

Test

复制代码
public class App {
    public static void main( String[] args ) {
        // 创建容器对象
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        // 获取对象
        Student student = (Student)context.getBean("S1");
​
        System.out.println(student.toString());
    }
}
  • 静态工厂

StudentFactory

复制代码
public class StudentFactory {
    // 创建一个Map集合,用于存放学生信息
    static HashMap<String,Student> studentHashMap = new HashMap<String, Student>();
    // 静态方法,用于创建Student对象
    static {
       Student s1 = new Student("S01", "张三");
       studentHashMap.put("stu1", s1);
​
       Student s2 = new Student("S02", "李四");
       studentHashMap.put("stu2", s2);
​
       Student s3 = new Student("S03", "王五");
       studentHashMap.put("stu3", s3);
​
       Student s4 = new Student("S04", "赵六");
       studentHashMap.put("stu4", s4);
    }
​
    // 创建一个方法,返回 Student 对象
    public static Student createStudent(String key) {
       return studentHashMap.get(key);
    }
​
}

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">
​
    <!--  StudentFactory.createStudent("stu1")  -->
    <bean id="S1" class="com.itdemo.factory.StudentFactory" factory-method="createStudent">
        <constructor-arg name="key" value="stu1"/>
    </bean>
​
</beans>

Test

复制代码
public class App {
    public static void main( String[] args ) {
        // 创建容器对象
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        // 获取对象
        Student student = (Student)context.getBean("S1");
​
        System.out.println(student.toString());
    }
}
3.通过注解创建对象
  • 将类注册为 Spring Bean

@Component 创建的对象,默认命名规则为类名首字母小写,如果修改名字可以在@Component后面添加

注解 所属层 职责说明
@Controller 表现层(Web 层) 处理 HTTP 请求,接收参数,返回视图或 JSON。通常与 @RequestMapping 配合使用。
@Service 业务逻辑层 封装核心业务逻辑,协调多个 Repository 或其他 Service。
@Repository 数据访问层(DAO) 访问数据库、执行 CRUD 操作。Spring 会自动将底层异常(如 SQLException)转换为 DataAccessException
@Component 通用组件 不属于上述三层的通用工具类、配置类、自定义组件等。
  • @Service

  • @Repository

  • @Controller

这三个注解 都标注了 @Component ,因此它们本质上都是 @Component特化形式(Specializations)

  • 依赖注入(Dependency Injection, DI)
注解 来源 注入方式 是否 Spring 专属 是否需要额外依赖 备注
@Autowired Spring 按类型 ✅ 是 ❌ 否 最常用,支持 @Qualifier
@Qualifier Spring 指定名称 ✅ 是 ❌ 否 必须配合 @Autowired
@Resource JSR-250 (Java) 按名称 ❌ 否 ❌(JDK 自带) 字段名即默认 Bean 名
复制代码
*************************Controller*************************
// @Component
@Controller
public class TestController {
    // 获取业务层对象
    @Autowired
    private TestService testService;
​
    public void test() {
       System.out.println("控制层...");
​
       testService.testService();
    }
}
​
*************************Service*************************
public interface TestService {
    void testService();
}
​
// @Component
@Service
public class TestServiceImpl implements TestService {
    // 获取数据持久层对象
    @Autowired
    private TestDao testDao;
​
    @Override
    public void testService() {
        System.out.println("业务层...");
​
        testDao.testDao();
    }
}
​
*************************Dao*************************
public interface TestDao {
    void testDao();
}
​
// @Component
@Repository
public class TestDaoImpl implements TestDao {
    @Override
    public void testDao() {
        System.out.println("数据持久层...");
    }
}
​

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"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
​
    <context:component-scan base-package="com.itdemo"/>
​
</beans>

Test

复制代码
public class App {
    public static void main( String[] args ) {
        // 启动spring容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        // 获取bean
        TestController testController = (TestController)context.getBean("testController");
        // 调用方法
        testController.test();
    }
}

基本类型,数组类型,集合类型的属性赋值

复制代码
@Value("1")
private int id;
@Value("张三")
private String name;
@Value("20")
private int age;
@Value("#{'Java,Spring,MySQL'.split(',')}")
private String[] courses;
@Autowired
private Course course;
@Value("#{'Spring,SpringMVC,MyBatis'.split(',')}")
private List<String> courseList;
@Value("#{'Java基础,Spring框架,数据库'.split(',')}")
private Set<String> courseSet;
@Value("#{{'course1':'Java基础','course2':'Spring框架','course3':'数据库'}}")
private Map<String, String> courseMap;

Spring容器对象的创建

1.BeanFactory

这是IOC容器的顶级接口 它定义了IOC的最基础的功能, 但是其功能比较简单,一般面向Spring自身使用

在第一次使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化[用的时候再创建] --> 【懒汉设计】

2.ApplicationContext

这是在BeanFactory基础上衍生出的接口,它扩展了BeanFactory的功能,一般面向程序员使用

在容器启动时,一次性创建并加载了所有的Bean [初始化的时候全创建好] --> 【饿汉设计】

复制代码
public static void main( String[] args ) {
    /****************************** 方式一:BeanFactory ********************************/
    XmlBeanFactory xmlBeanFactory = new XmlBeanFactory(new ClassPathResource("application.xml"));
    // 在获取Bean的时候,Spring会自动创建对象,并返回
    Student stu1 = (Student)xmlBeanFactory.getBean("student");
    System.out.println(stu1.toString());
​
    Student stu1_1 = (Student)xmlBeanFactory.getBean("student");
    System.out.println(stu1_1.toString());
​
    System.out.println(stu1 == stu1_1);  // true
​
​
    /***************************** 方式二:ApplicationContext **************************/
    // 在创建Bean的时候,Spring会自动创建对象,并返回
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    Student stu2 = (Student) context.getBean("student");
    System.out.println(stu2.toString());
​
    Student stu2_2 = (Student) context.getBean("student");
    System.out.println(stu2_2.toString());
​
    System.out.println(stu2 == stu2_2);  // true
    
}

两种获取容器对象的方式都是单例模式 ,即创建对象一次,获取对象多次,区别在于创建对象的时机不同

1.BeanFactory :默认采用懒加载(Lazy Initialization) ,即首次调用 getBean() 时才创建 Bean 2.ApplicationContext :默认采用预加载(Eager Initialization) ,即容器启动时就创建所有单例 Bean

特性 BeanFactory ApplicationContext
初始化策略 懒加载(Lazy) 预加载(Eager,默认)
启动速度 较慢(取决于 Bean 数量)
内存占用
getBean方法获取对象

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="student" class="com.itdemo.domain.Student">
        <property name="id" value="1"/>
        <property name="name" value="张三"/>
        <property name="age" value="18"/>
    </bean>
​
    <bean id="student2" class="com.itdemo.domain.Student">
        <property name="id" value="2"/>
        <property name="name" value="李四"/>
        <property name="age" value="19"/>
    </bean>
​
    
</beans>

测试类

复制代码
public static void main( String[] args ) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application2.xml");
    /****************************方式一:根据beanName ***********************************/
    Student stu1 = (Student) context.getBean("student");
    System.out.println(stu1.toString());
​
    /****************************方式二:根据bean.class *********************************/
    // Exception: expected single matching bean but found 2
    Student stu2 = context.getBean(Student.class);
    System.out.println(stu2.toString());
​
    /****************** 方式三:如果有多个相同对象,beanName + bean.class ******************/
    Student stu3 = context.getBean("student2", Student.class);
    System.out.println(stu3.toString());
}

根据beanName 需要进行强转 ,根据bean.class 不需要强转,但是如果有一个bean的多个对象,会出现异常,需要使用beanName + bean.class

Spring实例的其他功能使用


1.Bean实例的作用域
作用域(Scope) 适用环境 实例数量规则 生命周期管理 典型使用场景
singleton 所有环境 整个 Spring 容器中仅一个实例 ✅ 完整 无状态服务类(Service、DAO 等)
prototype 所有环境 每次请求(注入或 getBean())都创建新实例 ❌ 仅创建 有状态对象(如购物车、任务上下文)
request Web 环境 每个 HTTP 请求创建一个实例,同一请求内共享 请求级数据封装(如请求日志上下文)
session Web 环境 每个 HTTP 会话创建一个实例,同一会话内共享 用户会话数据(如登录用户信息)
application Web 环境 每个 ServletContext(Web 应用)中一个实例 Web 应用全局配置
websocket Web 环境(WebSocket) 每个 WebSocket 会话一个实例 WebSocket 会话状态

📝 补充说明:

  • 默认作用域singleton

  • 非 Web 环境 (如普通 Java SE 应用)仅支持 singletonprototype

  • 若在非 Web 环境使用 Web 作用域(如 request),启动时会报错:

    java.lang.IllegalStateException: No Scope registered for scope name 'request'

💡 提示:可通过 @Scope("scopeName") 或 XML <bean scope="..."> 指定作用域。

注解设置scope

复制代码
@Component
@Scope(scopeName = "singleton")
@Scope(scopeName = "prototype")

xml bean标签设置scope

复制代码
<?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">
​
    <!--  singleton:默认,单例模式,即一个bean只创建出一个对象,该bean在整个程序,无论获取多少次都是同一个对象  -->
    <bean id="studentSingleton" class="com.itdemo.domain.Student" scope="singleton">
        <property name="id" value="1"/>
        <property name="name" value="张三"/>
        <property name="age" value="18"/>
    </bean>
​
    <!--  prototype:原型模式。每次获取,都重新创建对象  -->
    <bean id="studentPrototype" class="com.itdemo.domain.Student" scope="prototype">
        <property name="id" value="2"/>
        <property name="name" value="李四"/>
        <property name="age" value="19"/>
    </bean>
    
</beans>

测试类

复制代码
public static void main( String[] args ) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application3.xml");
    // 方式一:scope="singleton" 单例模式
    Student stu1 = context.getBean("studentSingleton", Student.class);
    Student stu2 = context.getBean("studentSingleton", Student.class);
    System.out.println(stu1 == stu2);  // true
    // 方式二:scope="prototype" 原型模式
    Student stu3 = context.getBean("studentPrototype", Student.class);
    Student stu4 = context.getBean("studentPrototype", Student.class);
    System.out.println(stu3 == stu4);  // false
​
}
2.Bean实例的延迟加载

延迟创建对象只针对于单例模式

在bean标签内使用 lazy-init="true" 可以让 ClassPathXmlApplicationContext 创建对象的时机延迟到第一次getBean

复制代码
<?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">
​
    <!-- scope="singleton" 默认  lazy-init="true" 开启延迟创建 -->
    <bean id="student" class="com.itdemo.domain.Student" lazy-init="true">
        <property name="id" value="1"/>
        <property name="name" value="张三"/>
        <property name="age" value="18"/>
    </bean>
​
</beans>

如果是以注解的方式创建bean对象,在上面加 @Lazy 也可以实现延迟创建对象

3.Bean实例的生命周期方法

使用注解方式(推荐)

初始化方法:@PostConstruct 销毁方法:`@PreDestroy`

来源:javax.annotation.PostConstructjavax.annotation.PreDestroy(JSR-250 标准)

📌 示例代码

复制代码
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import org.springframework.stereotype.Component;
​
@Component
public class MyService {
​
    // 构造函数
    public MyService() {
        System.out.println("1. Bean 实例化");
    }
​
    // 初始化方法
    @PostConstruct
    public void init() {
        System.out.println("2. 初始化:@PostConstruct");
    }
​
    // 业务方法
    public void doWork() {
        System.out.println("3. 执行业务逻辑");
    }
​
    // 销毁方法
    @PreDestroy
    public void cleanup() {
        System.out.println("4. 销毁:@PreDestroy");
    }
}
  • 无需在 XML 或配置类中额外声明方法名。

  • 方法必须是 无参、无返回值、非静态

  • 容器关闭时(如调用 context.close()),才会触发 @PreDestroy

  • 仅对单例 Bean 有效prototype 不会调用销毁方法)。


使用 XML <bean> 标签方式

通过 init-methoddestroy-method 属性指定方法名。

📌 示例 Java 类(不能用注解,方法名任意)

复制代码
public class MyService {
​
    public MyService() {
        System.out.println("1. Bean 实例化");
    }
​
    // 自定义初始化方法(方法名任意)
    public void myInit() {
        System.out.println("2. 初始化:myInit()");
    }
​
    public void doWork() {
        System.out.println("3. 执行业务逻辑");
    }
​
    // 自定义销毁方法(方法名任意)
    public void myDestroy() {
        System.out.println("4. 销毁:myDestroy()");
    }
}

📌 XML 配置(applicationContext.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="myService"
          class="com.example.MyService"
          init-method="myInit"
          destroy-method="myDestroy" />
​
</beans>
  • 方法名必须与 init-method / destroy-method 指定的名称一致。

  • 方法必须是 public、无参、无返回值

  • 同样,销毁方法只对单例 Bean 调用

特性 注解方式 (@PostConstruct / @PreDestroy) XML <bean> 方式 (init-method / destroy-method)
标准来源 JSR-250(Java 标准) Spring 特有
是否侵入代码 是(需加注解) 否(配置与代码分离)
方法命名限制 方法名任意,但必须用注解标记 方法名必须与 XML 中指定的一致
适用场景 推荐用于现代 Spring / Spring Boot 项目 适用于 XML 配置为主的传统项目,或无法修改第三方类源码时
能否用于第三方类 ❌(无法加注解) ✅(只需知道方法名即可配置)

补充:Java 配置类中的 @Bean 方式(也属于注解体系)

复制代码
@Configuration
public class AppConfig {
​
    @Bean(initMethod = "myInit", destroyMethod = "myDestroy")
    public MyService myService() {
        return new MyService();
    }
}

这种方式结合了注解的便利性和 XML 的灵活性,适合需要精细控制 Bean 生命周期的场景。

  • 新项目优先使用 @PostConstruct / @PreDestroy:简洁、标准、可读性强。

  • 集成第三方库时 (无法修改源码),使用 XML 或 @Bean(initMethod=...) 指定生命周期方法。

  • 避免混合使用多种方式 (如同时用 @PostConstructinit-method),以免造成混淆或重复执行。

Spring测试操作(单元测试)


导入相关依赖 spring-test应于spring-context版本保持一致

复制代码
<dependencies>
    
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.25.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.25.RELEASE</version>
      <scope>test</scope>
    </dependency>

</dependencies>

示例测试Controller

@RunWith(SpringJUnit4ClassRunner.class) 注解的作用:指定使用 Spring 的 JUnit 4 测试运行器来执行测试,这样可以启用 Spring 的测试框架支持,包括依赖注入和事务管理等功能。

@ContextConfiguration(locations = "classpath:application.xml") 注解的作用: 指定 Spring 配置文件的位置,用于加载应用程序上下文。这里指定从类路径下的 application.xml 文件加载配置,使得测试环境中能够正确初始化 Spring 容器和相关 Bean。

Spring连接数据库操作


导入资源依赖

复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.itdemo</groupId>
  <artifactId>SpringDemo7</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>SpringDemo7</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <!--  Spring版本的统一  -->
    <project.Spring.version>5.2.25.RELEASE</project.Spring.version>
  </properties>

  <dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>${project.Spring.version}</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${project.Spring.version}</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.26</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${project.Spring.version}</version>
      <scope>test</scope>
    </dependency>

  </dependencies>
</project>

实体类 + 数据库

业务代码 Service + Dao

Xml配置文件

这里面的JdbcTemplate 和 ComboPooledDataSource 不会创建多个,因为bean标签默认是单例模式

复制代码
<?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:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--  用于扫描bean  -->
    <context:component-scan base-package="com.itdemo"/>

    <!--  JdbcTemplate 用于Autowired注入jdbcTemplate对象  -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- dataSource 创建一个连接池对象 用于创建配置c3p0对象-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/demo?serverTimezone=UTC"/>
        <property name="user" value="root"/>
        <property name="password" value="123456"/>
    </bean>


</beans>

编写测试类

复制代码
package com.itdemo.service;

import com.itdemo.domain.Account;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Iterator;
import java.util.List;

import static org.junit.Assert.*;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void findAll() {
       List<Account> accountList = accountService.findAll();
       Iterator<Account> iterator = accountList.iterator();
       while (iterator.hasNext()) {
          Account account = iterator.next();
          System.out.println(account.toString());
       }
    }
}

二、面向切面 AOP

**什么是AOP:**面向切面编程不是一种新的编程思想,是面向对象编程OOP的扩展与补充

AOP 的核心思想:在不修改原有业务代码的前提下,统一处理横切关注点(Cross-Cutting Concerns)。

**作用:**把逻辑代码和核心业务代码区分开管理,是业务代码更加清晰,逻辑代码放入到同一个类中管理,方便逻辑进行升级和维护

代理模式


代理模式:代替接口 管理实现类中重写的方法

静态代理代理类需要和 所有实现类 实现相同的接口,在代理类的重写方法中 管理 其他实现类的同名方法

动态代理 :代理类中通过一个方法,管理实现类中所有方法的执行

示例:

静态代理示例

复制代码
package com.itdemo.proxy;

import com.itdemo.service.MathService;

public class MathProxy implements MathService {

    /**
     * 被代理对象
     */
    private MathService mathService;

    public MathProxy() {
    }

    public MathProxy(MathService mathService) {
       this.mathService = mathService;
    }

    @Override
    public int sum(int a, int b) {
       System.out.println("sum开始计算" + a + " + " + b);
       int result = mathService.sum(a, b);
       System.out.println("sum计算结果为:" + result);
       System.out.println("sum计算结束");
       return result;
    }

    @Override
    public int sub(int a, int b) {
       System.out.println("sub开始计算" + a + " - " + b);
       int result = mathService.sub(a, b);
       System.out.println("sub计算结果为:" + result);
       System.out.println("sub计算结束");
       return result;
    }

    @Override
    public double mul(double a, double b) {
       System.out.println("mul开始计算" + a + " * " + b);
       double result = mathService.mul(a, b);
       System.out.println("mul计算结果为:" + result);
       System.out.println("mul计算结束");
       return result;
    }

    @Override
    public double div(double a, double b) {
       System.out.println("div开始计算" + a + " / " + b);
       double result = mathService.div(a, b);
       System.out.println("div计算结果为:" + result);
       System.out.println("div计算结束");
       return result;
    }
}

动态代理

复制代码
package com.itdemo.proxy;

import com.itdemo.service.MathService;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;

public class MathProxy2{

    private MathService mathService;

    public MathProxy2() {
    }

    public MathProxy2(MathService mathService) {
       this.mathService = mathService;
    }

    /***************************** 动态代理 *****************************/
    public MathService proxy(){
       // ClassLoader loader,
       ClassLoader classLoader = mathService.getClass().getClassLoader();
       // Class<?>[] interfaces,
       Class<?>[] interfaces = mathService.getClass().getInterfaces();
       // InvocationHandler h  通过匿名内部类

       MathService mProxy = (MathService) Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
             // 获取方法名称
             String name = method.getName();
             System.out.println(name + "开始计算:" + Arrays.toString(args));

             // 执行方法
             Object result = method.invoke(mathService, args);
             System.out.println(name + "计算结果为:" + result);
             System.out.println(name + "结束计算");

             return result;
          }
       });
       return mProxy;
    }

}

AOP相关专业术语


  • 目标对象(Target):真正干活的原始对象。

  • 连接点(JoinPoint) :程序中能被拦截的地方(Spring 里就是方法)。

  • 代理对象(Proxy) :包装了目标对象的"增强版",调用它来间接执行原方法 + 额外功能。

  • 通知(Advice)要加的额外功能 (比如日志、事务),定义"做什么"。

  • 切入点(Pointcut) :指定"在哪些方法上"加通知(比如所有 service 方法)。

  • 切面(Aspect) :把"在哪加 "(Pointcut)和"加什么"(Advice)合在一起的配置。

  • 织入(Weaving) :把通知"织"进目标对象,生成代理对象的过程

💡 一句话总结: 切面 = 在哪(切入点)+ 干啥(通知);通过代理,把增强功能织入目标对象。

Spring中AOP的使用


依赖 → 开启 AOP → 写通知 → 配切面 → 测试

依赖

在项目中引入 Spring AOP 和 AspectJ 的相关依赖(如 spring-aopspring-aspects),确保 AOP 功能可用。

开启 AOP

在 Spring 配置文件中添加 (注解方式)或使用(XML 方式),启用 AOP 支持。

写通知

创建一个普通 Java 类,在其中编写增强逻辑方法(如前置通知 before()、后置通知 after() 等)。

配切面

通过 XML 或注解,将通知方法与切点(要拦截的目标方法)关联起来,形成"切面"(Aspect)。

测试

调用被拦截的目标方法,验证通知逻辑是否按预期执行(如是否打印日志、是否记录耗时等)。

引入AOP资源
复制代码
<dependencies>
  <!--  基本核心依赖  -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.5.RELEASE</version>
  </dependency>

  <!--  AOP  -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.5.RELEASE</version>
  </dependency>


</dependencies>
方式1.基于XML形式

before + afterReturning + after + afterThrowing 创建切面类,设计通知方法

复制代码
beforepackage com.itdemo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

import java.util.Arrays;

public class MathAop {

	/**
	 * 前置通知 业务代码执行之前执行
	 * @param joinPoint
	 */
	public void before(JoinPoint joinPoint){
		System.out.println("************ 前置方法before ************");
		String methodName = joinPoint.getSignature().getName();
		Object[] args = joinPoint.getArgs();
		System.out.println(methodName + " 方法开始计算: " + Arrays.toString(args));
	}

	/**
	 * 获取返回值通知 业务代码获取到返回值之后执行
	 * @param result
	 */
	public void afterReturning(JoinPoint joinPoint,Object result){
		System.out.println("************ 获取到返回值之后执行result ************");
		String methodName = joinPoint.getSignature().getName();
		System.out.println(methodName + " 方法计算结果为: " + result);
	}

	/**
	 * 后置通知 业务代码执行之后执行
	 * @param joinPoint
	 */
	public void after(JoinPoint joinPoint){
		System.out.println("************ 后置方法after ************");
		String methodName = joinPoint.getSignature().getName();
		System.out.println(methodName + " 方法计算结束");
	}

	/**
	 * 异常通知
	 * @param joinPoint
	 * @param e
	 */
	public void afterThrowing(JoinPoint joinPoint, Exception e){
		System.out.println("************ 异常方法afterThrowing ************");
		String methodName = joinPoint.getSignature().getName();
		System.out.println(methodName + " 方法出现异常");
		String ExceptionMessage = e.getMessage();
		System.out.println("异常信息为: " + ExceptionMessage);
	}
    
}

before + afterReturning + after + afterThrowing 可以合并到一个 around

复制代码
package com.itdemo.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

import java.util.Arrays;

public class MathAroundAop {
    /**
     *  环绕通知
     * @param joinPoint
     * @return
     */
    public Object around(ProceedingJoinPoint joinPoint){
       System.out.println("************ 环绕方法around ************");
       // 获取方法名称 + 参数
       String methodName = joinPoint.getSignature().getName();
       Object[] args = joinPoint.getArgs();
       // 执行方法
       Object result = null;
       try {
          // 前置方法 before + 业务代码 + 获取返回值通知 afterReturning
          System.out.println(methodName + " 方法开始计算: " + Arrays.toString(args));
          result = joinPoint.proceed(args);
          System.out.println(methodName + " 方法计算结果为: " + result);
       } catch (Throwable e) {
          // 异常方法 afterThrowing
          System.out.println(methodName + " 方法出现异常");
          String ExceptionMessage = e.getMessage();
          System.out.println("异常信息为: " + ExceptionMessage);
       } finally {
          // 后置方法 after
          System.out.println(methodName + " 方法计算结束");
       }
       // 返回结果
       return result;
    }


}

before + afterReturning + after + afterThrowing 配置切面信息

复制代码
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--  实例化切面类  -->
    <bean id="aop" class="com.itdemo.aspect.MathAop"/>

    <!--  实例化实现类对象  -->
    <bean id="MSI1" class="com.itdemo.service.impl.MathServiceImpl"/>
    <bean id="MSI2" class="com.itdemo.service.impl.MathServiceImpl2"/>

    <!--  配置AOP,进行切面配置  -->
    <aop:config>
        <!--配置切入点:就是要拦截到哪些方法-->
        <aop:pointcut id="MathAopPointcut" expression="execution(public int com.itdemo.service.impl.MathServiceImpl.sum(int,int))"/>
        <!--配置切面:用来关联切入点和切面,指要对哪些方法进行增强-->
        <aop:aspect ref="aop">
            
            <!-- 配置前置通知 -->
            <aop:before method="before" pointcut-ref="MathAopPointcut"/>
            <!-- 配置异常通知 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="MathAopPointcut" throwing="e"/>
            <!-- 配置返回结果后通知 -->
            <aop:after-returning method="afterReturning" pointcut-ref="MathAopPointcut" returning="result"/>
            <!-- 配置后置通知 -->
            <aop:after method="after" pointcut-ref="MathAopPointcut"/>

        </aop:aspect>
    </aop:config>


</beans>

before + afterReturning + after + afterThrowing 可以合并到一个 around

复制代码
<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--  实例化切面类  -->
    <bean id="aop" class="com.itdemo.aspect.MathAroundAop"/>

    <!--  实例化实现类对象  -->
    <bean id="MSI1" class="com.itdemo.service.impl.MathServiceImpl"/>
    <bean id="MSI2" class="com.itdemo.service.impl.MathServiceImpl2"/>

    <!--  配置AOP,进行切面配置  -->
    <aop:config>
        <!--配置切入点:就是要拦截到哪些方法-->
        <aop:pointcut id="MathAopPointcut" expression="execution(public int com.itdemo.service.impl.MathServiceImpl.sum(int,int))"/>
        <!--配置切面:用来关联切入点和切面,指要对哪些方法进行增强-->
        <aop:aspect ref="aop">

            <!-- 配置环绕通知 -->
            <aop:around method="around" pointcut-ref="MathAopPointcut"/>

        </aop:aspect>
    </aop:config>


</beans>

测试Test

复制代码
package com.itdemo;

import com.itdemo.service.MathService;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main( String[] args ) {
        // 启动spring容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        // 获取MathServiceImpl
        MathService msi1 = context.getBean("MSI1", MathService.class);
        // 调用方法
        msi1.sum(2,5);

        System.out.println();
        System.out.println();

        // 启动spring容器
        ClassPathXmlApplicationContext contextAround = new ClassPathXmlApplicationContext("applicationAround.xml");
        // 获取MathServiceImpl
        MathService aroundMsi1 = contextAround.getBean("MSI1", MathService.class);
        // 调用方法
        aroundMsi1.sum(2,5);
    }
}

运行结果

方式2.基于注解

切面类

复制代码
package com.itdemo.aspect;


import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Aspect  // 声明切面类
public class MathAop {
    /**
     * 定义切点
     */
    @Pointcut("execution(* com.itdemo.service.impl.MathServiceImpl.*(..))")
    public void pointcut(){};

    /**
     * 前置方法
     * @param joinPoint
     */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
       System.out.println("************ 前置方法before ************");
       String methodName = joinPoint.getSignature().getName();
       Object[] args = joinPoint.getArgs();
       System.out.println(methodName + " 方法开始计算: " + Arrays.toString(args));
    }

    /**
     * 获取返回值
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "pointcut()",returning = "result")
    public void afterReturning(JoinPoint joinPoint,Object result){
       System.out.println("************ 获取到返回值之后执行result ************");
       String methodName = joinPoint.getSignature().getName();
       System.out.println(methodName + " 方法计算结果为: " + result);
    }

    /**
     * 后置方法
     * @param joinPoint
     */
    @After("pointcut()")
    public void after(JoinPoint joinPoint){
       System.out.println("************ 后置方法after ************");
       String methodName = joinPoint.getSignature().getName();
       System.out.println(methodName + " 方法计算结束");
    }

    /**
     * 抛出异常方法
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e){
       System.out.println("************ 异常方法afterThrowing ************");
       String methodName = joinPoint.getSignature().getName();
       System.out.println(methodName + " 方法出现异常");
       String ExceptionMessage = e.getMessage();
       System.out.println("异常信息为: " + ExceptionMessage);
    }

}

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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--  启用实例化注解扫描器  -->
    <context:component-scan base-package="com.itdemo"/>

    <!--  AOP注解驱动  -->
    <aop:aspectj-autoproxy/>

</beans>

两种方式的 service + serviceImpl

复制代码
package com.itdemo.service;

// 接口
public interface MathService {
    public int sum(int a,int b);

    public int sub(int a,int b);

    public double mul(double a,double b);

    public double div(double a,double b);
}
复制代码
package com.itdemo.service.impl;

import com.itdemo.service.MathService;
import org.springframework.stereotype.Service;

@Service("MSI1")  // 在方式2中需要声明
public class MathServiceImpl implements MathService {
    @Override
    public int sum(int a, int b) {
       if (a < 0 || b < 0){
          throw new RuntimeException("参数不能小于0");
       }else{
          int result = a + b;
          return result;
       }
    }

    @Override
    public int sub(int a, int b) {
       int result = a - b;
       return result;
    }

    @Override
    public double mul(double a, double b) {
       double result = a * b;
       return result;
    }

    @Override
    public double div(double a, double b) {
       double result = a / b;
       return result;
    }
}

配置切入点Pointcut,要拦截方法的expression表达式书写

复制代码
execution(修饰符? 返回类型 方法名(参数列表) throws异常?)
  1. 优先使用全限定类名 + 精确方法签名 进行调试,确保切点生效;

  2. 逐步放宽条件 :先精确匹配,再用 *.. 泛化;

  3. 注意参数类型 :基本类型 vs 包装类(如 intInteger);

  4. 包路径中的 .. 表示任意层级子包,* 表示当前包下的任意类;

  5. 避免过度宽泛 :如 execution(* *(..)) 会切入所有方法,影响性能

AOP 典型使用场景

场景 说明
🔒 权限校验 在执行敏感操作前,自动检查用户是否有权限
📝 日志记录 方法调用前后自动记录入参、出参、时间等
⏱️ 性能监控 统计方法执行耗时,用于性能分析
💾 事务管理 方法执行前开启事务,成功提交,异常回滚(Spring 声明式事务底层就是 AOP)
🛡️ 异常处理 统一捕获并处理特定异常,避免到处写 try-catch
🔄 缓存增强 方法执行前先查缓存,命中则直接返回,避免重复计算

关键优势:业务代码专注"做什么",横切逻辑由 AOP 统一"何时做、怎么做"。

三、Spring事务

**什么是事务:**SQL执行的最小单元,一组SQL执行要么全部成功,要么全部失败

特征:ACID

事务具备四大核心特性,简称 ACID,确保其可靠性和安全性。

特性 英文全称 含义
A Atomicity 原子性 :事务中的所有操作不可分割,要么全部成功,要么全部失败。
C Consistency 一致性 :事务执行前后,数据库从一个合法状态转换到另一个合法状态
I Isolation 隔离性多个事务并发执行时互不干扰,每个事务都像独立运行一样。
D Durability 持久性事务一旦提交,对数据库的修改将永久保存,即使系统崩溃也不会丢失。

事务操作

引入相关资源
复制代码
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.itdemo</groupId>
  <artifactId>SpringTransactionDemo1</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>SpringTransactionDemo1</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <!-- 基本资源 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.25.RELEASE</version>
    </dependency>

    <!-- AOP资源 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <!-- jdbc资源 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.25.RELEASE</version>
    </dependency>

    <!-- Spring-test -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.25.RELEASE</version>
    </dependency>

    <!-- junit测试 -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
    </dependency>

    <!-- mysql驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.26</version>
    </dependency>

    <!-- druid -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.20</version>
    </dependency>
  </dependencies>
</project>

实体层 + 数据库

dao层

复制代码
package com.itdemo.dao;

public interface UserDao {
    void subtractMoney(String fromUsername, double money);

    void sumbitMoney(String toUsername, double money);
}
复制代码
package com.itdemo.dao.impl;

import com.itdemo.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDaoImpl implements UserDao {

    // jdbcTemplate
    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 转出
     * @param fromUsername
     * @param money
     */
    @Override
    public void subtractMoney(String fromUsername, double money) {
       String sql = "update account set money = money - ? where name = ?";
       jdbcTemplate.update(sql, money, fromUsername);
    }

    /**
     * 转入
     * @param toUsername
     * @param money
     */
    @Override
    public void sumbitMoney(String toUsername, double money) {
       String sql = "update account set money = money + ? where name = ?";
       jdbcTemplate.update(sql, money, toUsername);
    }
}

Service层

复制代码
package com.itdemo.service;

public interface UserService {
    public void transfer(String fromUsername, String toUsername, double money);
}
复制代码
package com.itdemo.service.impl;

import com.itdemo.dao.UserDao;
import com.itdemo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;

    /**
     * 转账
     * @param fromUsername
     * @param toUsername
     * @param money
     */
    @Override
    public void transfer(String fromUsername, String toUsername, double money) {
       // 转出
       userDao.subtractMoney(fromUsername, money);

       // 制造异常 
       int sum = 1 / 0;

       // 接收
       userDao.sumbitMoney(toUsername, money);
    }
}

测试

复制代码
package com.itdemo.service;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;


@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:application.xml")
public class UserServiceTest {

    @Autowired
    private UserService userService;

    @Test
    public void transfer() {
       userService.transfer("李四", "张三", 100);
    }
}

在添加事务管理之前 ,运行后会出现 李四 的钱少了100,但是张三的钱没有增加,出现了异常数据

方式1.基于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"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 启动实例化注解扫描器-->
    <context:component-scan base-package="com.itdemo"/>

    <!-- 实例化JDBCTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 实例化数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!--  实例化数据源事务管理器   管事务 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 管理哪个数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
    <!-- 配置事务方法   定规则 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--  进一步筛选-->
        <tx:attributes>
            <!--  给哪个方法添加-->
            <tx:method name="transfer"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 配置AOP 切方法 -->
    <aop:config>
        <!-- 切入点表达式 -->
        <aop:pointcut id="pt" expression="execution(* com.itdemo.service.impl.*.*(..))"/>
        <!-- 关联  织进去 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
    </aop:config>

</beans>

管事务 → 定规则 → 切方法 → 织进去

用于定义事务属性(如传播行为、隔离级别、超时时间等)

属性 说明 默认值
name 必填!指定要应用事务的方法名(支持通配符 *
propagation 事务传播行为 REQUIRED
isolation 事务隔离级别 DEFAULT
timeout 事务超时时间(秒) -1(永不超时)
read-only 是否只读事务(优化性能) false
rollback-for 触发回滚的异常类(全限定名,多个用逗号分隔) 运行时异常(RuntimeException 及其子类)
no-rollback-for 触发回滚的异常类(即使抛出也不回滚)
方式2.基于注解形式
复制代码
<?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"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 启动实例化注解扫描器-->
    <context:component-scan base-package="com.itdemo"/>

    <!-- 实例化JDBCTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 实例化数据源 -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>

    <!--  实例化数据源事务管理器  -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 管理哪个数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--  开启注解式事务 关联一下数据源事务管理器 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

在要进行事务管理的方法上添加注解 @Transactional

复制代码
@Transactional
@Override
public void transfer(String fromUsername, String toUsername, double money) {
    // 转出
    userDao.subtractMoney(fromUsername, money);

    int sum = 1 / 0;

    // 接收
    userDao.sumbitMoney(toUsername, money);
}

Spring 事务的传播特性

传播行为 当前无事务 当前有事务
REQUIRED 创建新事务 加入当前事务
SUPPORTS 非事务执行 加入当前事务
MANDATORY 抛出异常 加入当前事务
REQUIRES_NEW 创建新事务 挂起当前,创建新事务
NOT_SUPPORTED 非事务执行 挂起当前,非事务执行
NEVER 非事务执行 抛出异常
NESTED 创建新事务 在嵌套事务中执行

使用建议

  • 默认使用 REQUIRED

  • 需要独立提交/回滚逻辑(如日志)用 REQUIRES_NEW

  • 只读查询可考虑 SUPPORTS 或不加事务。

  • 谨慎使用 NESTED,需了解底层数据库对 savepoint 的支持情况。

相关推荐
shengjk11 小时前
一文搞定 NTP 三大核心问题:是什么、为何不准、来自何方
后端
Arva .1 小时前
读写锁 (ReadWriteLock)
java·开发语言
SnrtIevg1 小时前
Spring Modulith :构建模块化单体应用
后端
LSTM971 小时前
用 Python 自动化编辑 Word 文档
后端
摇滚侠1 小时前
2025最新 SpringCloud 教程,接口测试,本地事务,打通链路,笔记65,笔记66,笔记67
笔记·spring·spring cloud
毕设源码-郭学长1 小时前
【开题答辩全过程】以 基于java的校园安全管理系统为例,包含答辩的问题和答案
java·开发语言
王中阳Go1 小时前
手把手教你用 GoFrame 实现 RBAC 权限管理,从零到一搞定后台权限系统
后端
苏三说技术1 小时前
try...catch真的影响性能吗?
后端
青梅主码1 小时前
麦肯锡发布最新报告《职场超级代理:赋能人们释放 AI 的全部潜力》:如何用 AI 赋能员工,释放无限潜力?
后端