概述
Spring是一款JavaSE/EE一站式轻量级开源框架,解决的是业务逻辑层和其他各层的松耦合问题,因此将面向接口的编程思想贯穿整个系统应用
Spring的优点
- 方便解耦,简化开发。可以将所有对象创建和依赖关系维护交由Spring进行管理
- AOP编程的支持。Spring提供面向切面编程,可以方便的实现对程序进行拦截、运行监控等功能
- 声明式事务的支持。只需要通过配置就可以完成对事务的管理,无需手动编程
- 方便程序的测试。支持JUnit4,可以通过注解方便的测试Spring程序
- 方便集成各种优秀框架。Spring内部提供了对各种优秀框架(struts、Hibernate、Mybatis等)的直接支持
- 降低JavaEE API的使用难度。Spring对JavaEE开发中非常难用的一些API提供了封装,使这些API应用难度大大降低
Spring Framework系统架构
Data Access:数据访问
Data Integration:数据集成
Web:web开发
AOP:面向切面编程
Aspects:AOP思想实现
Core Container:核心容器
Test:单元测试与集成测试
IOC
概述
IOC(Inversion of Control)控制反转:使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象控制权由程序转移到外部,此思想为控制反转
Spring对IOC思想进行了实现
- Spring提供了一个容器,称为IOC容器,用来充当IOC思想中的“外部”
- IOC容器负责对象的创建、初始化等一系列操作,被创建或被管理的对象在IOC容器中统称Bean
DI(Dependency Injection)依赖注入:在容器中建立bean与bean之间依赖关系的整个过程
入门案例
IDEA创建一个Maven项目,pom.xml中引入依赖
<?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.spark</groupId>
<artifactId>spring-ioc</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<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.13</version>
</dependency>
</dependencies>
</project>
编写Dao和Service接口及实现类
package com.spark.Dao;
/**
* BookDao class
* description: BookDao
*
* @author Administrator
* @date 2023/6/24
*/
public interface BookDao {
void save();
}
package com.spark.Dao.impl;
import com.spark.Dao.BookDao;
/**
* BookDaoImpl class
* description: BookDaoImpl
*
* @author Administrator
* @date 2023/6/24
*/
public class BookDaoImpl implements BookDao {
@Override
public void save() {
System.out.println("图书save成功了");
}
}
package com.spark.service;
/**
* BookService class
* description: BookService
*
* @author Administrator
* @date 2023/6/24
*/
public interface BookService {
void save();
}
package com.spark.service.impl;
import com.spark.Dao.BookDao;
import com.spark.Dao.impl.BookDaoImpl;
import com.spark.service.BookService;
/**
* BookServiceImpl class
* description: BookServiceImpl
*
* @author Administrator
* @date 2023/6/24
*/
public class BookServiceImpl implements BookService {
private BookDao bookDao = new BookDaoImpl();
@Override
public void save() {
System.out.println("service层调用了dao层的save方法");
bookDao.save();
}
}
在resources目录下新建spring配置文件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 将创建对象交由spring管理-->
<!--
id: bean的唯一标识
class: bean的数据类型
-->
<bean id="bookDao" class="com.spark.Dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.spark.service.impl.BookServiceImpl"/>
</beans>
编写测试方法,使用Spring提供的IOC容器获取对象
package com.spark.ioc;
import com.spark.Dao.BookDao;
import com.spark.service.BookService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* TestIocContainer class
* description: IOC Container
*
* @author Administrator
* @date 2023/6/24
*/
public class TestIocContainer {
@Test
public void testIocContainer() {
// 获取spring ioc容器
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取对象
BookDao bookDao = (BookDao) context.getBean("bookDao");
BookService bookService = (BookService) context.getBean("bookService");
bookDao.save();
bookService.save();
}
}
DI 入门案例
IOC入门案例中,Service实现类中还是通过new关键字去创建Dao的实现类从而去调用Dao接口的方法。需要将Service和Dao关联起来,将Service实现类中创建Dao实现类交由Spring进行管理
改写Service实现类
package com.spark.service.impl;
import com.spark.Dao.BookDao;
import com.spark.service.BookService;
/**
* BookServiceImpl class
* description: BookServiceImpl
*
* @author Administrator
* @date 2023/6/24
*/
public class BookServiceImpl implements BookService {
private BookDao bookDao;
@Override
public void save() {
System.out.println("service层调用了dao层的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 将创建对象交由spring管理-->
<!--
id: bean的唯一标识
class: bean的数据类型
-->
<bean id="bookDao" class="com.spark.Dao.impl.BookDaoImpl"/>
<!-- 将service与dao关联起来 -->
<bean id="bookService" class="com.spark.service.impl.BookServiceImpl">
<!--
property: 表示配置当前bean的属性
name属性表示配置哪一个具体的属性
ref属性表示参照哪一个bean
-->
<property name="bookDao" ref="bookDao"/>
</bean>
</beans>
Bean
基础配置
bean用于定义Spring核心容器管理的对象
语法格式
<beans>
<bean></bean>
<bean/>
</beans>
属性列表
- id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一
- class:bean的类型,即配置的bean的全路径类名
示例:
<bean id="bookDao" class="com.spark.Dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.spark.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
bean标签中的name属性可以为bean指定别名,别名可以定义多个,可以使用逗号、分号或者空格进行分隔
<bean id="bookDao" name="book books bookList" class="com.spark.Dao.impl.BookDaoImpl"/>
从Spring IOC容器中获取的Bean对象为单例对象。
bean标签中的scope属性可以为bean配置作用范围
- singleton:单例(默认)
- prototype:非单例
适合交给容器进行管理的bean
- 表现层对象
- 业务层对象
- 数据层对象
- 工具对象
不适合交给容器进行管理的bean
- 封装实体的域对象
Bean实例化
实例化Bean的三种方式
- 构造方法(常用)
- 静态工厂
- 实例工厂
创建一个新的Maven项目,实现实例化Bean的三种方式
构造方法
编写Dao接口和实现类
package com.spark.dao;
/**
* BookDao class
* description: BookDao
*
* @author Administrator
* @date 2023/6/24
*/
public interface BookDao {
void save();
}
package com.spark.dao.impl;
import com.spark.dao.BookDao;
/**
* BookDaoImpl class
* description: BookDaoImpl
*
* @author Administrator
* @date 2023/6/24
*/
public class BookDaoImpl implements BookDao {
public BookDaoImpl(){
System.out.println("BookDaoImpl constructor running");
}
@Override
public void save() {
System.out.println("BookDaoImpl save");
}
}
在resources目录下新建applicationContext.xml文件,并配置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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="bookDao" class="com.spark.dao.impl.BookDaoImpl"/>
</beans>
编写测试方法
package com.spark.bean;
import com.spark.dao.BookDao;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* TestBean class
* description: TestBean
*
* @author Administrator
* @date 2023/6/24
*/
public class TestBean {
// 测试Bean实例化方式一:构造器实例化
@Test
public void testBean(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookDao");
bookDao.save();
}
}
bean管理的类无参构造方法如果不存在,将抛出异常BeanCreationException
静态工厂
编写工厂类
package com.spark.factory;
import com.spark.dao.BookDao;
import com.spark.dao.impl.BookDaoImpl;
/**
* BookFactory class
* description: 静态工厂
*
* @author Administrator
* @date 2023/7/3
*/
public class BookFactory {
public static BookDao getBookDao(){
System.out.println("静态工厂....");
return new BookDaoImpl();
}
}
在applicationContext.xml中配置bean
<!-- 方式二:静态工厂 -->
<bean id="bookFactory" class="com.spark.factory.BookFactory" factory-method="getBookDao"/>
编写测试方法
// 测试Bean实例化方式二:静态工厂
@Test
public void testFactory(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookFactory");
bookDao.save();
}
实例工厂
编写实例工厂类
package com.spark.factory;
import com.spark.dao.BookDao;
import com.spark.dao.impl.BookDaoImpl;
/**
* BookFactoryBean class
* description: 实例工厂
*
* @author Administrator
* @date 2023/7/3
*/
public class BookFactoryBean {
public BookDao getBookDaoBean(){
return new BookDaoImpl();
}
}
在applicationContext.xml中配置bean
<!-- 方式三:实例工厂 -->
<bean id="bookFactoryBean" class="com.spark.factory.BookFactoryBean"/>
<bean id="bookDaoBean" factory-method="getBookDaoBean" factory-bean="bookFactoryBean"/>
编写测试方法
// 测试Bean实例化方式三:实例工厂
@Test
public void testFactoryBean(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookDaoBean");
bookDao.save();
}
由于配置bean时很麻烦,第一行是为了配合使用实际无意义,而且factory-method方法名不固定,每次都需要配置
改写实例工厂
package com.spark.factory;
import com.spark.dao.BookDao;
import com.spark.dao.impl.BookDaoImpl;
import org.springframework.beans.factory.FactoryBean;
/**
* BookFactoryBean class
* description: 实例工厂
*
* @author Administrator
* @date 2023/7/3
*/
public class BookFactoryBean implements FactoryBean<BookDao> {
@Override
public BookDao getObject() throws Exception {
return new BookDaoImpl();
}
@Override
public Class<?> getObjectType() {
return BookDao.class;
}
}
改写配置
<!-- 方式四:FactoryBean -->
<bean id="bookDaoBean" class="com.spark.factory.BookFactoryBean"/>
Bean生命周期控制
bean生命周期:bean从创建到销毁的整体过程
bean生命周期控制:在bean创建后到销毁前进行的操作
在bean初始化或者销毁前实现操作
方式一:在applicationContext.xml中配置
<bean id="bookDao" class="com.spark.dao.impl.BookDaoImpl" init-method="初始化方法名" destroy-method="销毁方法名"/>
方式二:实现接口
package com.spark.dao.impl;
import com.spark.dao.BookDao;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
/**
* BookDaoImpl class
* description: BookDaoImpl
*
* @author Administrator
* @date 2023/6/24
*/
public class BookDaoImpl implements BookDao, InitializingBean, DisposableBean {
@Override
public void destroy() throws Exception {
System.out.println("destory...");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("init...");
}
}
Bean的生命周期
- 初始化容器
- 创建对象(分配内存)
- 执行构造方法
- 执行属性注入(set操作)
- 执行bean初始化方法
- 使用bean
- 关闭业务操作
- 关闭/销毁容器
- 执行bean销毁方法
依赖注入
setter注入
① setter注入引用数据类型
编写Dao和Service
package com.spark.dao;
/**
* UserDao class
* description: UserDao
*
* @author Administrator
* @date 2023/7/3
*/
public interface UserDao {
void add();
}
package com.spark.dao.impl;
import com.spark.dao.UserDao;
/**
* UserDaoImpl class
* description: UserDaoImpl
*
* @author Administrator
* @date 2023/7/3
*/
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("添加用户...");
}
}
package com.spark.service;
/**
* UserService class
* description: UserService
*
* @author Administrator
* @date 2023/7/3
*/
public interface UserService {
void addUser();
}
package com.spark.service.impl;
import com.spark.dao.UserDao;
import com.spark.service.UserService;
/**
* UserServiceImpl class
* description: UserServiceImpl
*
* @author Administrator
* @date 2023/7/3
*/
public class UserServiceImpl implements UserService {
private UserDao userDao;
@Override
public void addUser() {
userDao.add();
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}
配置bean
<!-- setter注入引用数据类型 -->
<bean id="userDao" class="com.spark.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.spark.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
编写测试方法
// 测试Setter注入——引用数据类型
@Test
public void testUserService(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
userService.addUser();
}
② setter注入基本数据类型
UserServiceImpl类中添加成员变量,类型为基本数据类型并提供set方法
package com.spark.service.impl;
import com.spark.dao.UserDao;
import com.spark.service.UserService;
/**
* UserServiceImpl class
* description: UserServiceImpl
*
* @author Administrator
* @date 2023/7/3
*/
public class UserServiceImpl implements UserService {
private UserDao userDao;
private String dataSourceType;
private int dataSourceCount;
@Override
public void addUser() {
userDao.add();
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void setDataSourceType(String dataSourceType) {
this.dataSourceType = dataSourceType;
}
public void setDataSourceCount(int dataSourceCount) {
this.dataSourceCount = dataSourceCount;
}
public String toString() {
return dataSourceType + " " + dataSourceCount;
}
}
改写bean
<!-- setter注入引用数据类型 -->
<bean id="userDao" class="com.spark.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.spark.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"/>
<!-- setter注入基本数据类型 -->
<property name="dataSourceCount" value="10"/>
<property name="dataSourceType" value="mysql"/>
</bean>
构造器注入
改写UserServiceImpl类
package com.spark.service.impl;
import com.spark.dao.UserDao;
import com.spark.service.UserService;
/**
* UserServiceImpl class
* description: UserServiceImpl
*
* @author Administrator
* @date 2023/7/3
*/
public class UserServiceImpl implements UserService {
private UserDao userDao;
private String dataSourceType;
private int dataSourceCount;
public UserServiceImpl(UserDao userDao, String dataSourceType, int dataSourceCount){
this.userDao = userDao;
this.dataSourceType = dataSourceType;
this.dataSourceCount = dataSourceCount;
}
@Override
public void addUser() {
userDao.add();
}
public String toString() {
return dataSourceType + " " + dataSourceCount;
}
}
改写bean
<bean id="userDao" class="com.spark.dao.impl.UserDaoImpl"/>
<bean id="userService" class="com.spark.service.impl.UserServiceImpl">
<!-- 构造器注入引用数据类型 -->
<constructor-arg name="userDao" ref="userDao"/>
<!-- 构造器注入基本数据类型 -->
<constructor-arg name="dataSourceCount" value="10"/>
<constructor-arg name="dataSourceType" value="mysql"/>
</bean>
方式选择
- 强制依赖使用构造器注入,使用setter注入有概率不进行注入导致null对象出现
- 可选依赖使用setter注入进行,灵活性强
- Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
- 如果有必要可以二者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
- 自己开发的模块推荐使用setter注入
依赖自动装配
IOC容器根据bean依赖的资源在容器中自动查找并注入到bean中的过程称为自动装配
自动装配的方式
- 按类型(常用)
- 按名称
使用自动装配,在bean标签中使用autowire属性
① 按类型自动装配,autowire属性指定为byType
<bean id="userService" class="com.spark.impl.UserServiceImpl" autowire="byType"/>
② 按名称自动装配,autowire属性指定为byName
<bean id="userService" class="com.spark.impl.UserServiceImpl" autowire="byName"/>
自动装配注意点
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用
- 自动装配优先级低于setter注入和构造器注入,同时出现时自动装配失败
案例:数据源对象管理
之前Bean管理都是管理人员自己开发,如何管理第三方资源?
创建新的Maven工程,编写代码
package com.spark;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* ApplicationContext class
* description: ApplicationContext
*
* @author Administrator
* @date 2023/7/4
*/
public class App {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.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">
</beans>
pom.xml中引入三方依赖 c3p0
<!-- 引入c3p0 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- c3p0需要驱动包 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
在applicationContext.xml中配置管理三方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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 配置三方bean c3p0 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test"/>
<property name="user" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
获取DataSource对象
package com.spark;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import javax.sql.DataSource;
/**
* ApplicationContext class
* description: ApplicationContext
*
* @author Administrator
* @date 2023/7/4
*/
public class App {
public static void main(String[] args) {
// 解析applicationContext.xml
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 获取DataSource对象
DataSource dataSource = (DataSource) context.getBean("dataSource");
System.out.println(dataSource);
}
}
加载properties文件
在配置文件中配置bean时,属性值都是定死的,后续修改属性值不方便
Spring加载properties文件替换bean中的属性值
在applicationContext.xml的beans标签头中开启context命名空间
<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.xsd"
>
resources目录下新建配置文件jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
applicationContext.xml中使用context标签加载jdbc.properties
<!-- 加载properties文件 -->
<context:property-placeholder location="jdbc.properties"/>
改写bean标签中的property
<!-- 配置三方bean c3p0 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
拓展内容
① 不加载系统属性
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
② 加载多个properties文件
<context:property-placeholder location="jdbc.properties,jdbc2.properties"/>
③ 加载所有properties文件
<context:property-placeholder location="*.properties"/>
④ 加载properties文件标准格式
<context:property-placeholder location="classpath:jdbc.properties"/>
⑤ 从类路径或jar包中搜索并加载properties文件
<context:property-placeholder location="classpath*:*.properties"/>
容器
创建容器的方式
方式一:类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
方式二:文件路径加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");
方式三:加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml","bean2.xml");
获取bean的方式
方式一:使用bean名称获取
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
方式二:使用bean名称获取并指定类型
BookDao bookDao = ctx.getBean("bookDao",BookDao.class);
方式三:使用bean类型获取
BookDao bookDao = ctx.getBean(BookDao.class);
注解开发
使用注解开发简化在配置中配置bean
在实现类上加@Component注解
package com.spark.dao.impl;
import com.spark.dao.UserDao;
import org.springframework.stereotype.Component;
/**
* UserDaoImpl class
* description: UserDaoImpl
*
* @author Administrator
* @date 2023/7/5
*/
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDao save...");
}
}
在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.xsd">
<!-- 扫描注解 -->
<context:component-scan base-package="com.spark"/>
</beans>
获取bean
package com.spark;
import com.spark.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* App class
* description: App
*
* @author Administrator
* @date 2023/7/5
*/
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) ctx.getBean("userDao");
System.out.println(userDao);
}
}
Spring还提供了@Component注解的三个衍生注解
@Controller用于表现层bean定义
@Service用于业务层bean定义
@Repository用于数据层bean定义
纯注解开发
Spring3.0升级了纯注解开发模式,使用Java类替代配置文件
创建配置类代替applicationContext.xml配置文件
package com.spark.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
* SpringConfig class
* description: SpringConfig
*
* @author Administrator
* @date 2023/7/5
*/
@Configuration
@ComponentScan({"com.spark.dao","com.spark.service"})
public class SpringConfig {
}
@Configuration用于设定当前类为配置类
@ComponentScan用于设定扫描路径,此注解只能添加一次,多个数据使用数组格式
bean管理
bean的作用范围,可以使用@Scope定义
@Repository
@Scope("singleton")
public class BookDaoImpl implements BookDao{
}
bean生命周期控制使用@PostConstruct和@PreDestroy注解可以定义bean初始化和销毁前的操作
@Repository
@Scope("singleton")
public class BookDaoImpl implements BookDao{
public BookDaoImpl(){
System.out.println("BookDaoImpl constructory...");
}
@PostConstruct
public void init(){
System.out.println("book init ...");
}
@PreDestroy
public void destroy(){
System.out.println("book destroy ...");
}
}
@Scope注解取值
singleton:单例模式,全局有且仅有一个实例(默认类型)
prototype:原型模式,每次获取bean的时候会有一个新实例
request:针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP request内有效
session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效
globalsession:global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义
依赖注入
在类上使用@Autowired 注解实现自动装配
改写UserServiceImpl类
package com.spark.service.impl;
import com.spark.dao.UserDao;
import com.spark.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* UserServiceImpl class
* description: UserServiceImpl
*
* @author Administrator
* @date 2023/7/5
*/
@Service
public class UserServiceImpl implements UserService {
// 使用@Autowired实现自动装配
@Autowired
private UserDao userDao;
@Override
public void saveUser() {
userDao.save();
}
}
当UserDao有两个实现类,并且两个实现类注解中指定的名称相同,系统会因为无法识别装配哪个类型而抛出异常
package com.spark.dao.impl;
import com.spark.dao.UserDao;
import org.springframework.stereotype.Component;
/**
* UserDaoImpl class
* description: UserDaoImpl
*
* @author Administrator
* @date 2023/7/5
*/
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("UserDao save...");
}
}
package com.spark.dao.impl;
import com.spark.dao.UserDao;
import org.springframework.stereotype.Component;
/**
* UserDaoImpl class
* description: UserDaoImpl2
*
* @author Administrator
* @date 2023/7/5
*/
@Component("userDao")
public class UserDaoImpl2 implements UserDao {
@Override
public void save() {
System.out.println("UserDao save...2");
}
}
org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'userDao' for bean class [com.spark.dao.impl.UserDaoImpl2] conflicts with existing, non-compatible bean definition of same name and class [com.spark.dao.impl.UserDaoImpl
当出现上述情况时,可以将@Component里面指定的名称更改为 userDao和userDao2,之后使用@Autowired注解自动装配时配合 @Qualifier(名称) 注解去指定类型
package com.spark.service.impl;
import com.spark.dao.UserDao;
import com.spark.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
/**
* UserServiceImpl class
* description: UserServiceImpl
*
* @author Administrator
* @date 2023/7/5
*/
@Service
public class UserServiceImpl implements UserService {
// 使用@Autowired实现自动装配
@Autowired
@Qualifier("userDao2")
private UserDao userDao;
@Override
public void saveUser() {
userDao.save();
}
}
@Autowired注解注意点
- 自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
- 自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,需提供唯一的构造方法
@Qualifier注意点
- 该注解无法单独使用,必须配合@Autowired注解使用
使用@Value注解可以实现对简单类型进行注入
改写UserDaoImpl类
package com.spark.dao.impl;
import com.spark.dao.UserDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* UserDaoImpl class
* description: UserDaoImpl
*
* @author Administrator
* @date 2023/7/5
*/
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Value("测试简单类型注入")
private String name;
@Override
public void save() {
System.out.println("UserDao save..."+name);
}
}
@Value中的值更多的是动态注入,如何实现属性值灵活注入
在resources目录下新建一个jdbc.properties
name=MySQL数据库
在SpringConfig类中使用@PropertySource注解将文件引入
package com.spark.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* SpringConfig class
* description: SpringConfig
*
* @author Administrator
* @date 2023/7/5
*/
@Configuration
@ComponentScan({"com.spark.dao","com.spark.service"})
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
}
在UserDaoImpl类中使用@Value注解
package com.spark.dao.impl;
import com.spark.dao.UserDao;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* UserDaoImpl class
* description: UserDaoImpl
*
* @author Administrator
* @date 2023/7/5
*/
@Component("userDao")
public class UserDaoImpl implements UserDao {
@Value("${name}")
private String name;
@Override
public void save() {
System.out.println("UserDao save..."+name);
}
}
管理第三方bean
假设pom.xml中导入了三方依赖如druid,如果要对druid进行管理如何操作
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
创建一个配置类,在配置类中定义要获得管理的对象方法,并使用@Bean注解标识
package com.spark.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
/**
* DataSourceConfig class
* description: DataSourceConfig
*
* @author Administrator
* @date 2023/7/8
*/
public class DataSourceConfig {
// 定义一个方法要获得管理的对象
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
}
在SpringConfig类中导入管理第三方的配置类
两种方式
- 在管理第三方的配置类上加@Configuration,在SpringConfig类中的@ComponentScan注解中添加扫描路径
- 在SpringConfig类上使用@Import注解将管理第三方的配置类导入
package com.spark.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
/**
* SpringConfig class
* description: SpringConfig
*
* @author Administrator
* @date 2023/7/5
*/
@Configuration
@ComponentScan({"com.spark.dao","com.spark.service"})
@PropertySource("classpath:jdbc.properties")
// 将管理第三方配置类导入
@Import({DataSourceConfig.class})
public class SpringConfig {
}
编写测试方法测试
package com.spark;
import com.spark.config.SpringConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.sql.DataSource;
/**
* AppForAnnotation class
* description: AppForAnnotation
*
* @author Administrator
* @date 2023/7/5
*/
public class AppForAnnotation {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
}
}
Spring整合
Mybatis
在pom.xml中导入依赖
<?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.spark</groupId>
<artifactId>spring-mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
</dependencies>
</project>
在resources目录下新建jdbc.properties配置文件
jdbc.driverName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.userName=root
jdbc.password=root
创建jdbc配置类,获取数据源对象
package com.spark.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
/**
* JdbcConfig class
* description: JdbcConfig
*
* @author Administrator
* @date 2023/7/8
*/
public class JdbcConfig {
@Value("${jdbc.driverName}")
private String driverName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.userName}")
private String userName;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverName);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(password);
return dataSource;
}
}
创建mybatis配置类,获取SqlSessionFactory对象
package com.spark.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
/**
* MybatisConfig class
* description: MybatisConfig
*
* @author Administrator
* @date 2023/7/8
*/
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
// 设置别名包
sqlSessionFactoryBean.setTypeAliasesPackage("com.spark.entity");
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.spark.dao");
return msc;
}
}
创建Spring配置类,并导入配置
package com.spark.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
@Configuration
@ComponentScan("com.spark")
@Import({JdbcConfig.class})
@PropertySource({"classpath:jdbc.properties"})
public class SpringConfig{
}
编写实体类
package com.spark.entity;
/**
* UserEntity class
* description: UserEntity
*
* @author Administrator
* @date 2023/7/8
*/
public class UserEntity {
private int id;
private String username;
private String sex;
private int age;
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 getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "UserEntity{" +
"id=" + id +
", username='" + username + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
编写Dao接口
package com.spark.dao;
import com.spark.entity.UserEntity;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* UserDao class
* description: UserDao
*
* @author Administrator
* @date 2023/7/8
*/
public interface UserDao {
@Select("select * from users")
List<UserEntity> selectAllUsers();
}
package com.spark;
import com.spark.config.SpringConfig;
import com.spark.dao.UserDao;
import com.spark.entity.UserEntity;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
/**
* App class
* description: App
*
* @author Administrator
* @date 2023/7/8
*/
public class App {
public static void main(String[] args) {
ApplicationContext app = new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao dao = app.getBean(UserDao.class);
List<UserEntity> users = dao.selectAllUsers();
System.out.println(users);
}
}
JUnit
在pom.xml中导入依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
编写测试类
package com.spark;
import com.spark.config.SpringConfig;
import com.spark.dao.UserDao;
import com.spark.entity.UserEntity;
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.List;
/**
* AppTest class
* description: AppTest
*
* @author Administrator
* @date 2023/7/8
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= SpringConfig.class)
public class AppTest {
@Autowired
UserDao userDao;
@Test
public void testUsers(){
List<UserEntity> users = userDao.selectAllUsers();
System.out.println(users);
}
}
AOP
概述
AOP面向切面编程,是一种编程思想,指导开发者如何组织程序结构
作用:在不更改原始设计的基础上为其进行功能增强
- 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等;在SpringAOP中理解为方法的执行
- 切入点(Pointcut):匹配连接点的式子;在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 通知(Advice):在切入点处执行的操作,也就是共性功能;在SpringAOP中,功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面(Aspect):描述通知与切入点的对应关系
入门案例
导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
配置类开启AOP支持
package com.spark.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* SpringConfig class
* description: SpringConfig
*
* @author Administrator
* @date 2023/7/15
*/
@Configuration
@ComponentScan("com.spark")
// 开启AOP支持
@EnableAspectJAutoProxy
public class SpringConfig {
}
制作连接点
package com.spark.dao;
import org.springframework.stereotype.Repository;
/**
* UserDao class
* description: UserDao
*
* @author Administrator
* @date 2023/7/15
*/
@Repository
public interface UserDao {
void save();
void update();
}
package com.spark.dao.impl;
import com.spark.dao.UserDao;
import org.springframework.stereotype.Component;
/**
* UserDaoImpl class
* description: UserDaoImpl
*
* @author Administrator
* @date 2023/7/15
*/
@Component("userDao")
public class UserDaoImpl implements UserDao {
// 连接点方法
@Override
public void save() {
System.out.println("save...");
}
// 连接点方法
@Override
public void update() {
System.out.println("update...");
}
}
制作通知类和通知方法
package com.spark.aop;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
/**
* MyAdvice class
* description: MyAdvice
*
* @author Administrator
* @date 2023/7/15
*/
@Component
// 作为AOP处理
@Aspect
public class MyAdvice {
// 设置切入点,匹配连接方法
@Pointcut("execution(void com.spark.dao.UserDao.save())")
private void cut(){}
// 设置通知方法
@Before("cut()")
public void method(){
System.out.println("方法开始执行了...");
}
@After("cut()")
public void methodAfter(){
System.out.println("方法执行结束...");
}
}
切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际意义
编写测试方法
package com.spark;
import com.spark.config.SpringConfig;
import com.spark.dao.UserDao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* App class
* description: App
*
* @author Administrator
* @date 2023/7/15
*/
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
UserDao userDao = (UserDao) ctx.getBean("userDao");
userDao.save();
}
}
工作流程
- Spring容器启动
- 读取所有切面配置中的切入点
- 初始化Bean,判断bean对应的类中的方法是否匹配到任意切入点。匹配失败,则创建对象;匹配成功,则创建原始对象(目标对象)的代理对象
- 获取bean执行方法。获取的bean是目标对象,调用方法并执行,完成操作;获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与拓展的内容(通知方法),完成操作
目标对象(Target):原始功能去掉共性功能对应的类产生对象,这种对象是无法直接完成最终工作的
代理对象(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现
切入点表达式
切入点:要进行增强的方法
切入点表达式:要进行增强的方法的描述方式
切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数) 异常名)
execution(public User com.spark.service.UserService.findById(int))
动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点
访问修饰符:public,private等,可以省略
返回值
包名
类/接口名
方法名
参数
异常名:方法定义中抛出指定异常,可以省略
可以使用通配符描述切入点,快速描述
*:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现
execution(public * com.spark.*.UserService.find*(*))
匹配com.spark包下的任意包中的UserService类或接口中所有find开头的带至少一个参数的方法
..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写
execution(public User com..UserService.findById(..))
匹配com包下的任意包中的UserService类或接口中的findById的方法,参数个数任意
+:专用于匹配子类类型
execution(* *..*Service+.*(..))
匹配任意返回值,任意包下的以Service结尾的类或接口的子类的任意方法带有任意参数
通知类型
AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置
AOP通知共分为5种类型
- 前置通知(@Before),当前通知方法在原始切入点方法前执行
- 后置通知(@After),当前通知方法在原始切入点方法后执行
- 环绕通知(@Around),当前通知方法在原始切入点方法前后执行
- 返回后通知(@AfterReturning),当前通知方法在原始切入点方法正常执行完毕后执行
- 抛出异常后通知(@AfterThrowing),当前通知方法在原始切入点方法运行抛出异常后执行
环绕通知注意事项
- 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后通知添加通知
- 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法的执行
- 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定成Object类型
- 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object
- 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象
package com.spark.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* MyAdvice class
* description: MyAdvice
*
* @author Administrator
* @date 2023/7/15
*/
@Component
// 作为AOP处理
@Aspect
public class MyAdvice {
// 定义切入点,使用通配符匹配方法
// 匹配任意返回值类型,以e结尾的方法并且有任意参数的方法
@Pointcut("execution(* com.spark.dao.UserDao.*e(..))")
private void cut(){}
// 定义通知方法
// 前置通知
@Before("cut()")
public void beforeMethod(){
System.out.println("方法要开始执行了...");
}
// 后置通知
@After("cut()")
public void afterMethod(){
System.out.println("方法执行完毕了...");
}
// 环绕通知
@Around("cut()")
public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
// 执行原始方法
Object obj = joinPoint.proceed();
long end = System.currentTimeMillis();
System.out.println("方法耗时:"+(end-start)+"ms");
return obj;
}
// 返回后通知
@AfterReturning("cut()")
public void afterReturning(){
System.out.println("方法返回后执行...");
}
// 抛出异常后执行
@AfterThrowing("cut()")
public void afterThrowing(){
System.out.println("方法抛出异常后执行...");
}
}
通知获取数据
获取切入点方法的参数
- JoinPoint:适用于前置、后置、返回后、抛出异常后通知
- ProceedJoinPoint:适用于环绕通知
获取切入点方法返回值
- 返回后通知
- 环绕通知
获取切入点方法运行异常信息
- 抛出异常信息
- 环绕通知
① AOP通知获取参数数据
JoinPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
@Before("pt()")
public void before(JoinPoint jp){
Object [] args = jp.getArgs();
System.out.println(Arrays.toString(args));
}
ProceedJoinPoint是JoinPoint的子类
@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;
}
② AOP通知获取返回值数据
抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
returning指定的返回对象属性名必须与形参列表中的属性名一致
@AfterReturning(value="pt()",returning="ret")
public void afterReturning(Object ret){
System.out.println("afterReturning advice ..."+ret);
}
环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
@Around("pt()")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
Object ret = pjp.proceed();
return ret;
}
③ AOP通知获取异常数据
抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
@AfterThrowing(value="pt()",throwing="t")
public void afterThrowing(Throwable t){
System.out.println("afterThrowing advice ..."+t);
}
环绕通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象
@Around("pt()")
public Object around(ProceedingJoinPoint pjp){
Object ret = null;
try{
ret = pjp.proceed();
}catch(Throwable t){
t.printStackTrace();
}
return ret;
}
事务
事务作用:在数据层保障一系列的数据库操作同成功同失败
Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
入门案例
需求:实现两个账户间转账操作
分析:
- 数据层提供基础操作,指定账户减钱,指定账户加钱
- 业务层提供转账功能,调用减钱与加钱的操作
- 提供两个账号和操作金额执行转账操作
- 基于Spring整合mybatis环境搭建上述操作
引入依赖
<?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.spark</groupId>
<artifactId>spring-transcation</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.49</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</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.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
</dependencies>
</project>
resources目录下配置jdbc.properties
jdbc.driverName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.userName=root
jdbc.password=root
创建jdbc配置类,获取数据源对象和jdbc事务管理对象
package com.spark.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
/**
* JdbcConfig class
* description: jdbc数据源配置类
*
* @author Administrator
* @date 2023/7/22
*/
public class JdbcConfig {
@Value("${jdbc.driverName}")
private String driverName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.userName}")
private String userName;
@Value("${jdbc.password}")
private String password;
// 获取数据源对象
@Bean
public DataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverName);
dataSource.setUrl(url);
dataSource.setUsername(userName);
dataSource.setPassword(password);
return dataSource;
}
// 获取事务管理对象
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager ptm = new DataSourceTransactionManager();
ptm.setDataSource(dataSource);
return ptm;
}
}
创建Mybatis配置类获取SqlSessionFactoryBean
package com.spark.config;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
/**
* MybatisConfig class
* description: MybatisConfig
*
* @author Administrator
* @date 2023/7/22
*/
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
// 设置数据源
sqlSessionFactoryBean.setDataSource(dataSource);
// 设置别名包
sqlSessionFactoryBean.setTypeAliasesPackage("com.spark.entity");
return sqlSessionFactoryBean;
}
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.spark.dao");
return msc;
}
}
创建Spring配置类
package com.spark.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* SpringConfig class
* description: SpringConfig
*
* @author Administrator
* @date 2023/7/22
*/
@Configuration
@Import({JdbcConfig.class,MybatisConfig.class})
@PropertySource({"classpath:jdbc.properties"})
@ComponentScan("com.spark")
// 开启事务
@EnableTransactionManagement
public class SpringConfig {
}
定义Entity、Dao、Service实现接口逻辑
package com.spark.entity;
/**
* UserEntity class
* description: 用户
*
* @author Administrator
* @date 2023/7/22
*/
public class UserEntity {
private int id;
private String name;
private double money;
public UserEntity() {
}
public UserEntity(int id, String name, double money) {
this.id = id;
this.name = name;
this.money = money;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "UserEntity{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
package com.spark.dao;
import com.spark.entity.UserEntity;
import org.apache.ibatis.annotations.Update;
public interface UserDao {
@Update("update account set money=money-#{money} where id = #{id}")
void moneySubtract(UserEntity entity);
@Update("update account set money=money+#{money} where id = #{id}")
void moneyPlus(UserEntity entity);
}
package com.spark.service;
import com.spark.entity.UserEntity;
import org.springframework.transaction.annotation.Transactional;
/**
* UserService class
* description: UserService
*
* @author Administrator
* @date 2023/7/22
*/
public interface UserService {
// 添加事务注解
@Transactional
void transfer(UserEntity user1,UserEntity user2);
}
package com.spark.service.impl;
import com.spark.dao.UserDao;
import com.spark.entity.UserEntity;
import com.spark.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* UserServiceImpl class
* description: UserServiceImpl
*
* @author Administrator
* @date 2023/7/22
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
UserDao userDao;
@Override
public void transfer(UserEntity user1, UserEntity user2) {
System.out.println("A用户向B用户转了"+user1.getMoney()+"元");
userDao.moneySubtract(user1);
// 开启事务后遇到异常,执行的SQL进行回滚,钱不更改
int i = 1/0;
System.out.println("B用户接收到了A用户转来的"+user2.getMoney()+"元");
userDao.moneyPlus(user2);
}
}
编写测试方法
package com.spark;
import com.spark.config.SpringConfig;
import com.spark.entity.UserEntity;
import com.spark.service.UserService;
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;
/**
* App class
* description: App
*
* @author Administrator
* @date 2023/7/22
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= SpringConfig.class)
public class App {
@Autowired
UserService userService;
@Test
public void test(){
UserEntity user = new UserEntity(1,"A",1000);
UserEntity user2 = new UserEntity(2,"B",1000);
userService.transfer(user,user2);
}
}
注意:
- Spring注解式事务通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合;注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示中所有方法开启事务
- 事务管理器要根据实现技术进行选择,Mybatis框架使用的是JDBC事务
事务角色
事务管理员:发起事务方,在Spring中通常指代业务层开启事务方法
事务协调员:加入事务方,在Spring中通常代指数据层方法,也可以是业务层方法
入门案例中,数据层的添加和减少余额的方法就是两个事务,相互独立,不会因为某一个事务遇到异常从而将已经执行的SQL进行回滚。当业务层的方法使用@Transactional注解后,两个事务就会加入到Spring事务中由Spring管理为一个事务,SQL执行如果遇到异常,已执行的SQL便会回滚,做到同成功同失败
事务属性
属性 | 作用 | 示例 |
---|---|---|
readOnly | 设置是否只读 | readOnly = true 只读事务 |
timeout | 设置事务超时时间 | timeout = -1(永不超时) |
rollbackFor | 设置事务回滚异常(class) | rollbackFor = {NullPointException.class} |
rollbackForClassName | 设置事务回滚异常(String) | 同上格式为字符串 |
noRollbackFor | 设置事务不回滚异常(class) | noRollbackFor = {NullPointException.class} |
noRollbackForClassName | 设置事务不回滚异常(String) | 同上格式为字符串 |
propagation | 设置事务传播行为 |
事务传播行为
注意:事务回滚默认回滚Error(错误)和RuntimeExeception(运行时异常),其他异常不回滚,例如IOException
设置事务回滚
package com.spark.service;
import com.spark.entity.UserEntity;
import org.springframework.transaction.annotation.Transactional;
import java.io.IOException;
/**
* UserService class
* description: UserService
*
* @author Administrator
* @date 2023/7/22
*/
public interface UserService {
// 添加事务注解
// 当遇到IOException时回滚异常
@Transactional(rollbackFor = {IOException.class},timeout = -1)
void transfer(UserEntity user1,UserEntity user2);
}