Spring AOP和事务学习
一、Spring AOP介绍
1.AOP(Aspect Oriented Programming) 面向切面编程,一种编程范式,指导开发者如何组织程
序结构。 AOP 是一种编程思想,编程思想主要的内容就是指导程序员该 如何编写程序。AOP则利用一种称为"横切的技术,剖开对象内部,并将公共行为封装到可重用模块,从而减少重复代码,降低耦合。
二、Aop的作用
1.不修改源码的情况下,进行功能增强,通过动态代理实现的
2.减少重复代码,提高开发效率,降低耦合度方便维护
三、AOP相关术语
目标对象(Target):需要代理的/要增强的目标对象。
代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象
连接点(JoinPoint):能够被拦截到的点,在Spring里指的是方法
切入点(PointCut):要对哪些连接点进行拦截的定义
通知/增强(Advice):拦截到连接点之后要做的事情
切面(Aspect):是切入点和通知的结合。 告诉Spring的AOP:要对哪个方法,做什么样的增强
织入(Weaving):把增强/通知应用到目标对象来创建代理对象的过程
大多数用于描述的AOP功能的术语并不直观,所以为了理解AOP,我们必须了解这些术语。
四、AOP快速入门
1.需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能方法)
- 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
-
切点表达式的写法
语法:
execution([修饰符]返回值类型包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用星号*代表任意
- 包名与类名之间一个点.代表当前包下的类,两个点…表示当前包及其子包下的类
- 参数列表可以使用两个点…表示任意个数,任意类型的参数列表
例如:
execution(public void com.iflytek.aop.Target.method()) execution(void com.iflytek.aop.Target.* ( ..)) execution(* com.iflytek.aop.*.*( ..)) execution(* com.iflytek.aop..*.* (..)) execution(* *..*.*(..))
-
通知的类型
通知的配置语法:
<!--配置文件方式--> <aop:通知类型 method="切面类中方法名" pointcut="切点表达式"></aop:通知类型>
//注解方式 @通知注解("切点表达式")
名称 标签 注解 说明 前置通知 <aop:before> @Before 用于配置前置通知。指定增强的方法在切入点方法之前执行 后置通知 <aop:AfterReturning> @AfterReturning 用于配置后置通知。指定增强的方法在切入点方法之后执行 环绕通知 <aop:Around> @Around 用于配置环绕通知。指定增强的方法在切入点方法之前和之后都执行 异常抛出通知 <aop:AfterThrowing> @AfterThrowing 用于配置异常抛出通知。指定增强的方法在出现异常时执行 最终通知 <aop:After> @After 用于配置最终通知。无论增强方式执行是否有异常都会执行
四、实验步骤
-
基于配置文件方式实现日志打印
(1)导入AOP相关坐标
<dependencies> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.6</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.3.8</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.1</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.8</version> </dependency> </dependencies>
(2)创建DAO接口和实现类(内部有切点)
-
在com.cqgcxy.service包下创建目标接口StudyService
public interface StudyService {void study();
-
}
- 在com.cqgcxy.service.impl包下创建目标类StudyServiceImpl
public class PhoneServiceImpl implements PhoneService {
@Override
public void study() {
System.out.println("SpringAOP学习");
}
```
(3)定义通知类(切面类),制作通知方法
- 在com.cqgcxy.aop包下创建切面类LogManager
package com.cqgcxy.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import java.time.LocalDateTime;
public class LogManager {
public Object printLog(ProceedingJoinPoint pjp) throws Throwable {
Signature signature = pjp.getSignature();
System.out.println(LocalDateTime.now()+"==============>"+signature+"===============>开始执行!");
Object proceed = pjp.proceed();
System.out.println(LocalDateTime.now()+"==============>"+signature+"===============>结束执行!");
return proceed;
}
}
(4)将目标类和切面类的对象创建权交给spring
<bean id="logManager" class="com.cqgcxy.aop.LogManager"></bean>
<bean id="studyService" class="com.cqgcxy.service.impl.StudyServiceImpl"></bean>
(5)在applicationContext.xml中配置织入关系
-
方式一:
<aop:config> <aop:aspect ref="logManager"> <aop:around method="printLog" pointcut="execution(* com.cqgcxy.service..*.*(..))"></aop:around> </aop:aspect> </aop:config>
-
方式二:
<aop:config> <aop:aspect ref="logManager"> <aop:pointcut id="logPointcut" expression="execution(* com.cqgcxy.service..*.*(..))"/> <aop:around method="printLog" pointcut-ref="logPointcut"></aop:around> </aop:aspect> </aop:config>
(6)测试代码
package com.cqgcxy.service.impl;
import com.cqgcxy.service.PhoneService;
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 static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class PhoneServiceImplTest {
@Autowired
private StudyService studyService;
@Test
public void study() {
studyService.study();
}
}
-
基于注解方式实现日志打印
(1)创建目标接口和目标类(内部有切点)
-
在com.cqgcxy.service包下创建目标接口PhoneService
public interface StudyService { void study();}
-
在com.cqgcxy.service.impl包下创建目标类StudyServiceImp
@Service
public class StudyServiceImpl implements StudyService {
@Override
public void study() {
System.out.println(“SpringAop学习”);
}
(2)创建切面类(内部有增强方法),并配置织入关系
- 方式一:
@Aspect
@Component
public class LogManager {
@Around(“execution(* com.cqgcxy.service….(…))”)
public Object printLog(ProceedingJoinPoint pjp) throws Throwable {
Signature signature= pjp.getSignature();
System.out.println(LocalDateTime.now()+“=>"+ signature + "=>开始执行方法");
Object proceed= pjp.proceed();
System.out.println(LocalDateTime.now()+"=>"+ signature + "=>结束执行方法”);
return proceed;
}
- 方式二:
- @Aspect
@Component
public class LogManager {
@Pointcut(“execution(* com.cqgcxy.service….(…))”)
public void logPointcut(){
}
@Around(“LogManager.logPointcut()”)
public Object printLog(ProceedingJoinPoint pjp) throws Throwable {
Signature signature= pjp.getSignature();
System.out.println(LocalDateTime.now()+“=>"+ signature + "=>开始执行方法");
Object proceed= pjp.proceed();
System.out.println(LocalDateTime.now()+"=>"+ signature + "=>结束执行方法”);
return proceed;
}
(3)将目标类和切面类的对象创建权交给spring,并在配置文件中开启组件扫描和AOP的自动代理
@Configuration
@ComponentScan(“com.cqgcxy”)
@Component
@EnableAspectJAutoProxy
public class SpringConfiguration {
}(4)测试
package com.cqgcxy.service.impl; import com.cqgcxy.service.PhoneService; 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 static org.junit.Assert.*;
-
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(“classpath:applicationContext.xml”)
public class StudyServiceImplTest {
@Autowired
private StudyService studyService;
@Test
public void dawj() {
studyService.study();
}
}
``
五、实验结果
- 基于配置文件方式和注解方式实现日志打印的测试效果截图如下:
至此,AOP的基础入门就完成了!!
Spring事务管理
一、事务管理的介绍
1.1:事务的简介:
举个简单的例子,
转账业务会有两次数据层的调用,一次是加钱一次是减钱
把事务放在数据层,加钱和减钱就有两个事务
没办法保证加钱和减钱同时成功或者同时失败
这个时候就需要将事务放在业务层进行处理。
Spring为了管理事务,提供了一个平台事务管理器PlatformTransactionManager。
1.2事务的概念:事务是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合。
1.3事务的作用:在数据层保障一系列的数据库操作同成功同失败。
事务的ACID原则
1.原则性:一个事务已经是一个不可再分割的工作单位。事务中的全部操作要么都做;要么都不做
2.一致性:事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定。
3.隔离性:事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。
4.持久性:一个事务一旦提交,它对数据库中数据的改变会永久存储起来。其他操作不会对它产生影响
事务的操作
开启事务(一组操作开始前,开启事务):start transaction/begin;
提交事务(这组操作全部成功后,提交事务):commit;
回滚事务(中间任何一个操作出现异常,回滚事务):rollback;
事务隔离级别
1.1.Spring事务隔离级别:隔离级别由低到高【读未提交】=>【读已提交】=>【可重复读】=>【序列化操作】
1.2:事务的传播行为
指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
实现事务管理:
使用银行转账业务完成:
1.创建数据库:
DROP TABLE IF EXISTS account
;
CREATE TABLE account
(
id
int NOT NULL AUTO_INCREMENT,
name
varchar(11) COLLATE utf8mb4_general_ci NOT NULL,
money
decimal(10,2) NOT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT INTO account
VALUES (‘1’, ‘messi’, ‘100.00’);
INSERT INTO account
VALUES (‘2’, ‘pep’, ‘100.00’);
2.导入相关依赖:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</atifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.8</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.6</version>
</dependency>
</dependencies>
3.实体类的创建:
public class Account implements Serializable {
private Integer id;
private String name;
private Double money;
get和set方法此处省去
4.创建DAo接口和实现类
1.1创建目标接口:
public interface AccountDao {
void inMoney(String name,Double money);
void outMoney(String name,Double money);
}
1.2创建目标类:
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jt;
public void inMoney(String name, Double money) {
jt.update("update account set money = money + ? where name = ?",money,name);
}
public void outMoney(String name, Double money) {
jt.update("update account set money = money - ? where name = ?",money,name);
}
5.创建服务层接口和实现类:
服务层接口
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);
int i=3/0;
accountDao.inMoney(in, money);
}
6.配置连接数据库文件:
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///gcxy_teach?serverTimezone=Asia/Shanghai&characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
7.配置SPring配置文件
通过xml方式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
![请添加图片描述](https://img-blog.csdnimg.cn/69964456e0e34ee493ed8d50e0ce4e47.png)
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.cqgcxy"/>
<context:property-placeholder location="classpath*:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置通知 事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置通知的AOP织入-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.cqgcxy.service.impl.*.*(..))"></aop:advisor>
</aop:config>
</beans>
通过注解方式:
(1):Spring配置类SpringConfiguration
@Configuration
@ComponentScan("com.cqgcxy")
@Import({JdbcConfig.class})
//启动平台事务管理
@EnableTransactionManagement
public class SpringConfiguration {
}
(2)配置jdbcconfig类
@PropertySource("classpath:jdbc.properties")
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;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
JdbcTemplate jdbcTemplate=new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//配置事务管理器,mybatis使用的是jdbc事务
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
(3):业务层实现修改方法:
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Transactional
@Override
public void transfer(String out, String in, Double money) {
accountDao.inMoney(in,money);
accountDao.outMoney(out,money);
}
5.进行代码测试:
@RunWith(SpringJUnit4ClassRunner.class)
//1.xml配置文件方式
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountServiceImplTest {
@Autowired
private AccountService accountService;
@Test
public void transfer() {
accountService.transfer("messi","pep",20.0);
}
}
学习心得:我进一步了解了Spring AOP和事务管理在实际开发中的重要性和作用。Spring AOP通过面向切面的编程方式,使得代码的横切关注点(如日志等)能够被集中处理,而不需要在每个业务方法中重复编写这些逻辑。这大大减少了代码的冗余,提升了代码的可维护性和可读性。Spring AOP还提供了环绕等多种增强,在目标方法执行前后可以自定义处理逻辑。对于事务管理,它对于确保数据操作一致性和完整性是至关重要的。在方法执行时,如果发生异常或者满足其他事务回滚的条件,事务管理器会自动回滚事务,保证了数据的一致性。而如果方法执行成功,则事务管理器会提交事务,将数据永久性地保存到数据库中。我相信通过熟练掌握和应用Spring AOP和事务管理,在实际的开发中将对项目的质量和开发效率带来明显的提升。