Spring第二章

SpringAOP


1、入门案例

SpringAOP是spring的有一个核心的地方了,我觉得作为一种辅助工具是特别合适的。

通过一个业务场景来看下对应的使用场景以及利用springAOP所能够带来的好处。

最常见的就是银行转账的案例,所以我也来用这个例子来说明:

准备工作:JDK8+maven+Idea

controller层:

@RestController                                                                                                                                                  
@Slf4j                                                                                                                                                           
public class TransferController {                                                                                                                                
                                                                                                                                                                 
@Autowired                                                                                                                                                       
private TransferService transferService;                                                                                                                         
                                                                                                                                                                 
@RequestMapping("/transfer")                                                                                                                                     
public Result result(@RequestBody TransferRequestDTO transferRequestDTO){                                                                                        
log.info("接收到的参数是:{}",transferRequestDTO.toString());                                                                                                            
Integer flag = null;                                                                                                                                             
try {                                                                                                                                                            
flag = this.transferService.transfer(transferRequestDTO);                                                                                                        
if (flag>0){                                                                                                                                                     
return new Result(200,"转账成功",flag);                                                                                                                              
}                                                                                                                                                                
return new Result(300,"转账失败","金额不足");                                                                                                                            
} catch (Exception e) {                                                                                                                                          
e.printStackTrace();                                                                                                                                             
return new Result(300,"转账失败","用户名不存在");                                                                                                                          
}                                                                                                                                                                
}                                                                                                                                                                
}                                                                                                                                                                

service层:

@Service                                                                                                                                                         
@Slf4j                                                                                                                                                           
public class TransferService {                                                                                                                                   
                                                                                                                                                                 
@Autowired                                                                                                                                                       
private TransferMapper transferMapper;                                                                                                                           
                                                                                                                                                                 
@Autowired                                                                                                                                                       
private TrasactionManager trasactionManager;                                                                                                                     
                                                                                                                                                                 
public Integer transfer(TransferRequestDTO transferRequestDTO) {                                                                                                 
// 首先进行参数校验                                                                                                                                                      
if (transferRequestDTO == null) {                                                                                                                                
throw new RuntimeException("请求参数为空");                                                                                                                           
}                                                                                                                                                                
String from = transferRequestDTO.getFrom();                                                                                                                      
String to = transferRequestDTO.getTo();                                                                                                                          
if (from == null || to == null) {                                                                                                                                
throw new RuntimeException("无法查询到对应用户");                                                                                                                         
}                                                                                                                                                                
Double money = transferMapper.findMoneyByFrom(from);                                                                                                             
Double dtoMoney = transferRequestDTO.getMoney();                                                                                                                 
if (money - dtoMoney >= 0) {                                                                                                                                     
try {                                                                                                                                                            
trasactionManager.startTransaction();                                                                                                                            
transferMapper.transfer(from,-dtoMoney);                                                                                                                         
transferMapper.transfer(to,dtoMoney);                                                                                                                            
trasactionManager.commitAndClose();                                                                                                                              
} catch (Exception e) {                                                                                                                                          
trasactionManager.rollbackAndClose();                                                                                                                            
log.error("转账出现问题{}",e);                                                                                                                                         
}                                                                                                                                                                
return 1;                                                                                                                                                        
}                                                                                                                                                                
return -1;                                                                                                                                                       
}                                                                                                                                                                
}                                                                                                                                                                

mapper层:

@Mapper                                                                                                                                                          
public interface TransferMapper {                                                                                                                                
                                                                                                                                                                 
Double findMoneyByFrom(@Param("username") String username);                                                                                                      
                                                                                                                                                                 
void transfer(@Param("username") String username, @Param("money") Double money);                                                                                 
}                                                                                                                                                                

entity层:

@Data                                                                                                                                                            
@NoArgsConstructor                                                                                                                                               
@AllArgsConstructor                                                                                                                                              
public class TransferRequestDTO {                                                                                                                                
private String from;                                                                                                                                             
private String to;                                                                                                                                               
private Double money;                                                                                                                                            
}                                                                                                                                                                

utils层:

