Spring

Stone大约 50 分钟

Spring

Spring 是一个开源的 Java 应用框架,旨在简化企业级 Java 开发。Spring 框架提供了广泛的功能来支持应用开发,包括但不限于控制反转(IoC)容器、面向切面编程(AOP)、数据访问、事务管理、消息传递、Web 集成等。以下是 Spring 的一些主要特点和简介:

  • 控制反转(IoC)和依赖注入(DI):
    • Spring 容器负责管理应用程序组件的生命周期和依赖关系。
    • 通过依赖注入(DI),Spring 容器在创建对象时自动注入它们所依赖的其他对象。
  • 面向切面编程(AOP):
    • AOP 允许开发者定义横切关注点,如日志、事务管理等,并将它们与业务逻辑分离。
    • Spring AOP 通过代理模式实现,支持在运行时动态地将代码切入到类的指定方法、指定位置上。
  • 数据访问:
    • Spring 提供了与多种数据库交互的模板,如 JdbcTemplate、HibernateTemplate 等,以简化数据库操作。
    • Spring Data 项目提供了对多种 NoSQL 数据库的集成支持,如 MongoDB、Redis 等。
  • 事务管理:
    • Spring 提供了声明式事务管理,允许开发者通过注解或 XML 配置来管理事务。
    • Spring 支持编程式事务管理,也支持与多种事务管理器(如 Hibernate 事务管理器、JDBC 事务管理器等)的集成。
  • Web 集成:
    • Spring MVC 是 Spring 框架的一个模块,用于构建 Web 应用程序。它提供了模型-视图-控制器(MVC)架构的实现,并支持多种视图技术(如 JSP、Thymeleaf 等)。
    • Spring Boot 进一步简化了 Web 应用程序的搭建和开发,通过自动配置和约定优于配置(Convention Over Configuration)的原则,快速启动和运行 Web 应用程序。
  • 消息传递:
    • Spring 提供了对 JMS(Java Message Service)的支持,用于在企业应用程序中传递消息。
    • Spring Integration 项目为更复杂的消息传递场景提供了高级支持,包括路由、过滤、转换等功能。
  • 测试:
    • Spring 提供了对 JUnit 的集成支持,并提供了自己的测试框架 Spring Test,用于简化单元测试和集成测试。
    • Spring Boot Test 进一步简化了 Spring Boot 应用程序的测试过程。
  • 扩展性:
    • Spring 框架是一个可扩展的平台,允许开发者通过实现自定义的 BeanFactory、ApplicationContext 等接口来扩展其功能。
    • Spring 社区提供了大量的第三方库和插件,用于增强 Spring 框架的功能。
  • 社区支持:
    • Spring 拥有庞大的用户社区和丰富的文档资源,为开发者提供了大量的帮助和支持。
    • Spring 项目持续更新和演进,不断引入新的功能和改进。

总之,Spring 是一个功能强大、易于使用且广泛应用的 Java 应用框架,它简化了企业级 Java 开发的过程,提高了开发效率和代码质量。

Spring 体系结构:

image-20221216132513449

Start

IoC 入门案例

在 IDEA 中创建 Maven 项目,在 pom.xml 文件中引入 Spring 依赖:

<?xml version="1.0" encoding="UTF-8"?>

<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.itheima</groupId>
  <artifactId>spring_01_quickstart</artifactId>
  <version>1.0-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>

创建接口和实现类:

public interface BookDao {
    public void save();
}

public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao save ...");
    }
}

resources 目录下创建 Bean 的配置文件 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 标签表示配置 bean
    id 属性标示给 bean 起名字
    class 属性表示给 bean 定义类型-->
    <bean id="bookDao" class="net.stonecoding.dao.impl.BookDaoImpl"/>

</beans>

创建容器,从容器中获取 Bean,然后调用 Bean 的方法:

public class App {
    public static void main(String[] args) {
        // 获取 IoC 容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 获取 bean(根据 bean 配置 id 获取)
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
    }
}
输出:
book dao save ...

DI 入门案例

增加接口和实现类,并在实现类中引用其他类:

public interface BookService {
    public void save();
}

public class BookServiceImpl implements BookService {
    private BookDao bookDao;

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

在配置文件 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="bookDao" class="com.itheima.dao.impl.BookDaoImpl"/>

    <bean id="bookService" class="com.itheima.service.impl.BookServiceImpl">
        <!--7.配置 server 与 dao 的依赖关系-->
        <!--property 标签表示配置当前 bean 的属性
        name 属性表示配置哪一个具体的属性
        ref 属性表示参照哪一个 bean-->
        <property name="bookDao" ref="bookDao"/>
    </bean>

</beans>

创建容器,从容器中获取 Bean,然后调用 Bean 的方法,此时会再去调用注入的类的方法:

public class App2 {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}
输出:
book service save ...
book dao save ...

IoC

核心概念:

  • Spring IoC 容器:Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置它们,并管理他们的整个生命周期从创建到销毁。这些对象被称为 Spring Beans。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。
  • IOC(Inversion of Control)控制反转:使用对象时,由主动 new 产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称之为控制反转。Spring 提供了一个容器,称为 IOC 容器,用来充当 IOC 思想中的“外部”。IOC 容器负责对象的创建、初始化等一系列工作。
  • DI(Dependency Injection)依赖注入:在容器中建立 Bean 与 Bean 之间的依赖关系的整个过程,称之为依赖注入。依赖注入是一种实现控制反转的技术,通过它,我们可以将对象的依赖关系从硬编码的方式转变为在运行时由 Spring 容器注入的方式。
  • 解耦:Spring 框架的主要目标之一是充分解耦。通过使用 IOC 容器管理 Bean 以及在 IOC 容器内将有依赖关系的 Bean 进行关系绑定(DI),可以实现对象之间的松耦合,从而提高代码的可重用性和可维护性。

Spring 配置 Bean:

  • 配置形式:基于 XML 文件
  • 配置方式:通过全类名(反射)

Spring 支持 3 种依赖注入的方式:

  • 属性注入:通过 setter 方法注入 Bean 的属性或依赖的对象,使用 <property> 元素,使用 name 属性指定 Bean 的属性名称,value 属性或 <value> 子节点指定属性值。属性注入是实际应用中最常用的注入方式。
  • 构造器注入:通过构造方法注入 Bean 的属性或者依赖的对象,它包装了 Bean 实例在实例化后就可以使用。构造器注入在 <constructor-arg> 元素里声明属性,<constructor-arg> 没有 name 属性。使用构造器注入属性值可以指定参数的位置和参数的类型,以区分重载的构造器。
  • 工厂方法注入(很少使用,不推荐)

使用场景:

  • 强制依赖使用构造器进行,使用 setter 注入有概率不进行注入导致null对象出现
    • 强制依赖指对象在创建的过程中必须要注入指定的参数
  • 可选依赖使用 setter 注入进行,灵活性强
    • 可选依赖指对象在创建过程中注入的参数可有可无
  • Spring 框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  • 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用 setter 注入完成可选依赖的注入
  • 实际开发过程中还要根据实际情况分析,如果受控对象没有提供 setter 方法就必须使用构造器注入
  • 自己开发的模块推荐使用 setter 注入

级联属性赋值

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

	<!-- 
		配置Bean
		id:唯一标识容器中的Bean
		class:bean的全类名,通过反射的方式在IoC容器中创建Bean,要求Bean中必须有无参数的构造器
	 -->
	<bean id="helloWorld" class="com.stone.spring.HelloWorld">
		<property name="message" value="Hello World!"></property>
	</bean>
	
	<!-- 使用构造器注入属性值可以指定参数的位置和参数的类型,以区分重载的构造器 -->
	<bean id="car" class="com.stone.spring.Car">
		<constructor-arg value="Audi" index="0"></constructor-arg>
		<constructor-arg value="ShangHai" index="1"></constructor-arg>
		<constructor-arg value="300000" type="double"></constructor-arg>
	</bean>
	
	<bean id="car2" class="com.stone.spring.Car">
		<constructor-arg value="BMW" type="java.lang.String"></constructor-arg>
		<!-- 如果字面值包含特殊字符可以使用<![CDATA[]]>包裹起来 -->
		<!-- 属性值也可以使用<value>子节点进行配置 -->
		<constructor-arg type="java.lang.String">
			<value><![CDATA[<ShangHai>]]></value>
		</constructor-arg>
		<constructor-arg type="int">
			<value>240</value>
		</constructor-arg>
	</bean>
	
	<bean id="person" class="com.stone.spring.Person">
		<property name="name" value="Tom"></property>
		<property name="age" value="24"></property>
		<!-- 可以使用<property>的ref属性建立Bean之间的引用关系 -->
		<!--  <property name="car" ref="car2"></property>  -->
		<!-- 内部Bean,不能被外部应用,只能在内部使用 -->
		<property name="car">
			<bean class="com.stone.spring.Car">
				<constructor-arg value="Ford"></constructor-arg>
				<constructor-arg value="ChangAn"></constructor-arg>
				<constructor-arg value="200000" type="double"></constructor-arg>
			</bean>
		</property>
	</bean>
	
