1、AOP
AOP:【动态代理】指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;
Spring的AOP实现是基于 AspectJ 的,要在 Spring 中声明 AspectJ 切面,只需要在 IOC 容器中将切面声明为 Bean 实例. 当在 Spring IOC 容器中初始化 AspectJ 切面之后,Spring IOC 容器就会为那些与 AspectJ 切面相匹配的 Bean 创建代理。
AspectJ 支持 5 种类型的通知注解:
@Before: 前置通知, 在方法执行之前执行
@After: 后置通知, 在方法执行之后执行 。
@AfterRunning: 返回通知, 在方法返回结果之后执行
@AfterThrowing: 异常通知, 在方法抛出异常之后
@Around: 环绕通知, 围绕着方法执行
1.1 AspectJ实现AOP
使用步骤:
1、导入aop模块;Spring AOP:(spring-aspects),以及对应的maven坐标;
2、定义一个业务逻辑类(MathCalculator);在业务逻辑运行的时候将日志进行打印(方法之前、方法运行结束、在方法返回结果之后执行、方法出现异常, 围绕着方法执行);
3、定义一个日志切面类(LogAspects):切面类里面的方法需要动态感知 MathCalculator.div 运行到哪里然后执行;
4、给切面类的目标方法标注何时何地运行(通知注解);
5、将切面类和业务逻辑类(目标方法所在类)都加入到容器中;
6、必须告诉Spring哪个类是切面类(给切面类上加一个注解:@Aspect);
7、给配置类中加 @EnableAspectJAutoProxy 【开启基于注解的aop模式】
1.1.1 导入AspectJ的注解
创建maven工程,导入Spring和aspects的坐标以及junit:
<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencys>
1.1.2 创建业务类
package com.spring.annotation.aop.bean;
public class MathCalculator {
public int div(int a, int b){
System.out.println("业务方法执行了...");
return a/b;
}
}
1.1.3 创建切面类
注意:@Aspect 注解表明这是一个切面类;
package com.spring.annotation.aop.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import java.util.Arrays;
import java.util.List;
@Aspect //表明这是一个切面
public class MyLogAspects {
@Pointcut("execution(public int com.spring.annotation.aop.bean.MathCalculator.div(..))")
public void logPointcut(){}
@Before("logPointcut()")//前置通知
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("目标方法执行前,会执行该方法,目标方法是:"+methodName+",目标方法参数是:"+list);
}
@After("logPointcut()")//后置通知,不管是否发生异常都会执行该方法
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
List<Object> list = Arrays.asList(joinPoint.getArgs());
System.out.println("在执行了目标方法后,会执行该方法,目标方法是:"+methodName+",目标方法参数是:"+list);
}
//返回通知,方法正常执行后会执行该方法,可以拿到方法的返回值,与异常通知不共存
// JoinPoint一定要出现在参数表的第一位,通过returning = "result"和形参 Object result 就可以拿到返回值
@AfterReturning(value = "logPointcut()",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
System.out.println("目标方法正常执行并返回了结果了,返回的结果是:"+result);
}
//异常通知,目标方法发生异常会执行该方法,可以拿到方法的异常信息,与返回通知不共存
@AfterThrowing(value = "logPointcut()",throwing = "exception")
public void afterThrowingMethod(JoinPoint joinPoint,Exception exception){
System.out.println("目标方法出现了异常,异常信息是:"+exception);
}
//环绕通知,一个通知基本上可以顶前面的四个
@Around(value = "logPointcut()")
public Object aroungMethod(ProceedingJoinPoint proceedingJoinPoint){
//定义返回对象
Object result = null;
String methodName = proceedingJoinPoint.getSignature().getName();
List<Object> args = Arrays.asList(proceedingJoinPoint.getArgs());
try{
//相当于前置通知
System.out.println("前置通知:方法执行之前...");
result=proceedingJoinPoint.proceed();//调用目标方法
//相当于后置通知,但是发生了异常,就不会执行这句话
System.out.println("后置通知:方法执行之后...");
} catch (Throwable throwable) {
throwable.printStackTrace();
//相当于异常通知
System.out.println("异常通知:目标方法发生异常了...");
throw new RuntimeException(throwable);
}
//相当于返回通知
System.out.println("返回通知:方法正常执行,返回结果...");
return result;//返回结果
}
}
1.1.4 创建Spring配置类
配置类上两个注解要重视:
@Configuration:表明这是一个Spring的配置类,相当于xml配置的applicationContext.xml
@EnableAspectJAutoProxy:开启AspectJ的注解支持,相当于xml配置时的:<aop:aspectj-autoproxy/>
package com.spring.annotation.aop.config;
import com.spring.annotation.aop.aspect.MyLogAspects;
import com.spring.annotation.aop.bean.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy
public class SpringAopConfig {
@Bean
public MathCalculator mathCalculator(){
return new MathCalculator();
}
@Bean
public MyLogAspects myLogAspects(){
return new MyLogAspects();
}
}
1.1.5 测试
package com.spring.annotation.aop;
import com.spring.annotation.aop.bean.MathCalculator;
import com.spring.annotation.aop.config.SpringAopConfig;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringAopTest {
@Test
public void testAop(){
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(SpringAopConfig.class);
applicationContext.refresh();
MathCalculator mathCalculator = (MathCalculator) applicationContext.getBean("mathCalculator");
mathCalculator.div(2,0);
}
}
执行结果:
前置通知:方法执行之前...
目标方法执行前,会执行该方法,目标方法是:div,目标方法参数是:[2, 0]
业务方法执行了...
java.lang.ArithmeticException: / by zero
1.2 AOP原理
1、@EnableAspectJAutoProxy 开启AOP功能
2、@EnableAspectJAutoProxy 会给容器中注册一个组件 AnnotationAwareAspectJAutoProxyCreator
3、AnnotationAwareAspectJAutoProxyCreator是一个后置处理器
4、容器的创建流程:
1、registerBeanPostProcessors()注册后置处理器;创建AnnotationAwareAspectJAutoProxyCreator对象
2、finishBeanFactoryInitialization()初始化剩下的单实例bean
1)、创建业务逻辑组件和切面组件
2)、AnnotationAwareAspectJAutoProxyCreator拦截组件的创建过程
3)、组件创建完之后,判断组件是否需要增强
是:切面的通知方法,包装成增强器(Advisor);给业务逻辑组件创建一个代理对象(cglib)
5、执行目标方法:
1、代理对象执行目标方法
2、CglibAopProxy.intercept();
1)、得到目标方法的拦截器链(增强器包装成拦截器MethodInterceptor)
2)、利用拦截器的链式机制,依次进入每一个拦截器进行执行;
3)、效果:
正常执行:前置通知-》目标方法-》后置通知-》返回通知
出现异常:前置通知-》目标方法-》后置通知-》异常通知
2、事务管理
可以参照 之前的那篇通过xml配置实现JdbcTemplate和事务管理的文章。
这里演示如何通过注解来实现JdbcTemplate,以及进行注解事务管理,相关的数据库环境参照:之前的那篇通过xml配置实现JdbcTemplate和事务管理的文章。
2.1 事务管理的步骤
1、添加spring-jdbc的坐标依赖和数据源以及驱动依赖;
2、在配置类上添加注解:@EnableTransactionManagement 开启基于注解的事务管理功能
3、在配置类中,向IOC容器中注册:数据源,JdbcTemplate和事务管理器PlatformTransactionManager
4、在事务方法上添加@Transactional注解
2.1.1 添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/c3p0/c3p0 -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.44</version>
</dependency>
2.1.2 编写配置类
@EnableTransactionManagement //开启注解事务支持
PlatformTransactionManager transactionManager()
package com.spring.annotation.tx;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.beans.PropertyVetoException;
@Configuration
@ComponentScan("com.spring.annotation.tx")
@EnableTransactionManagement //开启注解事务支持
public class SpringTxConfig {
@Bean//配置数据源
public DataSource dataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/spring4");
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setUser("root");
dataSource.setPassword("123");
return dataSource;
}
@Bean//配置JdbcTemplate
public JdbcTemplate jdbcTemplate() throws PropertyVetoException {
//Spring对@Configuration类会特殊处理;给容器中加组件的方法,dataSource() 多次调用都只是从容器中找组件
return new JdbcTemplate(dataSource());
}
@Bean//注册事务管理器到容器中
public PlatformTransactionManager transactionManager() throws PropertyVetoException {
return new DataSourceTransactionManager(dataSource());
}
}
2.1.3 业务方法添加@Transactional注解
1、Dao接口和实现类
package com.spring.annotation.tx;
public interface BookShopDao {
//根据书号获取书的单价
public int findBookPriceByIsbn(String isbn);
//更新数的库存. 使书号对应的库存 - 1
public void updateBookStock(String isbn);
//更新用户的账户余额: 使 username 的 balance - price
public void updateUserAccount(String username, int price);
}
package com.spring.annotation.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
public int findBookPriceByIsbn(String isbn) {
String sql = "select price from book where isbn = ?";
Integer price = jdbcTemplate.queryForObject(sql, Integer.class, isbn);
return price;
}
public void updateBookStock(String isbn) {
//检查书的库存是否足够, 若不够, 则抛出异常
String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
int stock = jdbcTemplate.queryForObject(sql2, Integer.class, isbn);
if(stock == 0){
throw new RuntimeException("库存不足!");
}
String sql = "update book_stock set stock = stock - 1 where isbn = ?";
jdbcTemplate.update(sql,isbn);
}
public void updateUserAccount(String username, int price) {
//验证余额是否足够, 若不足, 则抛出异常
String sql2 = "SELECT balance FROM account WHERE username = ?";
int balance = jdbcTemplate.queryForObject(sql2, Integer.class, username);
if(balance < price){
throw new RuntimeException("余额不足!");
}
String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
jdbcTemplate.update(sql, price, username);
}
}
2、Service接口和实现类
package com.spring.annotation.tx;
public interface BookShopService {
//购买书本的方法
public void purchase(String username, String isbn);
}
package com.spring.annotation.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {
@Autowired
private BookShopDao bookShopDao;
@Transactional
public void purchase(String username, String isbn) {
//查询书本单价
int price = bookShopDao.findBookPriceByIsbn(isbn);
//更新库存
bookShopDao.updateBookStock(isbn);
//更新余额
bookShopDao.updateUserAccount(username,price);
}
}
2.1.4 测试
package com.spring.annotation.tx;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class SpringTxTest {
@Test
public void testTx(){
AnnotationConfigApplicationContext applicationContext =
new AnnotationConfigApplicationContext();
applicationContext.register(SpringTxConfig.class);
applicationContext.refresh();
BookShopService bookShopService =
(BookShopService) applicationContext.getBean("bookShopService");
bookShopService.purchase("tom","0001");
}
}