/**                                                                                                                                                              
* 操作数据库保证安全的时候需要保证使用的是同一个数据库连接                                                                                                                                   
*/                                                                                                                                                               
@Component                                                                                                                                                       
public class TrasactionManager implements ApplicationContextAware {                                                                                              
                                                                                                                                                                 
private static final ThreadLocal<Connection> CONNECTION_THREAD_LOCAL = new ThreadLocal<>();                                                                      
                                                                                                                                                                 
private  DataSource dataSource;                                                                                                                                  
                                                                                                                                                                 
/**                                                                                                                                                              
* 开启事务                                                                                                                                                           
*/                                                                                                                                                               
public  void startTransaction() {                                                                                                                                
try {                                                                                                                                                            
Connection connection = dataSource.getConnection();                                                                                                             
connection.setAutoCommit(false);                                                                                                                                 
CONNECTION_THREAD_LOCAL.set(connection);                                                                                                                         
} catch (SQLException throwables) {                                                                                                                              
throwables.printStackTrace();                                                                                                                                    
}                                                                                                                                                                
}                                                                                                                                                                
                                                                                                                                                                 
/**                                                                                                                                                              
* 操作成功,提交并关闭                                                                                                                                                     
*/                                                                                                                                                               
public  void commitAndClose() {                                                                                                                                  
Connection connection = null;                                                                                                                                    
try {                                                                                                                                                            
connection =CONNECTION_THREAD_LOCAL.get();                                                                                                                       
connection.commit();                                                                                                                                             
connection.close();                                                                                                                                              
CONNECTION_THREAD_LOCAL.remove();                                                                                                                                
} catch (SQLException throwables) {                                                                                                                              
throwables.printStackTrace();                                                                                                                                    
}                                                                                                                                                                
}                                                                                                                                                                
                                                                                                                                                                 
/**                                                                                                                                                              
* 操作失败,事务回滚                                                                                                                                                      
*/                                                                                                                                                               
public  void rollbackAndClose() {                                                                                                                                
Connection connection = null;                                                                                                                                    
try {                                                                                                                                                            
connection =CONNECTION_THREAD_LOCAL.get();                                                                                                                       
connection.rollback();                                                                                                                                           
connection.close();                                                                                                                                              
CONNECTION_THREAD_LOCAL.remove();                                                                                                                                
} catch (SQLException throwables) {                                                                                                                              
throwables.printStackTrace();                                                                                                                                    
}                                                                                                                                                                
}                                                                                                                                                                
                                                                                                                                                                 
@Override                                                                                                                                                        
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {                                                                 
this.dataSource = applicationContext.getBean(DataSource.class);                                                                                                  
}                                                                                                                                                                
}                                                                                                                                                                

但是这样达不到对应的效果。因为尽管是同一个连接,但是没有回滚成功。

使用动态带来来实现,这里放上关键的代码:

@Component("accountServiceProxyFactory")                                                                                                                         
public class AccountServiceProxyFactory {                                                                                                                        
                                                                                                                                                                 
@Autowired                                                                                                                                                       
                                                                                                                                                                 
                                                                                                                                                                 
@Autowired                                                                                                                                                       
private TransactionManager txManager;                                                                                                                            
                                                                                                                                                                 
public AccountService createProxy(){                                                                                                                             
return (TransferService) Proxy.newProxyInstance(                                                                                                                 
transferService.getClass().getClassLoader(),                                                                                                                     
transferService.getClass().getInterfaces(),                                                                                                                      
new InvocationHandler() {                                                                                                                                        
@Override                                                                                                                                                      
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                                                                              
Object result = null;                                                                                                                                            
                                                                                                                                                                 
try {                                                                                                                                                            
//开启事务                                                                                                                                                           
txManager.startTransaction();                                                                                                                                    
//调用目标对象的方法:执行转账                                                                                                                                                 
result = method.invoke(transferService, args);                                                                                                                   
//提交事务                                                                                                                                                           
txManager.commitAndClose();                                                                                                                                      
} catch (Exception e) {                                                                                                                                          
e.printStackTrace();                                                                                                                                             
//回滚事务                                                                                                                                                           
txManager.rollbackAndClose();                                                                                                                                    
}                                                                                                                                                                
return result;                                                                                                                                                   
}                                                                                                                                                                
}                                                                                                                                                                
);                                                                                                                                                               | }                                                                                                                                                                
}                                                                                                                                                                

可以看到由原始的