	<bean id="person2" class="com.stone.spring.Person">
		<constructor-arg value="Jerry"></constructor-arg>
		<constructor-arg value="25"></constructor-arg>
		<constructor-arg ref="car"></constructor-arg>
		<!-- 为级联属性赋值。注意:属性需要先初始化,后才可以为级联属性赋值。否则会有异常。和Struts2不同。 -->
		<property name="car.maxSpeed" value="240"></property>
		<!-- 测试复制null -->
		<!--  
		<constructor-arg><null/></constructor-arg>
		-->
	</bean>
</beans>
package com.stone.spring;
public class Car {
	private String brand;
	private String corp;
	private double price;
	private int maxSpeed;
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		this.brand = brand;
	}
	public String getCorp() {
		return corp;
	}
	public void setCorp(String corp) {
		this.corp = corp;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public int getMaxSpeed() {
		return maxSpeed;
	}
	public void setMaxSpeed(int maxSpeed) {
		this.maxSpeed = maxSpeed;
	}
	public Car(String brand, String corp, double price) {
		super();
		this.brand = brand;
		this.corp = corp;
		this.price = price;
	}
	public Car(String brand, String corp, int maxSpeed) {
		super();
		this.brand = brand;
		this.corp = corp;
		this.maxSpeed = maxSpeed;
	}
	@Override
	public String toString() {
		return "Car [brand=" + brand + ", corp=" + corp + ", price=" + price + ", maxSpeed=" + maxSpeed + "]";
	}
}
package com.stone.spring;

public class Person {
	
	private String name;
	private int age;
	private Car car;
	public Person() {
		super();
	}
	public Person(String name, int age, Car car) {
		super();
		this.name = name;
		this.age = age;
		this.car = car;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
	public Car getCar() {
		return car;
	}
	public void setCar(Car car) {
		this.car = car;
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
	}
}
package com.stone.spring;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloTest {
	public static void main(String[] args) {
		//1、创建Spring的IoC容器
		//ApplicationContext:代表IoC容器
		//ClassPathXmlApplicationContext:是ApplicationContext接口的实现类,该实现类从类路径下加载配置文件
		ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
		
		//2、从IoC容器中获取Bean实例
		//利用id定位到IoC容器中的Bean
		HelloWorld helloWorld = (HelloWorld) context.getBean("helloWorld");
		
		//利用类型返回IoC容器中的Bean,但要求IoC容器中只能有一个该类型的Bean
		//HelloWorld helloWorld = context.getBean(HelloWorld.class);
		
		helloWorld.getMessage();
		
		Car car = (Car) context.getBean("car");
		System.out.println(car);
		
		car = (Car) context.getBean("car2");
		System.out.println(car);
		
		Person person = (Person) context.getBean("person2");
		System.out.println(person);
	}
}

集合属性

	<!-- 测试如何配置集合属性 -->
	<bean id="person3" class="com.stone.spring.collection.Person">
		<property name="name" value="Mike"></property>
		<property name="age" value="27"></property>
		<property name="cars">
			<!-- 使用<list>节点为List类型的属性赋值 -->
			<list>
				<ref bean="car"/>
				<ref bean="car2"/>
			</list>
		</property>
	</bean>

Map属性

	<!-- 配置Map属性值 -->
	<bean id="newPerson" class="com.stone.spring.collection.NewPerson">
		<property name="name" value="Rose"></property>
		<property name="age" value="28"></property>
		<property name="cars">
			<!-- 使用<map>节点和map的<entry>子节点配置map类型的成员变量 -->
			<map>
				<entry key="AA" value-ref="car"></entry>
				<entry key="BB" value-ref="car2"></entry>
			</map>
		</property>
	</bean>

Properties属性

package com.stone.spring.collection;
import java.util.Properties;
public class DataSource {
	private Properties properties;

	public Properties getProperties() {
		return properties;
	}

	public void setProperties(Properties properties) {
		this.properties = properties;
	}

	@Override
	public String toString() {
		return "DataSource [properties=" + properties + "]";
	}
}
	<!-- 配置Properties属性值 -->
	<bean id="dataSource" class="com.stone.spring.collection.DataSource">
		<property name="properties">
			<!-- 使用<props>和<prop>子节点来为Properties属性赋值 -->
			<props>
				<prop key="user">root</prop>
				<prop key="password">123456</prop>
				<prop key="jdbcUrl">jdbc:mysql:///test</prop>
				<prop key="driverClass">com.mysql.jdbc.Driver</prop>
			</props>
		</property>
	</bean>

使用util命名空间

需要先添加util命名空间:

image-20221216154707125

	<!-- 配置独立的集合Bean,以供多个Bean进行引用,需要导入util命名空间 -->
	<util:list id="cars">
		<ref bean="car"/>
		<ref bean="car2"/>
	</util:list>
	
	<bean id="person4" class="com.stone.spring.collection.Person">
		<property name="name" value="Jack"></property>
		<property name="age" value="29"></property>
		<property name="cars" ref="cars"></property>
	</bean>

使用p命名空间

需要先添加p命名空间:

image-20221216154913895

	<!-- 通过p命名空间为Bean的属性赋值 ,需要先导入p命名空间-->
	<bean id="person5" class="com.stone.spring.collection.Person"
		p:name="Queue" p:age="30" p:cars-ref="cars"></bean>

自动装配

Spring IoC 容器可以自动装配 Bean,需要做的仅仅是在 <bean>autowire 属性里指定自动装配的模式。常用两种方式:byTypebyName

	<!-- 
		可以使用autowire属性指定自动装配的方式,
		byName:根据Bean的名字和当前Bean的setter风格属性名进行自动装配,若有匹配则进行自动装配,若没有匹配则不装配 。
		byType:根据Bean的类型和当前Bean的属性的类型进行自动装配,若IoC容器中有一个以上的类型匹配的Bean,则抛异常。
	-->	
	<bean id="person" class="com.stone.spring.autowire.Person"
		p:name="Tom" autowire="byName"></bean>

注意:

  • 自动装配用于引用类型依赖注入,不能对简单类型进行操作
  • 使用按类型装配时(byType)必须保障容器中相同类型的 Bean 唯一,推荐使用
  • 使用按名称装配时(byName)必须保障容器中具有指定名称的 Bean,因变量名与配置耦合,不推荐使用
  • 自动装配优先级低于 setter 注入与构造器注入,同时出现时自动装配配置失效

Bean的继承与依赖

	<!-- 
		抽象Bean:bean的abstract属性为true的bean。这样的bean不能被IoC容器实例化,只用来被继承配置。
		若某一个bean的class属性没有指定,则该bean必须是一个抽象bean。 
	-->
	<bean id="address" class="com.stone.spring.autowire.Address"
		p:city="BeiJing" p:street="WuDaoKou" abstract="true"></bean>
	
	<!-- Bean配置的继承,使用bean的parent属性指定继承哪个bean的配置 -->	
	<bean id="address2" p:city="ChongQing" p:street="DaZhongSi" parent="address"></bean>
	
	<bean id="car" class="com.stone.spring.autowire.Car"
		p:brand="Audi" p:price="300000"></bean>
	
	<!-- 要求在配置Person时,必须有一个关联的Car。也就是person这个bean依赖于car这个bean,通过depends-on属性配置前置依赖bean -->
	<bean id="person" class="com.stone.spring.autowire.Person"
		p:name="Tom" p:address-ref="address2" depends-on="car"></bean>

Bean的作用域

	<!-- 
		使用bean的scope属性来配置bean的作用域
		singleton:默认值,容器初始化时创建bean实例,在整个容器的生命周期内只创建这一个bean,单例的。
		prototype:原型的。容器初始化时不创建bean的实例,而在每次请求时都创建一个新的bean实例,并返回。
	 -->
	<bean id="car" class="com.stone.spring.autowire.Car" scope="prototype">
		<property name="brand" value="Audi"></property>
		<property name="price" value="300000"></property>
	</bean>

使用外部属性文件

先添加context命名空间:

image-20221216155715732

导入c3p0和mysql驱动jar包。

在src下创建属性文件db.properties:

user=stone
password=stone
dirverclass=com.mysql.jdbc.Driver
jdbcurl=jdbc:mysql://192.168.8.138:3306/stone
	<!-- 导入属性文件 -->
	<context:property-placeholder location="classpath:db.properties"/>
	
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<!-- 使用外部化属性文件的属性 -->
		<property name="user" value="${user}"></property>
		<property name="password" value="${password}"></property>
		<property name="driverClass" value="${dirverclass}"></property>
		<property name="jdbcUrl" value="${jdbcurl}"></property>
	</bean>

SpEL

	<bean id="address" class="com.stone.spring.spel.Address">
		<!-- 使用SpEL为属性赋一个字面值 -->
		<property name="city" value="#{'BeiJing'}"></property>
		<property name="street" value="WuDaoKou"></property>
	</bean>
	
