springAOP
一 spring的概念的介绍
- AOP: 全称是 Aspect Oriented programming 即: 面向切面编程.
- 简单的说:就是我们需要在很多功能上加上一段相同的新的代码或功能,我们可以使用spring帮我们封装的动态代理技术去实现.
- AOP的作用、优势、实现方式
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
优势:减少重复代码,提高开发效率,并且便于维护
实现方式:底层封装动态代理技术
二 动态代理技术
- 基于接口的动态代理(JDK)
- 基于子类的动态代理(CGLIB)
- 注意(AOP使用哪种代理技术,是框架根据是否需要实现接口类型来选择.)
1.JDK动态代理
接口IProducer
/**
* 对生产厂家要求的接口
*/
public interface IProducer {
/**
* 销售
* @param money
*/
public void saleProduct(float money);
/**
* 售后
* @param money
*/
public void afterService(float money);
}
实现类(真实对象)
public class ProducerImpl implements IProducer {
/**
* 销售
* @param money
*/
@Override
public void saleProduct(float money) {
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
@Override
public void afterService(float money) {
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
使用JDK动态代理创建代理对象
@Test
public void producerTest() {
final ProducerImpl producer = new ProducerImpl();
/**
* newProxyInstance方法参数
*参数1: ClassLoader,类加载器它是用于加载代理对象字节码的.和被代理对象使用相同的类加载器.固定写法.
*参数2: Class[],字节码数组它是用于让代理对象和被代理对象有相同方法.固定写法
*参数3: InvocationHandler,用于提供增强的代码,它是让我们写如何代理.
* 我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的.
*/
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),producer.getClass().getInterfaces(), new InvocationHandler() {
/**
* 执行被代理对象的任何接口方法都会经过该方法
*
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object object = null;
//获取方法执行的参数
Float money = (Float) args[0];//表示获取第一个参数
//判断当前方法是不是销售方法,"saleProduct"为销售方法名称
if ("saleProduct".equals(method.getName())) {
//是销售方法执行提成操作,代理商提取20%的利润
object = method.invoke(producer,money * 0.8f);
}
return object;
}
});
//调用销售方法
proxyProducer.saleProduct(1000f);
}
注意:Jdk实现的动态代理,只能给有接口的类做代理
2.CGLIB动态代理
创建一个类,不用实现任何接口,也可以实现接口,cglib动态代理,有没有接口都可以做代理. 基于子类的动态代理使用Enhancer类中的create方法创建代理对象
导入CGLIB的相关jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.2</version>
</dependency>
创建被代理类(真实对象)
public class Producer {
/**
* 销售
* @param money
*/
public void saleProduct(float money) {
System.out.println("销售产品,并拿到钱:"+money);
}
/**
* 售后
* @param money
*/
public void afterService(float money) {
System.out.println("提供售后服务,并拿到钱:"+money);
}
}
使用CGLIB动态代理创建代理对象
public class CglibProxyTest {
@Test
public void client(){
final Producer producer=new Producer();
/**
* create方法的参数
* 参数1:Class,字节码它是用于指定被代理对象的字节码
*参数2:Callback,用于提供增强的代码它是让我们写如何代理.
* 我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的.
*我们一般写的都是该接口的子接口实现类:MethodInterceptor
*/
Producer cglibProducer= (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor(){
/**
*执行被代理对象的任何接口方法都会经过该方法
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @param methodProxy 当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
}
三 AOP的相关术语
- Joinpoint( 连接点):所谓连接点是指那些被拦截到的点,在spring中这些点指的是方法,因为spring只支持方法类型的连接点
- 可以进行增强功能的方法
- Pointcut( 切入点):所谓切入点 表示一组 joint point,这些 joint point 或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方.简单说切入点是指我们要对哪些连接点进行拦截的定义
- 进行增强的方法
- Advice( 通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知.通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知.
- 需要增强的功能
- Aspect( 切面):是切入点和通知(引介)的结合.
- Introduction( 引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field.
- Target( 目标对象):代理的目标对象.
- Proxy(代理):一个类被 AOP 织入增强后,就产生一个结果代理类.
- Weaving( 织入):是指把增强应用到目标对象来创建新的代理对象的过程.spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入.
四 AOP实现的基础环境准备
1.导入依赖jar包
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.7.0</version>
<configuration>
<target>1.8</target>
<source>1.8</source>
</configuration>
</plugin>
</plugins>
</build>
2.创建业务层接口及实现类
接口
public interface AccountService {
/**
* 模拟保存帐户
*/
void saveAccount();
/**
* 模似拟修改帐户
*/
void updateAccount(int i);
/**
* 模拟删除帐户
*/
int delAccount();
}
实现类
@Service("accountService")
public class AccountServiceImpl implements AccountService {
/**
* 模拟保存
*/
@Override
public void saveAccount() {
System.out.println("执行了保存");
}
/**
* 模拟修改
*/
@Override
public void updateAccount(int i) {
System.out.println("执行了修改"+i);
}
/**
* 模拟删除
*/
@Override
public int delAccount() {
System.out.println("执行了删除");
return 0;
}
}
3. 编写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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.boke"></context:component-scan>
</beans>
4. 基于XML的AOP配置
需求介绍
我们现在要在所有的业务层功能执行时插入日志记录功能
抽取公用功能
/**
* 日志工具类
*/
@Component
public class Logger {
/**
* 记录日志,在执行切入点方法之前执行
*/
public void printLog() {
System.out.println("Logger类中的printLog方法开始记录日志....";
}
}
配置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/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置目标 target-->
<bean id="accountService" class="com.boke.service.impl.AccountServiceImpl"></bean>
<!--配置通知-->
<bean id="logger" class="com.boke.utils.Logger"></bean>
<!--配置Aop-->
<aop:config>
<!--配置Aop切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置通知类型,并建立通知方法和切入点方法的关联-->
<aop:before method="printLog" pointcut="execution(* com.boke.service..*.*(..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
编写测试代码
public class AccountServiceTest {
@Test
public void saveAccount() {
ApplicationContext ApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
AccountService accountService = (AccountService) ApplicationContext.getBean("accountService");
accountService.saveAccount();
}
@Test
public void updateAccount() {
ApplicationContext ApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
AccountService accountService = (AccountService) ApplicationContext.getBean("accountService");
accountService.updateAccount(1);
}
@Test
public void delAccount() {
ApplicationContext ApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
AccountService accountService = (AccountService) ApplicationContext.getBean("accountService");
int i = accountService.delAccount();
System.out.println(i);
}
}
五 配置AOP的步骤
- 配置需要被增强的类和通知
- 使用aop:config标签表明开始AOP的配置
- 使用 aop:aspect标签表明配置切面
- id属性 : 是给切面提供一个唯一标识
- ref属性 : 是指定通知类bean的Id.
4.在aop:aspect标签的内部使用对应标签来配置通知的类型
aop:before : 表示配置前置通知
method属性 : 用于指定Logger类中哪个方法是前置通知
pointcut属性 : 用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
六 切入点表达式
execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
全匹配方式:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.boke.domain.Account) 访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount(com.boke.domain.Account)
注意:
1.返回值可以使用*号,表示任意返回值
2.包名可以使用*号,表示任意包,但是有几级包,需要写几个*
3.使用..来表示当前包,及其子包
4.类名可以使用*号,表示任意类
5.方法名可以使用*号,表示任意方法
6.参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
7.参数列表可以使用..表示有无参数均可,有参数可以是任意类型
8.全通配方式: * *..*.*(..)
七 AOP的通知类型
Spring中通知分为: 前置通知,后置通知,异通知常,最终通知
<!--配置Aop-->
<!--配置Aop-->
<aop:config>
<!--配置Aop切面-->
<aop:aspect id="logAdvice" ref="logger">
<!--配置切入点-->
<!--配置通知类型,并建立通知方法和切入点方法的关联-->
<!--前置通知:目标行为执行之前执行-->
<aop:before method="printLog" pointcut="execution(* com.boke.service..*.*(..))"></aop:before>
<!--后置通知:目标行为执行之后执行-->
<aop:after-returning method="printLog" pointcut="execution(* com.boke.service..*.*(..))"></aop:after-returning>
<!--异常通知:目标行为只有抛出了异常后才会执行这个增强方法-->
<aop:after-throwing method="printLog" pointcut="execution(* com.boke.service..*.*(..))"></aop:after-throwing>
<!--最终通知:无论是否有异常,最终通知都会执行.-->
<aop:after method="printLog" pointcut="execution(* com.boke.service..*.*(..))"></aop:after>
<!--环绕通知:目标行为执行前后执行-->
<aop:around method="printLog" pointcut="execution(* com.boke.service..*.*(..))"></aop:around>
</aop:aspect>
</aop:config>
环绕通知
Spring中的环绕通知是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式.
public Object aroundPrintLog(ProceedingJoinPoint pjp) {
Object rtValue=null;
try{
//获得方法执行所需参数
Object[] args=pjp.getArgs();
System.out.println("logger类中的aroundPrintLog方法开始记录日志===>前置");
//明确调用业务层方法
rtValue=pjp.proceed(args);
System.out.println("logger类中的aroundPrintLog方法开始记录日志===>后置");
}catch (Throwable throwable){
System.out.println("logger类中的aroundPrintLog方法开始记录日志===>异常");
} finally {
System.out.println("logger类中的aroundPrintLog方法开始记录日志===>最终");
}
return rtValue;
}
Spring框架为我们提供了一个接口:ProceedingJoinPoint.该接口有一个方法proceed(),此方法就相当于明确调用切入点方法 , 该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用.