try {                                                                                                                                                            
trasactionManager.startTransaction();                                                                                                                            
transferMapper.transfer(from,-dtoMoney);                                                                                                                         
transferMapper.transfer(to,dtoMoney);                                                                                                                            
trasactionManager.commitAndClose();                                                                                                                              
} catch (Exception e) {                                                                                                                                          
trasactionManager.rollbackAndClose();                                                                                                                            
log.error("转账出现问题{}",e);                                                                                                                                         
}                                                                                                                                                                

这里的开启事务和关闭事务和这里的业务逻辑是没有关系的,但是仍然摆在了这里;

使用反射来进行改进:

try {                                                                                                                                                            
//开启事务                                                                                                                                                           
txManager.startTransaction();                                                                                                                                    
//调用目标对象的方法:执行转账                                                                                                                                                 
result = method.invoke(transferService, args);                                                                                                                   
//提交事务                                                                                                                                                           
txManager.commitAndClose();                                                                                                                                      
} catch (Exception e) {                                                                                                                                          
e.printStackTrace();                                                                                                                                             
//回滚事务                                                                                                                                                           
txManager.rollbackAndClose();                                                                                                                                    
}                                                                                                                                                                

将业务逻辑中的代码写到了这里,这是一种非常可取的方式。所以spring中的AOP也是利用反射的方式来进行操作的。

动态代理的优势就在于不修改源码的情况下,进行功能增强。

还有一种最常见的使用方式:打印日志以及事务管理功能。

2、SpringAOP

2.1、简单概述

Spring的AOP,底层是通过动态代理实现的。在运行期间,通过代理技术动态生成代理对象,代理对象方法执行时进行功能的增强介入,再去调用目标方法,从而完成功能增强。 |

常用的动态代理技术有:

  • JDK的动态代理:基于接口实现的
  • cglib的动态代理:基于子类实现的

Spring的AOP采用了哪种代理方式?

  • 如果目标对象有接口,就采用JDK的动态代理技术
  • 如果目标对象没有接口,就采用cglib技术

我们通常使用的是JDK的代理方式。

2.2、相关概念

  • 目标对象(Target):要代理的/要增强的目标对象。

  • 代理对象(Proxy):目标对象被AOP织入增强后,就得到一个代理对象

  • 连接点(JoinPoint):能够被拦截到的点,在Spring里指的是方法

目标类里,所有能够进行增强的方法,都是连接点

  • 切入点(PointCut):要对哪些连接点进行拦截的定义

已经增强的连接点,叫切入点

  • 通知/增强(Advice):拦截到连接点之后要做的事情

对目标对象的方法,进行功能增强的代码

  • 切面(Aspect):是切入点和通知的结合

  • 织入(Weaving):把增强/通知 应用到 目标对象来创建代理对象的过程。Spring采用动态代理技术织入,而AspectJ采用编译期织入和装载期织入

这些概念用一张图来进行总结一下:

代码是从上到下执行的,切面是横向拦截这些从上向下执行的代码。上面白色的是连接点,染红的是切点。

切面是一个通知组合起来的模块,通知是需要进行增强的代码,织入是将红色结点连接起来生成动态代理对象的方式。

明确两点原则:

在开发中我们需要做的事情:

1、编写出核心代码;

2、编写切面和通知;

3、将切点和通知连接起来;

Spring做的事情:

1、将切点和通知织入生成代理对象;

2、监控切点方法的执行,如果监控到了有对应的通知和切点结合,那么将会使用动态代理对象来执行对应的业务代码和通知方法;

2.3、切面和通知分类

看一个写好的版本:

xml配置版本的有点麻烦,所以直接使用注解版的。

import org.aspectj.lang.ProceedingJoinPoint;                                                                                                                     
import org.aspectj.lang.annotation.*;                                                                                                                            
import org.springframework.stereotype.Component;                                                                                                                
                                                                                                                                                                 