	<bean id="car" class="com.stone.spring.spel.Car">
		<property name="brand" value="Audio"></property>
		<property name="price" value="500000"></property>
		<!-- 使用SpEL引用类的静态属性 -->
		<property name="tyreperimeter" value="#{T(java.lang.Math).PI*80}"></property>
	</bean>
	
	<bean id="person" class="com.stone.spring.spel.Person">
		<!-- 使用SpEL来引用其他的Bean -->
		<property name="car" value="#{car}"></property>
		<!-- 使用SpEL来引用其他的Bean的属性 -->
		<property name="city" value="#{address.city}"></property>
		<!-- 在SpEL中使用运算符 -->
		<property name="info" value="#{car.price>300000?'金领':'白领'}"></property>
		<property name="name" value="Tom"></property>
	</bean>

Bean的生命周期

Spring IoC容器对Bean的生命周期进行管理的过程:

  • 通过构造器或工厂方法创建Bean实例
  • 为Bean的属性设置值和对其他Bean的引用
  • 调用Bean的初始化方法
  • Bean可以使用了
  • 当容器关闭时,调用Bean的销毁方法

在 Bean 的声明里设置 init-method 和 destroy-method 属性, 为 Bean 指定初始化和销毁方法。

	<bean id="car" class="com.stone.spring.cycle.Car" init-method="init" destroy-method="destroy">
		<property name="brand" value="Audi"></property>
	</bean>
package com.stone.spring.cycle;

public class Car {
	private String brand;
	public Car() {
		System.out.println("Car's Constructor...");
	}
	public String getBrand() {
		return brand;
	}
	public void setBrand(String brand) {
		System.out.println("setBrand...");
		this.brand = brand;
	}
	public void init() {
		System.out.println("init...");
	}
	public void destroy() {
		System.out.println("destroy...");
	}
}
package com.stone.spring.cycle;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
	public static void main(String[] args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans-cycle.xml");
		Car car = (Car) ctx.getBean("car");
		System.out.println(car);
		ctx.close();
	}
}
输出:
Car's Constructor...
setBrand...
init...
com.stone.spring.cycle.Car@2286778
destroy...

创建Bean的后置处理器:

  • Bean后置处理器允许在调用初始化方法前后对Bean进行额外的处理。
  • Bean后置处理器对IoC容器里的所有Bean实例逐一处理,而非单一实例。其典型的应用是:检查Bean属性的正确性或根据特定的标准更改Bean的属性。
  • 对Bean后置处理器而言,需要实现BeanPostProcessor接口。在初始化方法被调用前后,Spring将把每个Bean实例分别传递给上述接口的以下两个方法:
    • postProcessBeforeInitialization
    • postProcessAfterInitialization
package com.stone.spring.cycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcesser implements BeanPostProcessor {

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessBeforeInitialization: " + bean + "," + beanName);
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("postProcessAfterInitialization: " + bean + "," + beanName);
		return bean;
	}

}
	<bean id="car" class="com.stone.spring.cycle.Car" init-method="init" destroy-method="destroy">
		<property name="brand" value="Audi"></property>
	</bean>
	
	<!-- 
		实现BeanPostProcessor接口,并提供
		Object postProcessBeforeInitialization(Object bean, String beanName):init-method之前调用
		Object postProcessAfterInitialization(Object bean, String beanName):init-method之后调用
		bean:bean实例本身
		beanName:IoC容器配置的bean的名字
		返回值:是实际上返回给用户的那个Bean,注意:可以在以上两个方法中修改返回的bean,甚至返回一个新的bean
	-->
	<!-- 配置bean的后置处理器 :不需要配置id,IoC容器自动识别是一个BeanPostProcessor-->
	<bean class="com.stone.spring.cycle.MyBeanPostProcesser"></bean>
输出:
Car's Constructor...
setBrand...
postProcessBeforeInitialization: com.stone.spring.cycle.Car@52a86356,car
init...
postProcessAfterInitialization: com.stone.spring.cycle.Car@52a86356,car
com.stone.spring.cycle.Car@52a86356
destroy...

添加Bean后置处理器后Bean的生命周期:

  • 通过构造器或工厂方法创建Bean实例
  • 为Bean的属性设置值和对其他Bean的引用
  • 将Bean实例传递给Bean后置处理器的postProcessBeforeInitialization方法
  • 调用Bean的初始化方法
  • 将Bean实例传递给Bean后置处理器的postProcessAfterInitialization方法
  • Bean可以使用了
  • 当容器关闭时,调用Bean的销毁方法

通过工厂方法配置Bean

静态工厂方法:

package com.stone.spring.factory;

import java.util.HashMap;
import java.util.Map;

/**
 * 静态工厂方法,直接调用某一个类的静态方法就可以返回Bean的实例
 */
public class StaticCarFactory {
	private static Map<String, Car> cars = new HashMap<String, Car>();
	static {
		cars.put("audi", new Car("audi", 300000));
		cars.put("ford", new Car("ford", 400000));
	}
	//静态工厂方法。
	public static Car getCar(String name) {
		return cars.get(name);
	}
}
	<!-- 通过静态工厂方法来配置bean,注意不是配置静态工厂方法实例,而是配置bean实例 -->
	<!-- 
		class属性:指向静态工厂方法的全类名
		factory-method:指向静态工厂方法的名字
		constructor-arg:如果工厂方法需要传入参数,则使用constructor-arg来配置参数
	 -->
	<bean id="car1" class="com.stone.spring.factory.StaticCarFactory" factory-method="getCar">
		<constructor-arg value="audi"></constructor-arg>
	</bean>

实例工厂方法:

package com.stone.spring.factory;

import java.util.HashMap;
import java.util.Map;
/**
 * 实例工厂方法:实例工厂的方法,即需要创建工厂本身,再调用工厂的实例方法来返回bean的实例
 */
public class InstanceCarFactory {
	 private Map<String, Car> cars = null;
	 public InstanceCarFactory() {
		 cars = new HashMap<String, Car>();
		 cars.put("audi", new Car("audi", 300000));
		 cars.put("ford", new Car("ford", 400000));
	 }
	 public Car getCar(String brand) {
		 return cars.get(brand);
	 }
}
	<!-- 配置工厂的实例 -->
	<bean id="carFactory" class="com.stone.spring.factory.InstanceCarFactory"></bean>
	
	<!-- 通过实例工厂方法来配置bean -->
	<!-- 
		factory-bean属性:指向实例工厂的bean
		factory-method:指向实例工厂方法的名字
		constructor-arg:如果工厂方法需要传入参数,则使用constructor-arg来配置参数
	 -->
	<bean id="car2" factory-bean="carFactory" factory-method="getCar">
		<constructor-arg value="ford"></constructor-arg>
	</bean>

通过FactoryBean配置Bean

package com.stone.spring.factorybean;

import org.springframework.beans.factory.FactoryBean;

//自定义的FactoryBean需要实现FactoryBean的接口
public class CarFactoryBean implements FactoryBean<Car> {

	private String brand;
	public String getBrand() {
		return brand;
	}

	public void setBrand(String brand) {
		this.brand = brand;
	}

	//返回bean的对象
	@Override
	public Car getObject() throws Exception {
		return new Car(brand, 500000);
	}

	//返回bean的类型
	@Override
	public Class<?> getObjectType() {
		return Car.class;
	}

	@Override
	public boolean isSingleton() {
		return true;
	}
}
	<!-- 
		通过FactoryBean来配置Bean的实例
		class:指向FactoryBean的全类名
		property:配置FactoryBean的属性
		但实际返回的实例却是FactoryBean的getObject()方法返回的实例
	 -->
	<bean id="car" class="com.stone.spring.factorybean.CarFactoryBean">
		<property name="brand" value="BMW"></property>
	</bean>

通过注解配置Bean

在classpath中扫描组件:

  • 组件扫描(component scanning):Spring能够从classpath下自动扫描,侦测和实例化具有特定注解的组件。
  • 特定注解包括:
    • @Component:基本注解,标识了一个受Spring管理的组件
    • @Respository:标识持久层组件
    • @Service:标识服务层(业务层)组件
    • @Controller:标识表现层组件
  • 对于扫描到的组件,Spring有默认的命名策略:使用非限定类名,第一个字母小写,也可以在注解中通过value属性值标识组件的名称
  • 当在组件类上使用了特定的注解之后,还需要在Spring的配置文件中声明<context:component-scan>
    • base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包里及其子包中的所有类。
    • 当需要扫描多个包时,可以使用逗号分隔。
    • 如果仅希望扫描特定的类而非基类包下的所有类,可使用resource-pattern属性过滤特定的类。
    • <context:include-filter>子节点表示要包含的目标类
    • <context:exclude-filter>子节点表示要排除的目标类
    • <context:component-scan>下可以拥有若干个<context:include-filter><context:exclude-filter>子节点

<context:include-filter><context:exclude-filter>子节点支持多种类型的过滤表达式:

类型示例说明
annotationcom.stone.XxxAnnotation所有标注了XxxAnnotation的类。该类型采用目标类是否标注了某个注解进行过滤。
assignablecom.stone.XxxService所有继承或扩展XxxService的类。该类型采用目标类是否继承或扩展某个特定类进行过滤。

自动装配:

<context:component-scan>元素还会自动注册AutowiredAnnotationBeanPostProcessor实例,该实例可以自动装配具有@Autowired和@Resource、@Inject注解的属性。

@Autowired注解自动装配具有兼容类型的单个Bean属性:

  • 构造器,普通字段(即便是非public),一切具有参数的方法都可以应用@Autowired注解。
  • 默认情况下,所有使用@Autowired注解的属性都需要被设置。当Spring找不到匹配的Bean装配属性时,会抛出异常。当某一属性允许不被设置,可以设置@Autowired注解的required属性为false。
  • 默认情况下,当IoC容器里存在多个类型兼容的Bean时,通过类型的自动装配将无法工作,此时可以在@Qualifier注解里提供Bean的名称。Spring允许对方法的入参标注@Qualifier以指定注入Bean的名称。
  • @Autowired注解也可以应用在数组类型的属性上,此时Spring将会把所有匹配的Bean进行自动装配。
  • @Autowired注解也可以应用在集合属性上,此时Spring读取该集合的类型信息,然后自动装配所有与之兼容的Bean。
  • @Autowired注解用在java.util.Map上时,若该Map的键值为String,那么Spring将自动装配与之Map值类型兼容的Bean,此时Bean的名称作为键值。
package com.stone.spring.annotation;

import org.springframework.stereotype.Component;

@Component
public class TestObject {
	
}
package com.stone.spring.annotation.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

import com.stone.spring.annotation.service.UserService;

@Controller
public class UserController {
	@Autowired
	private UserService userService;
	public void execute() {
		System.out.println("UserController execute...");
		userService.add();
	}
}
package com.stone.spring.annotation.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.stone.spring.annotation.repository.UserRepository;

@Service
public class UserService {	
	@Autowired
	private UserRepository userRepository;
	public void add() {
		System.out.println("UserService add...");
		userRepository.save();
	}
}
package com.stone.spring.annotation.repository;

public interface UserRepository {
	void save();
}
package com.stone.spring.annotation.repository;

import org.springframework.stereotype.Repository;

@Repository("userRepository")
public class UserRepositoryImpl implements UserRepository {
	@Override
	public void save() {
		System.out.println("UserRepository save...");
	}
}
<?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 http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<!-- 指定Spring IoC容器扫描的包 -->
	<!-- 可以通过resource-pattern指定扫描的资源 -->
	<!--  
	<context:component-scan base-package="com.stone.spring.annotation"
		resource-pattern="repository/*.class"
		>
	</context:component-scan>
	-->
	
	<!-- context:exclude-filter子节点指定排除哪些表达式的组件 -->
	<!-- context:include-filter子节点指定包含哪些表达式的组件,该子节点需要use-default-filters配合使用 -->
	<context:component-scan base-package="com.stone.spring.annotation">
		<!--
		<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
		-->
		<!--
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
		-->
		<!--
		<context:exclude-filter type="assignable" expression="com.stone.spring.annotation.repository.UserRepository"/>
		-->
		<!--
		<context:include-filter type="assignable" expression="com.stone.spring.annotation.repository.UserRepository"/>
		-->	
	</context:component-scan>

</beans>
package com.stone.spring.annotation;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.stone.spring.annotation.controller.UserController;
import com.stone.spring.annotation.repository.UserRepository;
import com.stone.spring.annotation.service.UserService;

public class Test {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-annotation.xml");
		
//		TestObject testObject = (TestObject) ctx.getBean("testObject");
//		System.out.println(testObject);
//		
		UserController userController = (UserController) ctx.getBean("userController");
		System.out.println(userController);
		userController.execute();
		
//		UserService userService = (UserService) ctx.getBean("userService");
//		System.out.println(userService);
//		
//		UserRepository userRepository = (UserRepository) ctx.getBean("userRepository");
//		System.out.println(userRepository);	
	}
}
输出:
com.stone.spring.annotation.controller.UserController@679b62af
UserController execute...
UserService add...
UserRepository save...

纯注解开发

Spring 纯注解开发是一种不依赖 XML 配置文件,仅通过注解来定义和配置 Spring 容器的开发方式。

基本步骤

  • 添加 Spring 相关依赖: 在项目的构建文件(如 Maven 的 pom.xml 或 Gradle 的 build.gradle)中添加 Spring 的相关依赖,如 spring-context 等。
  • 创建配置类: 使用 @Configuration 注解定义一个配置类,用于替代 XML 配置文件。在配置类中,可以使用 @Bean 注解来定义和初始化 Bean。
@Configuration  
@ComponentScan(basePackages = "com.example")  
public class AppConfig {  
  
    @Bean  
    public MyService myService() {  
        return new MyServiceImpl();  
    }
}
  • 组件扫描: 使用 @ComponentScan 注解来指定 Spring 需要扫描的包路径,以便发现带有 @Component@Service@Repository@Controller 等注解的类,并将它们注册为 Bean。

  • 依赖注入: 在需要使用其他 Bean 的地方,使用 @Autowired@Inject 注解来进行自动依赖注入。同时,可以使用 @Qualifier 注解来指定注入的 Bean 的名称。

@Service  
public class AnotherService {  
  
    @Autowired  
    private MyService myService;  
  
    // ...  
}
  • 其他配置: 根据项目的需要,可能还需要配置数据源、事务管理、MVC 等。这些配置都可以通过注解来实现。

常用注解

  • @Configuration: 用于声明当前类是一个配置类,相当于一个 XML 配置文件。
  • @Bean: 用于在配置类中定义一个 Bean,并指定其名称和初始化方法。
  • @ComponentScan: 用于指定Spring需要扫描的包路径,以便发现带有 @Component@Service@Repository@Controller 等注解的类,并将它们注册为 Bean。
  • @Component@Service@Repository@Controller: 用于声明一个类是一个Spring的组件,其中 @Service 通常用于业务逻辑层,@Repository 用于数据访问层,@Controller 用于 Web 层。这些注解都是 @Component 的特化,它们的区别主要是语义上的。
  • @Autowired@Inject: 用于自动注入依赖的 Bean。其中 @Autowired 是 Spring 提供的注解,而 @Inject 是 JSR-330 规范提供的注解,两者在功能上类似。
  • @Qualifier: 用于指定注入的 Bean 的名称,当存在多个相同类型的 Bean 时,可以使用该注解来指定注入哪一个。
  • @Value: 用于注入基本类型值、字符串、其他 Bean 的引用等。
  • @Profile: 用于定义 Spring 环境的配置,可以根据不同的环境(如开发、测试、生产)来加载不同的配置。
  • @PropertySource: 用于加载 .properties 文件中的配置属性,配合 @Value 注解使用。
  • @Enable 系列注解(如 @EnableWebMvc@EnableTransactionManagement 等): 用于开启 Spring 的某些功能或模块。

泛型依赖注入

Spring 4.x 中可以为子类注入子类对应的泛型类型的成员变量的引用。

package com.stone.spring.generic.di;
public class User {
}
package com.stone.spring.generic.di;
public class BaseRepository<T> {
}
package com.stone.spring.generic.di;
import org.springframework.beans.factory.annotation.Autowired;
public class BaseService<T> {
	@Autowired
	protected BaseRepository<T> repository;
	public void add() {
		System.out.println("add...");
		System.out.println(repository);
	}
}
package com.stone.spring.generic.di;
import org.springframework.stereotype.Repository;
@Repository
public class UserRepository extends BaseRepository<User> {
}
package com.stone.spring.generic.di;
import org.springframework.stereotype.Service;
@Service
public class UserService extends BaseService<User> {	
}
package com.stone.spring.generic.di;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Test {
	public static void main(String[] args) {
		ApplicationContext ctx = new ClassPathXmlApplicationContext("beans-generic-di.xml");
		UserService userService = (UserService) ctx.getBean("userService");
		userService.add();
	}
}
<?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 http://www.springframework.org/schema/context/spring-context-4.3.xsd">

	<context:component-scan base-package="com.stone.spring.generic.di"></context:component-scan>

</beans>
输出:
add...
com.stone.spring.generic.di.UserRepository@679b62af

AOP

Spring AOP(Aspect-Oriented Programming)是 Spring 框架中的一个重要部分,它基于面向切面编程(AOP)的思想。AOP 是一种编程范式,旨在提高模块化,它通过将横切关注点从它们所影响的业务逻辑中分离出来,从而实现代码的重用和降低耦合度。

在 Spring AOP 中,横切关注点通常指的是与核心业务逻辑无关但需要在多个地方进行处理的非功能性需求,例如日志记录、事务管理、性能监控、权限控制等。通过使用 Spring AOP,开发人员可以定义这些横切关注点,并将它们作为切面(Aspect)应用到需要的位置,而无需修改原有的业务逻辑代码。

