Spring框架(二)
目录
一、AOP入门案例
二、AOP的相关概念
2.1 AOP的概念
2.2 AOP的优势
2.3 AOP的底层原理
三、Spring的AOP技术
3.1 AOP相关术语
3.2 AOP配置文件方式的入门
3.3 切入点表达式
3.4 AOP通知类型
四、Spring的AOP技术-注解方式
4.1 AOP注解方式的入门
4.2 纯注解方式
一、AOP入门案例
1. 创建mavenJava项目,引入开发坐标
<?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.qcby</groupId><artifactId>springAOP01</artifactId><version>1.0-SNAPSHOT</version><dependencies><!--spring核心依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><!--日志相关--><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><!--有单元测试的环境,Spring5 版本,Junit4.12 版本--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!--连接池 阿里巴巴 第三方--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version></dependency><!--mysql 驱动包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.25</version></dependency><!-- Spring 整合 Junit 测试的 jar 包 --><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version><scope>test</scope></dependency></dependencies></project>
package com.qcby.utils;
import com.alibaba.druid.pool.DruidDataSource;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;/*** 事务的工具类*/
public class TxUtils {private static DruidDataSource ds = null;// 使用ThreadLocal存储当前线程中的Connection对象private static ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();// 在静态代码块中创建数据库连接池static {try {// 通过代码创建C3P0数据库连接池ds = new DruidDataSource();ds.setDriverClassName("com.mysql.jdbc.Driver");ds.setUrl("jdbc:mysql:///spring_db");ds.setUsername("root");ds.setPassword("12345");} catch (Exception e) {throw new ExceptionInInitializerError(e);}}/*** @Method: getConnection* @Description: 从数据源中获取数据库连接* @Anthor:* @return Connection* @throws SQLException*/public static Connection getConnection() throws SQLException {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn == null) {// 从数据源中获取数据库连接conn = getDataSource().getConnection();// 将conn绑定到当前线程threadLocal.set(conn);}return conn;}/*** @Method: startTransaction* @Description: 开启事务* @Anthor:**/public static void startTransaction() {try {Connection conn = threadLocal.get();if (conn == null) {conn = getConnection();// 把 conn绑定到当前线程上threadLocal.set(conn);}// 开启事务conn.setAutoCommit(false);} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: rollback* @Description:回滚事务* @Anthor:*/public static void rollback() {try {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn != null) {// 回滚事务conn.rollback();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: commit* @Description:提交事务* @Anthor:*/public static void commit() {try {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn != null) {// 提交事务conn.commit();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: close* @Description:关闭数据库连接(注意,并不是真的关闭,而是把连接还给数据库连接池)* @Anthor:**/public static void close() {try {// 从当前线程中获取ConnectionConnection conn = threadLocal.get();if (conn != null) {conn.close();// 解除当前线程上绑定connthreadLocal.remove();}} catch (Exception e) {throw new RuntimeException(e);}}/*** @Method: getDataSource* @Description: 获取数据源* @Anthor:* @return DataSource*/public static DataSource getDataSource() {// 从数据源中获取数据库连接return ds;}}
3. 编写持久层、业务层相关代码
package com.qcby.dao;import com.qcby.model.Account;import java.sql.SQLException;/*** AOP 概念引入需要的持久层接口* 模拟转账业务*/
public interface AccountDao {public void save(Account account) throws SQLException;
}package com.qcby.dao;import com.qcby.model.Account;
import com.qcby.utils.TxUtils;
import org.springframework.stereotype.Repository;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;/*** AOP 概念引入需要的持久层接口* 模拟转账业务*/
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao{public void save(Account account) throws SQLException {
// // 把数据存储到数据库中
// // 先获取到连接
// Connection conn = null;
// try {
// conn = TxUtils.getConnection();
// // 编写 sql 语句
// String sql = "insert into account values (null,?,?)";
// // 预编译 SQL 语句
// PreparedStatement stmt = conn.prepareStatement(sql);
// // 设置值
// stmt.setString(1,account.getName());
// stmt.setDouble(2,account.getMoney());
// // 执行操作
// stmt.executeUpdate();
// // 关闭资源 ,conn 不能关闭
// stmt.close();
// } catch (SQLException e) {
// e.printStackTrace();
// }finally {
// //TxUtils.close();
// }Connection conn = TxUtils.getConnection();// 编写 sql 语句String sql = "insert into account values (null,?,?)";// 预编译 SQL 语句PreparedStatement stmt = conn.prepareStatement(sql);// 设置值stmt.setString(1,account.getName());stmt.setDouble(2,account.getMoney());// 执行操作stmt.executeUpdate();// 关闭资源 ,conn 不能关闭stmt.close();}
}
package com.qcby.service;import com.qcby.model.Account;import java.sql.SQLException;/*** AOP 概念引入需要的业务层接口* 模拟转账业务*/
public interface AccountService {//转账逻辑方法 account1扣款,account2增款public void saveAll(Account account1,Account account2) throws SQLException;
}package com.qcby.service.Impl;import com.qcby.dao.AccountDao;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;import java.sql.SQLException;/*** AOP 概念引入需要的业务层实现类* 模拟转账业务*/
@Service
public class AccountServiceImpl implements AccountService{@Autowired@Qualifier("accountDao")private AccountDao accountDao;public void saveAll(Account account1, Account account2) throws SQLException {
// try {
// //开启事务
// TxUtils.startTransaction();
// //转账业务 扣款
// accountDao.save(account1);
// //模拟异常
// //int i=10/0;
// //收款
// accountDao.save(account2);
// TxUtils.commit();
// System.out.println("转账成功!");
// }catch (Exception e){
// System.out.println("转账失败!!!");
// TxUtils.rollback();
// e.printStackTrace();
// }finally {
// TxUtils.close();
// }//转账业务 扣款accountDao.save(account1);//模拟异常//int i=10/0;//收款accountDao.save(account2);}
}
4. 编写Spring核心配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd"><!-- 开启注解扫描 --><context:component-scan base-package="com.qcby"/><bean id="accountService" class="com.qcby.service.Impl.AccountServiceImpl"/></beans>
5. 生成代理对象
代理对象——因为每进行一次转账业务,都需要在业务层(service层)进行开启事务、提交事务、出现异常回滚事务以及关闭资源的操作,所以我们使用代理对象进行优化处理,在代理对象中进行上述的相关操作(4种操作)以及对目标方法进行增强。有了代理对象后,在业务层的实现类中,我们就可以只进行转账的操作,事物的相关处理交给代理对象去处理。
package com.qcby.jdkUtils;import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;/*** 传入目标对象,生成该对象的代理对象,返回。* 对目标对象的方法进行增强*/
public class JdkProxy {/*** 获取代理对象,返回,增强目标对象的方法* @param accountService* @return*/public static Object getProxy(final AccountService accountService){/*** 使用 Jdk 的动态代理生成代理对象*/Object proxy = Proxy.newProxyInstance(JdkProxy.class.getClassLoader(),accountService.getClass().getInterfaces(), new InvocationHandler(){/*** 调用代理对象的方法,invoke 方法就会去执行* @param proxy* @param method* @param args* @return* @throws Throwable*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result = null;try {// 开启事务TxUtils.startTransaction();// 对象目标对象的方法进行增强result = method.invoke(accountService,args);// 提交事务TxUtils.commit();} catch (Exception e) {e.printStackTrace();TxUtils.rollback();} finally {// 关闭资源TxUtils.close();}return result;}});return proxy;}
}
6. 编写测试类
package com.qcby.springAOPTest;import com.qcby.jdkUtils.JdkProxy;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import java.sql.SQLException;@RunWith(value = SpringJUnit4ClassRunner.class) // 运行单元测试
@ContextConfiguration(value = "classpath:applicationContext.xml")
public class Demo1 {@Autowiredprivate AccountService accountService;@Testpublic void run() throws SQLException {
// ApplicationContext ac = new
// ClassPathXmlApplicationContext("applicationContext.xml");
// // 获取 service 对象
// AccountService accountService = (AccountService) ac.getBean("accountService");
// Account account1 = new Account();
// account1.setName("张三");
// account1.setMoney(1000d);
// Account account2 = new Account();
// account2.setName("李四");
// account2.setMoney(1000d);
// accountService.saveAll(account1,account2);Account account1 = new Account();account1.setName("张三");account1.setMoney(1000d);Account account2 = new Account();account2.setName("李四");account2.setMoney(1000d);//代理//生成代理对象Object proxyobj=JdkProxy.getProxy(accountService);//强转AccountService proxy= (AccountService) proxyobj;//调用代理对象方法proxy.saveAll(account1,account2);}
}
在上述代码中实现了新增账户的操作,若在创建账户的过程中发生异常则事务会执行回滚操作,若正常执行则事务进行提交,数据库中会正确插入两个账户的信息。
二、AOP的相关概念
2.1 AOP的概念
AOP(Aspect Oriented Programming,面向切面编程)是一种编程范式,旨在通过预编译方式或运行期动态代理实现程序功能的统一维护。AOP最早由AOP联盟提出,Spring框架将其引入并遵循AOP联盟的规范。
2.2 AOP的优势
- 减少重复代码:通过切面将横切关注点(如日志、事务管理)从业务逻辑中分离出来。
- 提高开发效率:开发者可以专注于业务逻辑,而不必重复编写相同的代码。
- 便于维护:横切关注点的修改只需在切面中进行,降低了代码的耦合度。
2.3 AOP的底层原理
- JDK动态代理
- 为目标对象创建代理类的字节码文件;
- 使用ClassLoader将字节码文件加载到JVM;
- 创建代理类实例对象,执行对象的目标方法。
- CGLIB代理:通过继承目标类生成代理类。
三、Spring的AOP技术
3.1 AOP相关术语
Joinpoint(连接点):被拦截到的点,Spring中通常指方法(spring 只支持方法类型的连接点)。——在上述案例中相当于save方法
Pointcut(切入点):定义对哪些Joinpoint进行拦截。——也可以理解为save方法
Advice(通知/增强):拦截到Joinpoint后要做的事情就是通知,包括前置通知、后置通知、异常通知、最终通知、环绕通知(切面要完成的功能)。——相当于开启事务、提交事务、出现异常回滚事务以及关闭资源
Target(目标对象):代理的目标对象。——accountService
Weaving(织入):将增强应用到目标对象来创建新的代理对象的过程。
Proxy(代理):被AOP织入增强后产生的代理类。
Aspect(切面):切入点和通知的结合。
3.2 AOP配置文件方式的入门
1. 创建mavenJava项目,引入相关依赖
<?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.qcby</groupId><artifactId>springAOP02</artifactId><version>1.0-SNAPSHOT</version><dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><!-- AOP 联盟 --><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!-- Spring Aspects --><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.0.2.RELEASE</version></dependency><!-- aspectj --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.3</version></dependency></dependencies></project>
2. Spring配置文件,引入AOP的schema约束
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"></beans>
3. 创建包结构,编写具体的接口和实现类
package com.qcby.service;public interface UserService {public void save();
}package com.qcby.service.impl;import com.qcby.service.UserService;public class UserServiceImpl implements UserService {public void save() {System.out.println("业务层:保存用户...");}
}
将目标类配置到 Spring 中
<!--管理 bean 对象-->
<bean id="userService" class="com.qcby.service.impl.UserServiceImpl"></bean>
4. 定义切面类并进行配置
package com.qcby.aspect;/*** 自定义切面类 = 切入点(表达式) + 通知(增强的代码)*/
public class MyXmlAspect {/*** 通知*/public void log(){// 发送手机短信// 发送邮件/记录日志/事务管理System.out.println("增强的方法执行了...");}}
在配置文件中定义切面类和AOP配置:
<!--配置切面类,把该类交给 IOC 容器管理--><bean id="myXmlAspect" class="com.qcby.aspect.MyXmlAspect"></bean><!--配置 AOP 的增强--><aop:config><!--配置切面 = 切入点 + 通知组成--><aop:aspect ref="myXmlAspect"><!--AOP 的通知类型前置通知:目标方法执行前,进行增强。<aop:before method="log" pointcut="execution(*com.qcby.*.*ServiceImpl.save*(..))" />最终通知:目标方法执行成功或者失败,进行增强。<aop:after method="log" pointcut="execution(*com.qcby.*.*ServiceImpl.save*(..))" />后置通知:目标方法执行成功后,进行增强。<aop:after-returning method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />异常通知:目标方法执行失败后,进行增强。<aop:after-throwing method="log" pointcut="execution(* com.qcby.*.*ServiceImpl.save*(..))" />环绕通知:目标方法执行前后,都可以进行增强。目标对象的方法需要手动执行。--><!--前置通知:UserServiceImpl 的 save 方法执行前,会增强--><aop:before method="log"pointcut="execution(public void com.qcby.service.impl.UserServiceImpl.save())" /></aop:aspect></aop:config>
5. 编写测试类
package com.qcby.springAOP02Test;import com.qcby.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;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo {@Autowiredprivate UserService userService;/*** 测试*/@Testpublic void run1(){userService.save();}}
3.3 切入点表达式
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
-
修饰符(可选):如
public
、private
,通常可省略。 -
返回值类型(必填):int String 通用的写法,可用
*
通配任意返回值(如*
表示任意返回值)。 -
包名.类名(必填):
-
包名从最外层开始写,不可省略
com
等顶级包。 -
可用
*
替代部分包名或类名(如com.qcby.*
匹配qcby
下所有类)。 -
用
..
表示当前包及其子包(如com.qcby..*
匹配qcby
及其子包下所有类)。
-
-
方法名(必填):可用
*
匹配任意方法名,或用*前缀
、后缀*
匹配部分名称(如save*
匹配save
、saveUser
)。 -
参数列表(必填):
-
()
表示无参数。 -
(..)
表示任意数量和类型的参数。 -
(*)
表示单个任意参数。 -
(..,String)
表示最后一个参数为String
,前面可有多个任意参数。
-
比较通用的表达式:execution(public * com.qcby.*.*ServiceImpl.*(..))
3.4 AOP通知类型
通知类型 | xml方式 | 注解 | 说明 |
---|---|---|---|
前置通知 | aop:before | @Before | 目标方法执行前执行(进行增强) |
最终通知 | aop:after | @After | 目标方法执行后(无论是否成功),进行增强 |
后置通知 | aop:after-returning | @AfterReturning | 目标方法成功执行后执行 |
异常通知 | aop:after-throwing | @AfterThrowing | 目标方法抛出异常时/执行失败后执行 |
环绕通知 | 无直接xml支持 | @Around | 目标方法执行前后执行,目标对象的方法需要手动执行(手动调用方法) |
四、Spring的AOP技术-注解方式
4.1 AOP注解方式的入门
1. 创建maven工程,引入相关依赖
2. 编写切面类
给切面类添加注解 @Aspect,编写增强的方法,使用通知类型注解声明
package com.qcby.demo3;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;/*** 切面类*/
@Component // 把该类交给 IOC 去管理
@Aspect // 声明是切面类 == <aop:aspect ref="myXmlAspect">
public class MyAnnoAspect {/*** 通知的方法*/@Before(value = "execution(public void com.qcby.service.impl.UserServiceImpl.save())")public void log(){System.out.println("增强了...");}
}
3. 在Spring配置文件开启自动代理(启用注解扫描和AOP自动代理)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"><!--管理 bean 对象--><bean id="userService" class="com.qcby.service.impl.UserServiceImpl"></bean><!-- 开启自动代理 --><aop:aspectj-autoproxy/></beans>
4. 编写测试类
package com.qcby.springAOP02Test;import com.qcby.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;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Demo3 {@Autowiredprivate UserService userService;/*** 测试*/@Testpublic void run1(){userService.save();}}
4.2 纯注解方式
1. 纯注解配置类
package com.qcby.demo4;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration // 配置类
@ComponentScan(value = "com.qcby.demo4") // 扫描包
@EnableAspectJAutoProxy // 开启自动代理 == <aop:aspectj-autoproxy/>
public class SpringConfig {}
2. 定义切面类
package com.qcby.demo4;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Component // 将切面类纳入IOC容器管理
@Aspect // 声明为切面类
public class MyAnnoAspect {// 前置通知:目标方法执行前触发@Before("execution(* com.qcby.demo4.OrderServiceImpl.save(..))")public void log() {System.out.println("【AOP前置通知】增强了目标方法...");}
}
3. 编写具体的接口和实现类
package com.qcby.demo4;public interface OrderService {public void save();
}package com.qcby.demo4;import org.springframework.stereotype.Service;@Service // 标记为Service层组件
public class OrderServiceImpl implements OrderService{public void save() {System.out.println("业务逻辑:保存订单成功!");}
}
4. 编写测试类实现
package com.qcby.springAOP02Test;import com.qcby.demo4.OrderService;
import com.qcby.demo4.SpringConfig;
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;@RunWith(SpringJUnit4ClassRunner.class) // 使用Spring测试运行器
@ContextConfiguration(classes = SpringConfig.class) // 加载纯注解配置类
public class demo4 {@Autowiredprivate OrderService orderService;@Testpublic void run1() {orderService.save();}
}
关键点说明
-
配置类:
-
@Configuration
声明为配置类,替代XML文件。 -
@ComponentScan
指定包扫描路径,确保切面类和目标类被IOC容器管理。 -
@EnableAspectJAutoProxy
启用AOP自动代理,无需XML配置。
-
-
切面类:
-
@Aspect
声明切面,@Component
确保切面类被扫描到。 -
使用
@Before
定义前置通知,通过表达式execution(...)
指定切入点。
-
-
测试类:
-
@RunWith(SpringJUnit4ClassRunner.class)
集成Spring测试环境。 -
@ContextConfiguration(classes = SpringConfig.class)
加载纯注解配置。 -
注入
OrderService
并调用方法,验证切面逻辑是否触发。
-