//声明当前类是切面类:把切入点和通知,在这个类里进行织入,当前类就成为了一个切面类                                                                                                                       
@Aspect                                                                                                                                                          
@Component("myAdvice")                                                                                                                                           
public class MyAdvice {                                                                                                                                          
                                                                                                                                                                 
@Before("execution(void com.itheima.impl..*.save())")                                                                                                            
public void before(){                                                                                                                                            
System.out.println("前置通知...");                                                                                                                                   
}                                                                                                                                                                
                                                                                                                                                                 
@AfterReturning("execution(void com.itheima.impl..*.save()))")                                                                                                   
public void afterReturning(){                                                                                                                                    
System.out.println("后置通知");                                                                                                                                      
}                                                                                                                                                                
                                                                                                                                                                 
@After("execution(void com.itheima.impl..*.save())")                                                                                                             
public void after(){                                                                                                                                             
System.out.println("最终通知");                                                                                                                                      
}                                                                                                                                                                
                                                                                                                                                                
@AfterThrowing("execution(void com.itheima.impl..*.save())")                                                                                                     
public void afterThrowing(){                                                                                                                                     
System.out.println("抛出异常通知");                                                                                                                                    
}                                                                                                                                                                
                                                                                                                                                                 
/**                                                                                                                                                              
* @param pjp ProceedingJoinPoint:正在执行的切入点方法对象。                                                                                                                   
* @return 切入点方法的返回值                                                                                                                                              
*/                                                                                                                                                               
@Around("execution(void com.itheima.impl..*.save())")                                                                                                            
public Object around(ProceedingJoinPoint pjp) throws Throwable {                                                                                                 
System.out.println("环绕:前置通知...");                                                                                                                                
                                                                                                                                                                 
//切入点方法执行                                                                                                                                                        
Object proceed = pjp.proceed();                                                                                                                                  
                                                                                                                                                                 
System.out.println("环绕:后置通知...");                                                                                                                                
                                                                                                                                                                 
return proceed;                                                                                                                                                  
}                                                                                                                                                                
}                                                                                                                                                                

注意:在一个配置类上加上下面的注解

@EnableAspectJAutoProxy //开启AOP自动代理                                                                                                                              
通知的类型
名称 注解 说明
前置通知 @Before 通知方法在切入点方法之前执行
后置通知 @AfterRuturning 通知方法在切入点方法之后执行
异常通知 @AfterThrowing 通知方法在抛出异常时执行
最终通知 @After 通知方法无论是否有异常,最终都执行
环绕通知 @Around 通知方法在切入点方法之前、之后都执行

切点表达式的抽取

  • 同xml的AOP一样,当多个切面的切入点表达式相同时,可以将切入点表达式进行抽取;
  • 抽取方法是:
  • 在增强类(切面类,即被@Aspect标的类)上增加方法,在方法上使用@Pointcut注解定义切入点表达式,
  • 在增强注解中引用切入点表达式所在的方法
@Aspect                                                                                                                                                          
@Component("myAdvice1")                                                                                                                                          
public class MyAdvice1 {                                                                                                                                         
                                                                                                                                                                
//定义切入点表达式,目的是为了被其他引用而已                                                                                                                                          
@Pointcut("execution(void com.itheima.service..*.save())")                                                                                                       
public void myPointcut(){}                                                                                                                                       
                                                                                                                                                                 
//引用切入点表达式                                                                                                                                                      
//完整写法:com.itheima.aop.MyAdvice.myPointcut()                                                                                                                     
//简单写法:myPointcut(), 引入当前类里定义的表达式,可以省略包类和类名不写                                                                                                                    
@Before("myPointcut()")                                                                                                                                          
public void before(){                                                                                                                                            
System.out.println("前置通知...");                                                                                                                                   
}                                                                                                                                                                
                                                                                                                                                                 
@AfterReturning("myPointcut()")                                                                                                                                  
public void afterReturning(){                                                                                                                                    
System.out.println("后置通知");                                                                                                                                      
}                                                                                                                                                                
                                                                                                                                                                 
@After("myPointcut()")                                                                                                                                           
public void after(){                                                                                                                                             
System.out.println("最终通知");                                                                                                                                      
}                                                                                                                                                                
                                                                                                                                                                
@AfterThrowing("myPointcut()")                                                                                                                                   
public void afterThrowing(){                                                                                                                                     
System.out.println("抛出异常通知");                                                                                                                                    
}                                                                                                                                                                
                                                                                                                                                                 
/*@Around("myPointcut()")                                                                                                                                        
public Object around(ProceedingJoinPoint pjp) throws Throwable {                                                                                                 
System.out.println("前置通知...");                                                                                                                                   
                                                                                                                                                                
//切入点方法执行                                                                                                                                                        
Object proceed = pjp.proceed();                                                                                                                                 
                                                                                                                                                                 
System.out.println("后置通知...");                                                                                                                                   |                                                                                                                                                                  
return proceed;                                                                                                                                                  
}*/                                                                                                                                                              
}                                                                                                                                                                