Spring AOP 的实现主要基于动态代理技术,即在运行时动态地为目标对象创建一个代理对象,代理对象会拦截目标对象的方法调用,并在方法调用前后添加相应的横切逻辑。通过这种方式,Spring AOP 可以在不修改原有代码的情况下,为系统添加新的功能或修改现有功能。

核心概念:

  • 切面(Aspect):切面是 AOP 中的核心概念,它表示一个横切关注点。切面是一个包含了通知(Advice)和切点(Pointcut)的类,通知和切点共同定义了切面的行为。
  • 通知(Advice):通知是 AOP 框架在特定切入点执行的增强处理,它是在连接点之前或之后要执行的代码。通知类型包括前置通知(Before advice)、后置通知(After advice)、环绕通知(Around advice)和异常通知(Throws advice)等。
  • 切入点(Pointcut):切入点定义了通知应该应用到哪些连接点。切入点通过表达式语言来定义,这些表达式描述了方法调用的特定方面,如方法名、参数类型和返回类型等。
  • 连接点(Joinpoint):连接点是程序执行过程中能够应用通知的所有点,如方法的调用、异常的处理等。连接点是 AOP 框架的一个核心概念,但通常与开发人员直接打交道的是切点,而不是连接点。
  • 织入(Weaving):织入是将切面应用到目标对象并创建新的代理对象的过程。这个过程可以在编译时、类加载时或运行时完成。在 Spring AOP 中,织入通常是在运行时完成的,通过动态代理技术实现。
  • 目标对象(Target Object):目标对象是被一个或多个切面所通知的对象。在 Spring AOP 中,目标对象通常是一个或多个 Spring Bean。
  • 代理对象(Proxy):代理对象是由 AOP 框架创建的对象,它包含了目标对象的所有方法,并在方法调用时添加通知逻辑。代理对象对于客户端来说是透明的,客户端通常不会直接访问目标对象,而是通过代理对象来调用目标对象的方法。

image-20240515171930071

入门

在 IDEA 中创建 Maven 项目,在 pom.xml 文件中引入依赖:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
</dependencies>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>

创建接口和实现类:

public interface BookDao {
    public void save();
    public void update();
}

@Repository
public class BookDaoImpl implements BookDao {

    public void save() {
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
    }

    public void update(){
        System.out.println("book dao update ...");
    }
}

创建 Spring 的配置类,使用 @EnableAspectJAutoProxy 开启注解格式 AOP 功能:

@Configuration
@ComponentScan("com.itheima")
@EnableAspectJAutoProxy
public class SpringConfig {
}

创建启动类:

public class App {
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.save();
    }
}

使用 Spring AOP 的方式在不改变 update 方法的前提下让其具有打印系统时间的功能,创建通知类和通知方法:

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt(){}
    
    @Before("pt()")
    public void method(){
        System.out.println(System.currentTimeMillis());
    }
}

其中:

  • @Aspect:设置当前类为 AOP 切面类。
  • @Pointcut:定义切入点,依托一个不具有实际意义的方法进行,即无参数、无返回值、方法体无实际逻辑。
  • @Before:设置当前通知方法与切入点之间的绑定关系,当前通知方法在切入点指定的方法之前运行。

切入点表达式

在使用 @Pointcut 定义切入点时,其语法为:

动作关键字(访问修饰符  返回值  包名.类/接口名.方法名(参数) 异常名)

例如:

execution(public User com.itheima.service.UserService.findById(int))
  • execution:动作关键字,描述切入点的行为动作,例如 execution 表示执行到指定切入点。
  • public:访问修饰符,可以是 publicprivate 等,可以省略。
  • User:返回值,写返回值类型。
  • com.itheima.service:包名,多级包使用点连接。
  • UserService:类/接口名称。
  • findById:方法名。
  • int:参数,直接写参数的类型,多个类型用逗号隔开。
  • 异常名:方法定义中抛出指定异常,可以省略。

可以使用通配符描述切入点,简化配置,包括:

  • *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

    execution(public * com.itheima.*.UserService.find*(*))
    

    匹配 com.itheima 包下的任意包中的 UserService 类或接口中所有 find 开头的带有一个参数的方法。

  • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

    execution(public User com..UserService.findById(..))
    

    匹配 com 包下的任意包中的 UserService 类或接口中所有名称为 findById 的方法。

  • +:专用于匹配子类类型

    execution(* *..*Service+.*(..))
    

    这个使用率较低,描述子类的,很少使用。*Service+ 表示所有以 Service 结尾的接口的子类。

例如:

// 匹配接口,能匹配到
execution(void com.itheima.dao.BookDao.update())

// 匹配实现类,能匹配到
execution(void com.itheima.dao.impl.BookDaoImpl.update())

// 返回值任意,能匹配到
execution(* com.itheima.dao.impl.BookDaoImpl.update())

// 返回值任意,但是 update 方法必须要有一个参数
execution(* com.itheima.dao.impl.BookDaoImpl.update(*))

// 返回值为 void,com 包下的任意包三层包下的任意类的 update 方法,匹配到的是实现类,能匹配
execution(void com.*.*.*.*.update())

// 返回值为 void,com 包下的任意两层包下的任意类的 update 方法,匹配到的是接口,能匹配
execution(void com.*.*.*.update())

// 返回值为 void,方法名是 update 的任意包下的任意类,能匹配
execution(void *..update())

// 匹配项目中任意类的任意方法,能匹配,但是不建议使用这种方式,影响范围广
execution(* *..*(..))

// 匹配项目中任意包任意类下只要以 u 开头的方法,update 方法能满足,能匹配
execution(* *..u*(..))

// 匹配项目中任意包任意类下只要以 e 结尾的方法,update 和 save 方法能满足,能匹配
execution(* *..*e(..))

// 返回值为 void,com 包下的任意包任意类任意方法,能匹配,* 代表的是方法
execution(void com..*())

// 将项目中所有业务层方法的以 find 开头的方法匹配
execution(* com.itheima.*.*Service.find*(..))

// 将项目中所有业务层方法的以 save 开头的方法匹配
execution(* com.itheima.*.*Service.save*(..))

通知类型

通知类型主要有以下几种:

  • 前置通知(Before Advice):在目标方法执行之前执行的通知,无法阻止方法的继续执行(除非它抛出一个异常)。
  • 后置通知(After Returning Advice):在目标方法成功执行之后执行的通知。如果目标方法通过抛出异常退出,则不会执行此类型的通知。
  • 异常通知(After Throwing Advice):在目标方法通过抛出异常退出时执行的通知。通常用于记录异常信息或进行异常处理。
  • 最终通知(After (finally) Advice):无论目标方法通过何种方式退出(正常返回或异常退出),该通知都会执行。它类似于 Java 语言中的 finally 块。
  • 环绕通知(Around Advice):环绕通知是最强大的通知类型,它将目标方法封装起来,可以在方法调用之前和之后自定义行为,甚至可以完全控制是否调用目标方法。
package com.stone.spring.aop.impl;

import java.util.Arrays;
import java.util.List;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

//把这个类声明为一个切面:需要把该类放入到IoC容器中、再声明为一个切面
@Component
@Aspect
public class LoggingAspect {
	//声明该方法是一个前置通知:在目标方法开始之前执行
	@Before("execution(* com.stone.spring.aop.impl.*.*(..))")
	public void beforeMethod(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		System.out.println("The method " + methodName + " begins with " + args);
	}
	
	//后置通知:在目标方法执行后(无论是否发生异常),执行的通知
	//在后置通知中还不能访问目标方法执行的结果
	@After("execution(* com.stone.spring.aop.impl.*.*(..))")
	public void afterMethod(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("The method " + methodName + " ends");
	}
	
	//在方法正常结束后执行的代码,返回通知是可以访问到方法的返回值的。
	@AfterReturning(value="execution(* com.stone.spring.aop.impl.*.*(..))",returning="result")	
	public void afterReturning(JoinPoint joinPoint, Object result) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("The method " + methodName + " ends with " + result);
	}
	
	//在目标方法出现异常时会执行代码。可以访问到异常对象;且可以指定在出现特定异常时再执行通知代码
	@AfterThrowing(value="execution(* com.stone.spring.aop.impl.*.*(..))",throwing="ex")
	public void afterThrowing(JoinPoint joinPoint, Exception ex) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("The method " + methodName + " occurs exception " + ex);
	}
	
	//环绕通知需要携带ProceedingJoinPoint类型的参数。
	//环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法
	//且环绕通知必须有返回值,返回值即为目标方法的返回值
	@Around("execution(* com.stone.spring.aop.impl.*.*(..))")
	public Object aroundMethod(ProceedingJoinPoint pjp) {
		Object result = null;
		String methodName = pjp.getSignature().getName();
		
		
		try {
			//前置通知
			System.out.println("The aroundmethod " + methodName + " begins with " + Arrays.asList(pjp.getArgs()));
			//执行目标方法
			result = pjp.proceed();
			//返回通知
			System.out.println("The aroundmethod " + methodName + " ends with " + result);
		} catch (Throwable e) {
			//异常通知
			System.out.println("The aroundmethod " + methodName + " occurs exception " + e);
			throw new RuntimeException(e);
		}
		//后置通知
		System.out.println("The aroundmethod " + methodName + " ends");
		
		return result;
	}
}