3、Spring的事务管理

首先来看下事务的传播行为隔离级别

3.1、隔离级别

数据库中的知识点:

1、读未提交;----->脏读,读到了别的事务未提交的数据;                                                                                                                                   
2、读已提交;----->不可重复读。针对的是某一行记录                                                                                                                                     
3、可重复读;------>幻读------>针对的是一张表的记录                                                                                                                                
4、串行化;-------->事务一一实现,安全                                                                                                                                         

MySQL的默认级别是第三种,可重复读;

3.2、传播行为

传播行为就是为了解决业务逻辑层中的方法调用方法的问题的。有七种方式,我就不在一一举例了,选择最常用的来进行讲解吧。

PROPAGATION_REQUIRED:需要有事务,默认的(我们通常来使用的)。

如果有事务,就使用这个事务,如果没有事务,就创建事务。

3.3、事务超时和是否只读

在事务运行的时候,我们通常需要来设置事务的超时后事务自动回滚的情况。默认值是-1,表示的没有超时限制;如果,那么以秒为单位来进行设置;

对于事务来说,分为增删改查,那么针对于查的,设置为只读即可;

这里需要注意下失效的问题!

失效的问题是业务层方法调用业务层方法,然后对于这两个方法来说,两个事务不同的话那么就会有冲突。

有了冲突,就得来解决,使用谁的事务来解决冲突。

3.4、Demo

@Configuration                                                                                                                                                   
@ComponentScan("com.guang")                                                                                                                                      
@EnableTransactionManagement // 自动byType注入事务管理器                                                                                                                  
public class AppConfig{                                                                                                                                          
                                                                                                                                                                 
//连接池                                                                                                                                                            
@Bean                                                                                                                                                            
public DataSource dataSource() throws PropertyVetoException {                                                                                                   
ComboPooledDataSource dataSource = new ComboPooledDataSource();                                                                                                  
dataSource.setDriverClass("com.mysql.jdbc.Driver");                                                                                                              
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/jdbc");                                                                                                      
dataSource.setUser("root");                                                                                                                                      
dataSource.setPassword("root");                                                                                                                                 
return dataSource;                                                                                                                                               
}                                                                                                                                                                
                                                                                                                                                                 
//JdbcTemplate                                                                                                                                                   
@Bean                                                                                                                                                            
public JdbcTemplate jdbcTemplate(DataSource dataSource){                                                                                                         
return new JdbcTemplate(dataSource);                                                                                                                             
}                                                                                                                                                                
                                                                                                                                                                 
// 事务管理器,需要使用到连接池                                                                                                                                                
// 还需要注意这里的实现有点不同:                                                                                                                                               
// 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。如果你添加的是 spring-boot-starter-data-     // jpa 依赖,框架会默认注入 JpaTransactionManager 实例。 
// 直接自己来进行指定,因为后期方便维护                                                                                                                                            
@Bean                                                                                                                                                            
public PlatformTransactionManager transactionManager(DataSource dataSource){                                                                                     
return new DataSourceTransactionManager(dataSource);                                                                                                             
}                                                                                                                                                                
}                                                                                                                                                                
```                                                                                                                                                              |                                                                                                                                                                  
对应的业务逻辑层:                                                                                                                                                        
                                                                                                                                                                 
```java                                                                                                                                                          
@Override                                                                                                                                                        
@Transactional(isolation = Isolation.DEFAULT, propagation = Propagation.REQUIRED, readOnly = false)                                                              
public void transfer(String from, String to, Double money) {                                                                                                     
Account fromAccount = accountDao.findByName(from);                                                                                                               
Account toAccount = accountDao.findByName(to);                                                                                                                   
fromAccount.setMoney(fromAccount.getMoney() - money);                                                                                                            
toAccount.setMoney(toAccount.getMoney() + money);                                                                                                                
accountDao.edit(fromAccount);                                                                                                                                    
//int i = 1/0;                                                                                                                                                   
accountDao.edit(toAccount);                                                                                                                                      
}                                                                                                                                                                

结果这里发现,我们就是利用了数据源配置了一个事务管理器,然后在核心方法上加了注解,就能够完成我们想要的效果,很明显,底层是通过动态代理的方式来实现的。

posted @ 2021-08-05 12:52  雩娄的木子  阅读(43)  评论(0编辑  收藏  举报