环绕通知注意事项:

  • 环绕通知必须依赖形参 ProceedingJoinPoint 才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知。
  • 通知中如果未使用 ProceedingJoinPoint 对原始方法进行调用将跳过原始方法的执行。
  • 对原始方法的调用可以不接收返回值,通知方法设置成 void 即可,如果接收返回值,最好设定为 Object 类型。
  • 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成 void,也可以设置成 Object
  • 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须要处理 Throwable 异常。

例子:测试业务层接口执行效率

@Component
@Aspect
public class ProjectAdvice {
    //配置业务层的所有方法
    @Pointcut("execution(* com.itheima.service.*Service.*(..))")
    private void servicePt(){}
    
    @Around("servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp){
        //获取执行签名信息
        Signature signature = pjp.getSignature();
        //通过签名获取执行操作名称(接口名)
        String className = signature.getDeclaringTypeName();
        //通过签名获取执行操作名称(方法名)
        String methodName = signature.getName();
        
        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
           pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("万次执行:"+ className+"."+methodName+"---->" +(end-start) + "ms");
    } 
}

获取数据

可以从 AOP 中获取的数据有:

  • 获取切入点方法的参数,所有的通知类型都可以获取参数
    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    • ProceedingJoinPoint:适用于环绕通知
  • 获取切入点方法返回值,前置和抛出异常后通知是没有返回值,后置通知可有可无,所以不做研究
    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息,前置和返回后通知是不会有,后置通知可有可无,所以不做研究
    • 抛出异常后通知
    • 环绕通知

获取参数

非环绕通知,在方法上添加 JoinPoint 来获取参数:

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @Before("pt()")
    public void before(JoinPoint jp) 
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice ..." );
    }
	//...其他的略
}

环绕通知使用 ProceedingJoinPoint,因为 ProceedingJoinPointJoinPoint 类的子类,所以对于 ProceedingJoinPoint 类中应该也会有对应的 getArgs() 方法:

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp)throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        Object ret = pjp.proceed();
        return ret;
    }
	//其他的略
}

获取返回值

对于返回值,只有 AfterReturingAround 这两个通知类型可以获取。

环绕通知获取返回值:

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;
        Object ret = pjp.proceed(args);
        return ret;
    }
	//其他的略
}

上述代码中,ret 就是方法的返回值,不但可以获取,如果需要还可以进行修改。

返回后通知获取返回值:

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @AfterReturning(value = "pt()",returning = "ret")
    public void afterReturning(Object ret) {
        System.out.println("afterReturning advice ..."+ret);
    }
	//其他的略
}

获取异常

对于获取抛出的异常,只有 AfterThrowing 和环绕 Around 这两个通知类型可以获取。

环绕通知获取异常:

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp){
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;
        Object ret = null;
        try{
            ret = pjp.proceed(args);
        }catch(Throwable throwable){
            t.printStackTrace();
        }
        return ret;
    }
	//其他的略
}

抛出异常后通知获取异常:

@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @AfterThrowing(value = "pt()",throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out.println("afterThrowing advice ..."+t);
    }
	//其他的略
}

范例:处理参数中的空格

@Component
@Aspect
public class DataAdvice {
    @Pointcut("execution(boolean com.itheima.service.*Service.*(*,*))")
    private void servicePt(){}
    
    @Around("DataAdvice.servicePt()")
    // @Around("servicePt()")这两种写法都对
    public Object trimStr(ProceedingJoinPoint pjp) throws Throwable {
        //获取原始方法的参数
        Object[] args = pjp.getArgs();
        for (int i = 0; i < args.length; i++) {
            //判断参数是不是字符串
            if(args[i].getClass().equals(String.class)){
                args[i] = args[i].toString().trim();
            }
        }
        //将修改后的参数传入到原始方法的执行中
        Object ret = pjp.proceed(args);
        return ret;
    }
    
}

切面的优先级

package com.stone.spring.aop.impl;

import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
//可以使用@Order指定切面的优先级,值越小优先级越高
@Order(1)
public class ValidationAspect {
	@Before("execution(* com.stone.spring.aop.impl.*.*(..))")
	public void validateArgs(JoinPoint joinPoint) {
		System.out.println("validate: " + Arrays.asList(joinPoint.getArgs()));
	}
}

重用切点表达式

package com.stone.spring.aop.impl;

import java.util.Arrays;
import java.util.List;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

//把这个类声明为一个切面:需要把该类放入到IoC容器中、再声明为一个切面
@Component
@Aspect
@Order(2)
public class LoggingAspect {
	
	//定义一个方法,用于声明切点表达式。一般地,该方法中不需要其他的代码.
	//使用@Pointcut来声明切点表达式。后面的其他通知直接使用方法名来引用当前的切点表达式
	@Pointcut("execution(* com.stone.spring.aop.impl.*.*(..))")
	public void declareJointPointExpression() {}
	
	//声明该方法是一个前置通知:在目标方法开始之前执行
	@Before("declareJointPointExpression()")
	public void beforeMethod(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();
		List<Object> args = Arrays.asList(joinPoint.getArgs());
		System.out.println("The method " + methodName + " begins with " + args);
	}
	
	//后置通知:在目标方法执行后(无论是否发生异常),执行的通知
	//在后置通知中还不能访问目标方法执行的结果
	@After("declareJointPointExpression()")
	public void afterMethod(JoinPoint joinPoint) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("The method " + methodName + " ends");
	}
	
	//在方法正常结束后执行的代码,返回通知是可以访问到方法的返回值的。
	@AfterReturning(value="declareJointPointExpression()",returning="result")	
	public void afterReturning(JoinPoint joinPoint, Object result) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("The method " + methodName + " ends with " + result);
	}
	
	//在目标方法出现异常时会执行代码。可以访问到异常对象;且可以指定在出现特定异常时再执行通知代码
	@AfterThrowing(value="declareJointPointExpression()",throwing="ex")
	public void afterThrowing(JoinPoint joinPoint, Exception ex) {
		String methodName = joinPoint.getSignature().getName();
		System.out.println("The method " + methodName + " occurs exception " + ex);
	}
	
	//环绕通知需要携带ProceedingJoinPoint类型的参数。
	//环绕通知类似于动态代理的全过程:ProceedingJoinPoint类型的参数可以决定是否执行目标方法
	//且环绕通知必须有返回值,返回值即为目标方法的返回值
	@Around("declareJointPointExpression()")
	public Object aroundMethod(ProceedingJoinPoint pjp) {
		Object result = null;
		String methodName = pjp.getSignature().getName();
		
		
		try {
			//前置通知
			System.out.println("The aroundmethod " + methodName + " begins with " + Arrays.asList(pjp.getArgs()));
			//执行目标方法
			result = pjp.proceed();
			//返回通知
			System.out.println("The aroundmethod " + methodName + " ends with " + result);
		} catch (Throwable e) {
			//异常通知
			System.out.println("The aroundmethod " + methodName + " occurs exception " + e);
			throw new RuntimeException(e);
		}
		//后置通知
		System.out.println("The aroundmethod " + methodName + " ends");
		
		return result;
	}
}
package com.stone.spring.aop.impl;

import java.util.Arrays;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
//可以使用@Order指定切面的优先级,值越小优先级越高
@Order(1)
public class ValidationAspect {
	@Before("LoggingAspect.declareJointPointExpression()")
	public void validateArgs(JoinPoint joinPoint) {
		System.out.println("validate: " + Arrays.asList(joinPoint.getArgs()));
	}
}

基于配置文件方式

创建配置文件applicationContext-xml.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: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 http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">

	<!-- 配置bean -->
	<bean id="arithmeticCalculatorImpl" class="com.stone.spring.aop.xml.ArithmeticCalculatorImpl"></bean>
	
	<!-- 配置切面的bean -->
	<bean id="loggingAspect" class="com.stone.spring.aop.xml.LoggingAspect"></bean>
	<bean id="validationAspect" class="com.stone.spring.aop.xml.ValidationAspect"></bean>
	
	<!-- 配置aop -->
	<aop:config>
		<!-- 配置切点表达式 -->
		<aop:pointcut expression="execution(* com.stone.spring.aop.xml.*.*(..))" id="pointcut"/>
		<!-- 配置切面及通知 -->
		<aop:aspect ref="loggingAspect" order="2">
			<aop:before method="beforeMethod" pointcut-ref="pointcut"/>
			<aop:after method="afterMethod" pointcut-ref="pointcut"/>
			<aop:after-throwing method="afterThrowing" pointcut-ref="pointcut" throwing="ex"/>
			<aop:after-returning method="afterReturning" pointcut-ref="pointcut" returning="result"/>
			<aop:around method="aroundMethod" pointcut-ref="pointcut"/>
		</aop:aspect>
		<aop:aspect ref="validationAspect" order="1">
			<aop:before method="validateArgs" pointcut-ref="pointcut"/>
		</aop:aspect>
	</aop:config>

</beans>

创建接口ArithmeticCalculator:

package com.stone.spring.aop.xml;

public interface ArithmeticCalculator {
	int add(int i,int j);
	int sub(int i,int j);
	int mul(int i,int j);
	int div(int i,int j);
}

创建接口的实现类ArithmeticCalculatorImpl:

package com.stone.spring.aop.xml;

public class ArithmeticCalculatorImpl implements ArithmeticCalculator {

	@Override
	public int add(int i, int j) {
		int result = i + j;
		return result;
	}

	@Override
	public int sub(int i, int j) {
		int result = i - j;
		return result;
	}

	@Override
	public int mul(int i, int j) {
		int result = i * j;
		return result;
	}

	@Override
	public int div(int i, int j) {
		int result = i / j;
		return result;
	}

}

测试,不修改类ArithmeticCalculatorImpl的情况下,为该类中的方法增加日志和参数验证:

package com.stone.spring.aop.xml;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {
	public static void main(String[] args) {
		// 1.创建Spring的IoC容器
		ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext-xml.xml");

		// 2.从IoC容器获取bean的实例
		ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) ctx.getBean("arithmeticCalculatorImpl");

		// 3.使用bean
		int result = arithmeticCalculator.add(3, 6);
		System.out.println("result: " + result);
		
		result = arithmeticCalculator.div(12, 2);
		System.out.println("result: " + result);
	}

}

JDBC

JdbcTemplate

创建属性文件db.properties:

user=stone
password=stone
driverClass=com.mysql.jdbc.Driver
jdbcUrl=jdbc:mysql://192.168.8.138:3306/stone
initialPoolSize=5
maxPoolSize=10

创建配置文件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"
	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 http://www.springframework.org/schema/context/spring-context-4.3.xsd">
	
	<context:component-scan base-package="com.stone.spring.jdbc"></context:component-scan>
	
	<!-- 导入资源文件 -->
	<context:property-placeholder location="classpath:db.properties"/>
	
	<!-- 配置C3P0数据源 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${user}"></property>
		<property name="password" value="${password}"></property>
		<property name="driverClass" value="${driverClass}"></property>
		<property name="jdbcUrl" value="${jdbcUrl}"></property>
		<property name="initialPoolSize" value="${initialPoolSize}"></property>
		<property name="maxPoolSize" value="${maxPoolSize}"></property>
	</bean>
	
	<!-- 配置Spring的JdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

</beans>
package com.stone.spring.jdbc;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

public class JDBCTest {
	
	ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
	JdbcTemplate jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");

	@Test
	public void testDataSource() throws SQLException {
		DataSource dataSource = ctx.getBean(DataSource.class);
		System.out.println(dataSource.getConnection());
	}
	
	/**
	 * 执行insert,update,delete
	 */
	@Test
	public void testUpdate() {
		String sql = "update t_user set user_name=? where id=?";
		jdbcTemplate.update(sql,"ccc",2);
	}
	
	/**
	 * 执行批量更新:批量的insert,update,delete
	 * 最后一个参数是Object[]的List类型:因为修改一条记录需要一个Object数组,多条记录就需要多个Object数组
	 */
	@Test
	public void testBatchUpdate() {
		String sql = "insert into t_user(id,user_name,password) values(?,?,?)";
		List<Object[]> batchArgs = new ArrayList<>();
		batchArgs.add(new Object[] {3,"ccc","ccc"});
		batchArgs.add(new Object[] {4,"ddd","ddd"});
		batchArgs.add(new Object[] {5,"eee","eee"});
		batchArgs.add(new Object[] {6,"fff","fff"});
		batchArgs.add(new Object[] {7,"ggg","ggg"});
		jdbcTemplate.batchUpdate(sql, batchArgs);
	}
	
	/**
	 * 从数据库中获取一条记录,实际得到对应的一个对象
	 * 注意不是调用queryForObject(String sql, Class<User> requiredType, Object... args)方法! 
	 * 而需要调用queryForObject(String sql, RowMapper<User> rowMapper, Object... args)方法
	 * 1、其中的RowMapper指定如何去映射结果集的行,常用的实现类为BeanPropertyRowMapper
	 * 2、使用SQL列的别名完成列名和类的属性名的映射,例如last_name lastName
	 * 3、不支持级联属性,JdbcTemplate到底是一个JDBC的小工具,而不是ORM框架
	 */
	@Test
	public void testQueryObject() {
		String sql = "select id,user_name userName,password from t_user where id=?";
		RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
		User user = jdbcTemplate.queryForObject(sql, rowMapper, 1);
		System.out.println(user);
	}
	
	/**
	 * 查到实体类的集合
	 * 注意调用的不是queryForList方法
	 */
	@Test
	public void testQueryForList() {
		String sql = "select id,user_name userName,password from t_user where id>?";
		RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
		List<User> users = jdbcTemplate.query(sql, rowMapper, 3);
		System.out.println(users);
	}
	
	/**
	 * 获取单个列的值,或做统计查询
	 * 使用queryForObject(String sql, Class<Long> requiredType)方法
	 */
	@Test
	public void testQueryForObject2() {
		String sql = "select count(id) from t_user";
		long count = jdbcTemplate.queryForObject(sql, Long.class);
		System.out.println(count);
	}
}
package com.stone.spring.jdbc;

public class User {	
	private int id;
	private String userName;
	private String password;
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", userName=" + userName + ", password=" + password + "]";
	}
}
package com.stone.spring.jdbc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	public User get(Integer id) {
		String sql = "select id,user_name userName,password from t_user where id=?";
		RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
		User user = jdbcTemplate.queryForObject(sql, rowMapper, id);
		return user;
	}
}
    UserDao userDao = ctx.getBean(UserDao.class);
	@Test
	public void testUserDao() {
		System.out.println(userDao.get(1));
	}

JdbcDaoSupport

package com.stone.spring.jdbc;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;

/**
 * 不推荐使用JdbcDaoSupport,而推荐直接使用JDBCTemplate作为Dao类的成员变量
 */
@Repository
public class UserDao1 extends JdbcDaoSupport {
	
	@Autowired
	public void setDataSource2(DataSource dataSource) {
		setDataSource(dataSource);
	}
	
	public User get(Integer id) {
		String sql = "select id,user_name userName,password from t_user where id=?";
		RowMapper<User> rowMapper = new BeanPropertyRowMapper<>(User.class);
		User user = getJdbcTemplate().queryForObject(sql, rowMapper, id);
		return user;
	}
}
	UserDao1 userDao1 = ctx.getBean(UserDao1.class);
	@Test
	public void testUserDao1() {
		System.out.println(userDao1.get(1));
	}

NamedParameterJdbcTemplate

	<!-- 配置NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参构造器,所以必须为其构造器指定参数 -->
	<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
		<constructor-arg ref="dataSource"></constructor-arg>
	</bean>
package com.stone.spring.jdbc;

public class User {	
	private int id;
	private String userName;
	private String password;
	public User() {
		super();
	}
	public User(int id, String userName, String password) {
		super();
		this.id = id;
		this.userName = userName;
		this.password = password;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getUserName() {
		return userName;
	}
	public void setUserName(String userName) {
		this.userName = userName;
	}
	public String getPassword() {
		return password;
	}
	public void setPassword(String password) {
		this.password = password;
	}
	@Override
	public String toString() {
		return "User [id=" + id + ", userName=" + userName + ", password=" + password + "]";
	}
}
	NamedParameterJdbcTemplate namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class)
	/**
	 * 可以为参数起名字。
	 * 1、好处:若有多个参数,则不用再去对应位置,直接对应参数名,便于维护
	 * 2、缺点:较为麻烦。
	 */
	@Test
	public void testNamedParameterJdbcTemplate() {
		String sql = "insert into t_user(id,user_name,password) values(:id,:username,:password)";
		
		Map<String, Object> paramMap = new HashMap<>();
		paramMap.put("id", 8);
		paramMap.put("username", "hh");
		paramMap.put("password", "hh");
		
		namedParameterJdbcTemplate.update(sql, paramMap);
	}
	
	/**
	 * 使用具名参数时,可以使用update(String sql, SqlParameterSource paramSource)方法进行更新操作
	 * 1.SQL语句中的参数名和类的属性一致!
	 * 2.使用SqlParameterSource的BeanPropertySqlParameterSource实现类作为参数
	 */
	@Test
	public void testNamedParameterJdbcTemplate2() {
		String sql = "insert into t_user(id,user_name,password) values(:id,:userName,:password)";
		User user = new User(9, "iii", "iii");
		SqlParameterSource paramSource = new BeanPropertySqlParameterSource(user);
		namedParameterJdbcTemplate.update(sql, paramSource);
	}

Transaction

Spring 框架提供了强大的事务管理功能,使得开发者能够更容易地处理数据库操作中的事务。在 Spring 中,事务管理主要分为编程式事务管理和声明式事务管理两种方式。

  • 编程式事务管理:需要开发者在代码中显式地调用事务管理接口,例如使用 PlatformTransactionManagerTransactionDefinition 接口来定义和管理事务。这种方式提供了更大的灵活性,但代码会相对复杂一些。
  • 声明式事务管理:主要通过 AOP(面向切面编程)实现,开发者只需在配置文件中进行声明,或者在注解中进行配置,Spring 会自动将事务管理代码嵌入到目标方法执行流程中。声明式事务管理大大简化了事务管理的代码,是 Spring 推荐使用的方式。

声明式事务

声明式事务管理的基本步骤:

  • 配置数据源:首先需要配置数据源(DataSource),指定连接数据库的信息。
  • 配置事务管理器:配置事务管理器(TransactionManager),Spring 提供了多种事务管理器,例如 DataSourceTransactionManager 用于 JDBC 数据源。
  • 配置事务通知:定义事务通知(Transaction Advice),即告诉 Spring 在何时(例如方法执行前、后)进行事务管理。
  • 配置 AOP 切面:将事务通知应用到目标方法上,这通常通过配置 AOP 切面实现。

以转账为例,整合 MyBatiopen in new windows 进行测试,先创建测试表:

create table tbl_account(
    id int primary key auto_increment,
    name varchar(35),
    money double
);
insert into tbl_account values(1,'Tom',1000);
insert into tbl_account values(2,'Jerry',1000);

在 IDEA 中创建 Maven 项目,在 pom.xml 文件中引入依赖:

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.16</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.0</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.10.RELEASE</version>
    </dependency>

</dependencies>

创建实体类:

public class Account implements Serializable {

    private Integer id;
    private String name;
    private Double money;
	//setter...getter...toString...方法略    
}

创建 Dao 接口及其实现类:

public interface AccountDao {

    @Update("update tbl_account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    @Update("update tbl_account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}

创建 Service 接口及其实现类:

public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    public void transfer(String out,String in ,Double money) ;
}

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        accountDao.inMoney(in,money);
    }

}

创建 jdbc.properties 属性文件:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_db?useSSL=false
jdbc.username=root
jdbc.password=root

创建 JdbcConfig 配置类:

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }
}

创建 MybatisConfig 配置类:

public class MybatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.itheima.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.itheima.dao");
        return msc;
    }
}

创建 SpringConfig 配置类:

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
public class SpringConfig {
}

创建测试类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {

    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom","Jerry",100D);
    }

}

在需要被事务管理的方法上添加注解:

public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    //配置当前接口方法具有事务
    public void transfer(String out,String in ,Double money) ;
}

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
    
	@Transactional
    public void transfer(String out,String in ,Double money) {
        accountDao.outMoney(out,money);
        int i = 1/0;
        accountDao.inMoney(in,money);
    }

}

注意:

  • @Transactional 可以写在接口类上、接口方法上、实现类上和实现类方法上
  • 写在接口类上,该接口的所有实现类的所有方法都会有事务
  • 写在接口方法上,该接口的所有实现类的该方法都会有事务
  • 写在实现类上,该类中的所有方法都会有事务
  • 写在实现类方法上,该方法上有事务
  • 建议写在实现类或实现类的方法上

JdbcConfig 类中配置事务管理器:

public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(userName);
        ds.setPassword(password);
        return ds;
    }

    //配置事务管理器,MyBatis 使用的是 jdbc 事务
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

**注意:**事务管理器要根据使用技术进行选择,MyBatis 框架使用的是 JDBC 事务,可以直接使用 DataSourceTransactionManager

SpringConfig 配置类中开启事务注解:

@Configuration
@ComponentScan("com.itheima")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class
//开启注解式事务驱动
@EnableTransactionManagement
public class SpringConfig {
}

事务属性

@Transactional 注解上还可以配置如下属性:

  • readOnlytrue 表示只读事务,false 表示读写事务,默认为 false

  • timeout:设置超时时间,单位秒,在多长时间之内事务没有提交成功就自动回滚,默认值 -1 表示不设置超时时间。

  • rollbackFor:指定需要回滚的异常类数组。当抛出这些异常时,事务将回滚。并不是所有的异常都会回滚事务,Spring 的事务只会对 Error 异常和 RuntimeException 异常及其子类进行事务回滚,其他的异常类型不会自动回滚。

  • noRollbackFor:指定不需要回滚的异常类数组。当抛出这些异常时,事务不会回滚。

  • rollbackForClassName:等同于 rollbackFor,只不过属性为异常的类全名字符串。

  • noRollbackForClassName:等同于 noRollbackFor,只不过属性为异常的类全名字符串。

  • isolation:设置事务的隔离级别

    • DEFAULT:默认隔离级别, 会采用数据库的隔离级别
    • READ_UNCOMMITTED:读未提交
    • READ_COMMITTED:读已提交
    • REPEATABLE_READ:重复读取
    • SERIALIZABLE:串行化
  • propagation:指定事务传播行为。
属性取值含义
Propagation.REQUIRED支持当前事务,如果当前没有事务,就新建一个事务。这是默认值,也是最常见的选择。
Propagation.REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
Propagation.SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
Propagation.MANDATORY支持当前事务,如果当前没有事务,就抛出异常。
Propagation.NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
Propagation.NEVER以非事务方式执行,如果当前存在事务,则抛出异常。
Propagation.NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED相同的操作。

例子:在一个事务中,增加日志功能,不论事务是否执行成功,都需要记录日志,此时就需要对记录日志作为一个单独事务,即指定传播行为为 Propagation.REQUIRES_NEW

创建表:

create table tbl_log(
   id int primary key auto_increment,
   info varchar(255),
   createDate datetime
)

添加 LogDao 接口:

public interface LogDao {
    @Insert("insert into tbl_log (info,createDate) values(#{info},now())")
    void log(String info);
}

添加 LogService 接口与实现类:

public interface LogService {
    void log(String out, String in, Double money);
}
@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;
	@Transactional
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

在转账的业务中添加记录日志:

public interface AccountService {
    /**
     * 转账操作
     * @param out 传出方
     * @param in 转入方
     * @param money 金额
     */
    //配置当前接口方法具有事务
    public void transfer(String out,String in ,Double money)throws IOException ;
}
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;
    @Autowired
    private LogService logService;
	@Transactional
    public void transfer(String out,String in ,Double money) {
        try{
            accountDao.outMoney(out,money);
            accountDao.inMoney(in,money);
        }finally {
            logService.log(out,in,money);
        }
    }

}

修改 logService 改变事务的传播行为:

@Service
public class LogServiceImpl implements LogService {

    @Autowired
    private LogDao logDao;
	//propagation设置事务属性:传播行为设置为当前操作需要新事务
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String out,String in,Double money ) {
        logDao.log("转账操作由"+out+"到"+in+",金额:"+money);
    }
}

使用 XML 文件配置

修改 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 http://www.springframework.org/schema/context/spring-context-4.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
	
	<context:component-scan base-package="com.stone.spring.tx.xml"></context:component-scan>
	
	<!-- 导入资源文件 -->
	<context:property-placeholder location="classpath:db.properties"/>
	
	<!-- 配置C3P0数据源 -->
	<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
		<property name="user" value="${user}"></property>
		<property name="password" value="${password}"></property>
		<property name="driverClass" value="${driverClass}"></property>
		<property name="jdbcUrl" value="${jdbcUrl}"></property>
		<property name="initialPoolSize" value="${initialPoolSize}"></property>
		<property name="maxPoolSize" value="${maxPoolSize}"></property>
	</bean>
	
	<!-- 配置Spring的JdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 配置bean -->
	<bean id="bookShopDao" class="com.stone.spring.tx.xml.BookShopDaoImpl">
		<property name="jdbcTemplate" ref="jdbcTemplate"></property>
	</bean>
	
	<bean id="bookShopService" class="com.stone.spring.tx.xml.BookShopServiceImpl">
		<property name="bookShopDao" ref="bookShopDao"></property>
	</bean>
	
	<bean id="cashier" class="com.stone.spring.tx.xml.CashierImpl">
		<property name="bookShopService" ref="bookShopService"></property>
	</bean>
	
	<!-- 1.配置事务管理器 -->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 2.配置事务属性 -->
	<tx:advice id="txAdvice" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 根据方法名指定事务的属性 -->
			<tx:method name="purchase" propagation="REQUIRES_NEW"/>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="find*" read-only="true"/>
			<tx:method name="*"/>
		</tx:attributes>
	</tx:advice>
	
	<!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来 -->
	<aop:config>
		<aop:pointcut expression="execution(* com.stone.spring.tx.xml.*.*(..))" id="txPointCut"/>
		<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
	</aop:config>

</beans>

将前面的接口及实现类拷贝到 com.stone.spring.tx.xml 下即可进行测试。

上次编辑于:
贡献者: stonebox,stone