public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
package com.atuigu;
/**
* 计算器接口的实现类
*/
public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
在计算器的每个方法执行前后加入日志打印
方案一:在每个方法的前后都加上日志打印的代码
方案二:创建一个工具类,将日志打印的代码写在工具类中,然后再每个方法的前后直接调用工具类中的方法打印日志
方案三:创建一个父类,在父类的方法中打印日志,子类重写父类的方法(对目前功能不适用)
方案四:动态代理
方案五:AOP
二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
未经过代理的情况:
使用了代理模式的情况:
先添加依赖包
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.1version>
dependency>
dependencies>
创建代理类
package com.atuigu.proxy;
import com.atuigu.Calculator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 代理类
*/
@Component
public class CalculatorProxy implements Calculator {
// 自动装配 根据类型进行依赖注入
@Autowired
private Calculator calculator;
@Override
public int add(int i, int j) {
System.out.println("日志 add 方法开始了,参数是:" + i + "," + j);
// 调用代理对象的核心方法
int result = calculator.add(i, j);
System.out.println("日志 add 方法结束了,结果是:" + result);
return result;
}
@Override
public int sub(int i, int j) {
System.out.println("日志 sub 方法开始了,参数是:" + i + "," + j);
// 调用代理对象的核心方法
int result = calculator.sub(i, j);
System.out.println("日志 sub 方法结束了,结果是:" + result);
return result;
}
@Override
public int mul(int i, int j) {
System.out.println("日志 mul 方法开始了,参数是:" + i + "," + j);
// 调用代理对象的核心方法
int result = calculator.mul(i, j);
System.out.println("日志 mul 方法结束了,结果是:" + result);
return result;
}
@Override
public int div(int i, int j) {
System.out.println("日志 div 方法开始了,参数是:" + i + "," + j);
// 调用代理对象的核心方法
int result = calculator.div(i, j);
System.out.println("日志 div 方法结束了,结果是:" + result);
return result;
}
}
package com.atguigu;
import com.atuigu.Calculator;
import com.atuigu.proxy.CalculatorProxy;
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 javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class CalculatorTest {
// 自动装配 根据类型匹配
@Autowired
private CalculatorProxy calculatorProxy;
@Test
public void test01(){
System.out.println(calculatorProxy.add(1, 2));
}
}
实现需求
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
JDK本身就支持动态代理,这是反射技术的一部分。下面我们还是创建一个代理类(生产代理对象的工厂类)
package com.atuigu.proxy;
import com.atuigu.Calculator;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* 在此类中编写代理规则
*/
public class AtguiguInvocationHandler<T> implements InvocationHandler {
// 被代理者
private T t;
public AtguiguInvocationHandler(T t) {
this.t = t;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1.invoke()方法它会在代理对象调用任何方法的时候都执行,所以我们在invoke()方法中编写代理规则
// 参数一 proxy:指定的是代理对象
// 参数二 method:指定的是代理对象调用的方法
String methodName = method.getName();
// 参数三 args:指定的是代理对象调用的方法传入的参数
// 编写代理的规则
// 1.如果被代理者是Calculator接口的实现类,并且调用的方法不是Object类的方法(就表示调用的是加减乘除方法)
if (t instanceof Calculator && !Object.class.equals(method.getDeclaringClass())){
System.out.println("日志" + methodName + "方法开始了,参数是:" + args[0] + "," + args[1]);
// 调用被代理者的方法
Object result = method.invoke(t, args);
System.out.println("日志" + methodName + "方法结束了,参数是:" + result);
}
// 表示调用不是我们需要代理的方法,按照原本的方法执行
return method.invoke(t, args);
}
}
package com.atuigu.proxy;
import java.lang.reflect.Proxy;
public class ProxyFactory<T> {
// 被代理者
private final T t;
// 创建代理工厂的时候将代理者传进来
public ProxyFactory(T t) {
this.t = t;
}
// 创建代理对象
public T newInstance(AtguiguInvocationHandler<T> atguiguInvocationHandler){
// Proxy.newProxyInstance(类加载器对象,要代理的接口的字节码对象数组,InvocationHandler接口的实现类对象)就是JDK提供的动态代理的API
// 参数一:类加载器,我们可以使用被代理对象的类加载器
// 1.获取被代理对象的字节码对象
Class<?> clazz = t.getClass();
// 2.使用被代理对象的字节码对象获取类加载器
ClassLoader classLoader = clazz.getClassLoader();
// 参数二:要代理的接口的字节码对象数组,我们可以使用被代理者对象实现的接口的数组
// 3.使用被代理者的字节码对象获取被代理者实现的接口的数组
Class<?>[] interfaces = clazz.getInterfaces();
// 参数三:InvocationHandler接口的实现类对象,我们可以创建一个InvocationHandler接口的实现类
// 转成代理类型
return (T) Proxy.newProxyInstance(classLoader, interfaces, atguiguInvocationHandler);
}
}
package com.atguigu;
import com.atuigu.Calculator;
import com.atuigu.CalculatorPureImpl;
import com.atuigu.proxy.AtguiguInvocationHandler;
import com.atuigu.proxy.ProxyFactory;
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(locations = "classpath:spring.xml")
public class CalculatorTest {
// 自动装配 根据类型匹配
@Autowired
private CalculatorPureImpl calculatorPure;
@Test
public void test01(){
// calculatorPure是被代理对象
// 1.创建代理工厂
ProxyFactory<Calculator> calculatorProxyFactory = new ProxyFactory<>(calculatorPure);
// 2.创建代理对象
Calculator proxyCalculatorPure = calculatorProxyFactory.newInstance(new AtguiguInvocationHandler<>(this.calculatorPure));
// 3.代理方法调用方法
// 调用Object方法并不会进行代理,没有在方法执行前后添加日志
System.out.println(proxyCalculatorPure.toString());
System.out.println(proxyCalculatorPure.add(1, 2));
}
}
AOP:Aspect Oriented Programming 面向切面编程
- 简化代码:把目标方法中固定位置的重复的代码抽取出来,让目标方法更专注于自己的核心功能,提高内聚性。
- 代码增强:把抽取出来的特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。
横切关注点是从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。
封装通知方法的类。
被代理的目标对象,执行核心业务代码的那个对象
向目标对象应用通知之后创建的代理对象
这也是一个纯逻辑概念,不是语法定义的。
把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点。连接点其实就是各个方法中可以被增强或修改的点
每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。而切入点指的则是方法中真正要去配置增强或者配置修改的地方
在IOC所需依赖基础上载入下面依赖即可
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.13version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>5.3.1version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>5.3.1version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aspectsartifactId>
<version>5.3.1version>
dependency>
dependencies>
package com.atuigu;
/**
* 计算器
*/
public interface Calculator {
int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j);
}
在Spring环境下工作,所有的一切都必须放在IOC容器中。现在接口的实现类是AOP要代理的目标类,所以它也必须放入IOC容器
package com.atuigu;
import org.springframework.stereotype.Component;
/**
* 计算器接口的实现类
* 被代理类
*/
@Component
public class CalculatorPureImpl implements Calculator {
@Override
public int add(int i, int j) {
return i + j;
}
@Override
public int sub(int i, int j) {
return i - j;
}
@Override
public int mul(int i, int j) {
return i * j;
}
@Override
public int div(int i, int j) {
return i / j;
}
}
package com.atuigu.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
/**
* Aspect注解:指定一个切面类
* Component注解:对这个切面类进行IOC
*
* 注解AOP的关键点:
* 1.一定要在配置文件中加上> 表示允许自动代理
* 2.切面类一定要加上Aspect注解,并且切面类一定要进行IOC
* 3.其他的类改进型IOC和依赖注入的就一定要进行IOC和依赖注入
* 4.通知上一定要指定切入点(怎么使用切入点表达式描述切入点又是一个难点)
*/
@Aspect
@Component
public class LogAspect {
// 切入点 后续再介绍
@Before("execution(int com.atuigu.CalculatorPureImpl.* (int,int))")
public void printLogBefore() {
System.out.println("[前置通知]在方法执行之前打印日志...");
}
@AfterReturning("execution(int com.atuigu.CalculatorPureImpl.* (int,int))")
public void printLogAfterReturning() {
System.out.println("[返回通知]在方法执行成功之后打印日志..."); }
@AfterThrowing("execution(int com.atuigu.CalculatorPureImpl.* (int,int))")
public void printLogAfterThrowing() {
System.out.println("[AOP异常通知]在方法抛出异常之后打印日志...");
}
@After("execution(int com.atuigu.CalculatorPureImpl.* (int,int))")
public void printLogAfter() {
System.out.println("[AOP后置通知]在方法最终结束之后打印日志...");
}
}
<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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.atuigu">context:component-scan>
<aop:aspectj-autoproxy>aop:aspectj-autoproxy>
beans>
package com.atguigu;
import com.atuigu.Calculator;
import com.atuigu.CalculatorPureImpl;
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 javax.annotation.Resource;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:spring.xml")
public class CalculatorTest {
// 自动装配 根据类型匹配
@Resource
private Calculator Calculator;
@Test
public void testAdd(){
System.out.println(Calculator.add(22, 33));
}
}
// 切入点表达式:告诉Sprint当前通知方法套用到哪个目标方法上
// 在前置通知方法形参位置声明一个JoinPoint类型的参数,spring就会将这个对象传入
// 根据JoinPoint对象就可以获取目标方法名称/实际参数列表
@Before("execution(int com.atuigu.CalculatorPureImpl.* (int,int))")
public void printLogBefore(JoinPoint joinPoint) {
// 1.通过JoinPoint对象获取目标方法签名对象
// 获取方法的签名信息
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 2.从方法的签名信息中,我们可以获取到很多东西
// 2.1 获取切入点方法
Method method = methodSignature.getMethod();
// 2.2 获取方法的所属的类的全限定名
Class<?> clazz = method.getDeclaringClass();
String className = clazz.getName();
// 2.3 获取方法名
String methodName = method.getName();
// 2.4 获取方法的返回值类型
Class<?> returnType = method.getReturnType();
String returnTypeName = returnType.getName();
// 2.5 获取方法的参数信息
Object[] args = joinPoint.getArgs();
System.out.println("[前置通知]" + className + "." + methodName + "方法开始了,参数是:" + args[0] + "," + args[1]);
}
需要获取方法签名、传入的实参等信息时,可以在通知方法声明JoinPoint类型的形参。
只有在AfterReturning返回通知中才能够获取目标方法的返回值
// @AfterReturning注解标记返回通知方法
// 在返回通知中获取目标方法返回值分两步:
// 第一步:在@AfterRetruning注解中通过returnning属性设置一个名称
// 第二部:使用returning属性设置的名称在通知方法中声明一个对应的形参
@AfterReturning(value = "execution(int com.atuigu.CalculatorPureImpl.* (int,int))", returning = "result")
public void printLogAfterReturning(JoinPoint joinPoint, Object result) {
// 1.获取方法的签名信息
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取切入点方法
Method method = methodSignature.getMethod();
// 获取全限定名
String className = method.getDeclaringClass().getName();
// 获取方法名
String methodName = method.getName();
// 2.获取切入点执行的结果
System.out.println("[返回通知]" + className + "." + methodName + "方法正常结束了,执行结果是:" + result); }
打印效果:
只有在AfterThrowing异常通知中才能获取到目标方法抛出的异常
通过@AfterThrowing注解的throwing属性获取目标方法抛出的异常对象
// @AfterThrowing注解标记异常通知方法
// 在异常通知中获取目标方法抛出的异常分两步:
// 第一步:在@AfterThrowing注解中声明一个throwing属性设定形参名称
// 第二步:使用throwing属性指定的名称在通知方法声明形参,Spring会将目标方法抛出的异常对象从这里传给我们
@AfterThrowing(value = "execution(int com.atuigu.CalculatorPureImpl.* (int,int))", throwing = "throwable")
public void printLogAfterThrowing(JoinPoint joinPoint, Throwable throwable) {
// 1.获取方法的签名信息
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 获取切入点方法
Method method = methodSignature.getMethod();
// 获取全限定名
String className = method.getDeclaringClass().getName();
// 获取方法名
String methodName = method.getName();
System.out.println("[AOP异常通知]" + className + "." + methodName + "方法异常结束了,异常信息是" + throwable);
}
打印效果:
无论是正常结束还是异常结束都会执行
// @AfterThrowing注解通知方法
// 不管是正常结束还是异常结束 都会执行此方法 无法获取到返回值和报错信息 因为不确定是否正常执行结束和异常结束
@After("execution(int com.atuigu.CalculatorPureImpl.* (int,int))")
public void printLogAfter(JoinPoint joinPoint) {
// 1.h获取方法的签名信息
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
// 2.获取方法对象
Method method = methodSignature.getMethod();
// 3.获取全限定名
String className = method.getDeclaringClass().getName();
// 4.获取方法名
String methodName = method.getName();
System.out.println("[AOP后置通知]" + className +"." + methodName + "方法执行结束了");
}
在一处声明切入点表达式之后,其他有需要的地方引用这个切入点表达式。易于维护,一处修改,处处生效。声明方式如下:
@Pointcut("execution(int com.atguigu.component.CalculatorPureImpl.*(int,int))")
public void calculatorPointCut(){
}
@Before("calculatorPointCut()")
public void printLogBeforeCore(JoinPoint joinPoint){
@Before("com.atguigu.pointcut.AtguiguPointCut.calculatorPointCut()")
public void printLogBeforeCore(JoinPoint joinPoint){}
而作为存放切入点表达式的类,可以把整个项目中所有切入点表达式全部集中过来,便于统一管理:
package com.atguigu.pointcut;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
public class AtguiguPointCut {
// 表示返回值可以任意类型 任意包中任意类的任意方法 任意参数类型和任意个数
@Pointcut("execution(* *..*.*(..))")
public void pt1(){}
// 精确匹配
@Pointcut("!execution(int com.atguigu.component.CalculatorPureImpl.add(int,int))")
public void pt2(){}
// 两者任意一个都可以匹配
@Pointcut("execution(int com.atguigu.component.CalculatorPureImpl.add(int,int)) || execution(int com.atguigu.component.CalculatorPureImpl.sub(int,int))")
public void pt3(){}
// 两者都满足才可以匹配
@Pointcut("!execution(int com.atguigu.component.CalculatorPureImpl.add(int,int)) && execution(int com.atguigu.component.CalculatorPureImpl.*(int,int))")
public void pt4(){}
}
切入点表达式的作用是用于描述将代理逻辑套用在哪些目标方法上
*Impl
(1). 返回值类型
① 精确
② * 表示任意返回值类型(2). 包名:
① 精确
② *. 表示一级包,包名可以任意
③ *… 表示包的层级任意,包名也任意(3). 类名:
① 精确
② * 任意类名
③ *Impl(4). 方法名:
① 精确 add
② * 任意方法名
③ select*(5). 参数:
① 精确 (int,int)
② * 表示一个参数,但是类型可以任意
③ (…) 参数的个数和类型都任意
对于execution()表达式整体可以使用三个逻辑运算符号
- execution() || execution()表示满足两个execution()中的任何一个即可
- execution() && execution()表示两个execution()表达式必须都满足
- !execution()表示不满足表达式的其他方法
环绕通知对应整个try…catch…finally结构,可以在目标方法的各个部位进行套用代理逻辑,它能够真正介入并改变目标方法的执行
(1). 作用位置: 切入点执行的前后
(2). 注解: @Around
在切入点执行之前,校验切入点的参数; 在切入点执行成功之后,记录切入点执行的结果; 在切入点执行出现异常之后,将异常信息记录到日志文件中。不适合用环绕通知,因为这里是多件事情
在切入点上加入事务控制: 适合使用环绕通知
(1). 开启事务
(2). 执行切入点
(3). 没有异常,则提交事务
(4). 出现异常,则回滚事务
(5). 无论出没出现异常,都需要设置connection的autoCommit为true,并且归还连接
计算切入点的执行时长: 适合使用环绕通知
(1). 获取切入点执行之前的毫秒数
(2). 执行切入点
(3). 获取切入点执行结束之后的毫秒数
(4). 两个毫秒数之间的差值就是切入点的执行时长
缓存: 适合使用环绕通知
(1). 从缓存中查询数据,如果有数据则直接返回
(2). 如果缓存中没有数据,则执行切入点从数据库查询数据
(3). 将数据库查询出来的数据存储到缓存中
(4). 返回数据库查询到的数据
@Around("com.atguigu.pointcut.AtguiguPointCut.pt1()")
public Object printExecuteTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
// 1.获取切入点执行前的毫秒数
long startTime = System.currentTimeMillis();
// 2.执行切入点
try {
Object result = proceedingJoinPoint.proceed();
return result;
} catch (Throwable e) {
e.printStackTrace();
throw e;
} finally {
// 休眠3秒
Thread.sleep(3000);
// 3.获取切入点执行之后的毫秒数
long endTime = System.currentTimeMillis();
// 4.计算执行时长
long executeTime = endTime - startTime;
MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
String className = methodSignature.getMethod().getDeclaringClass().getName();
String methodName = methodSignature.getMethod().getName();
// 5.日志输出执行时长
System.out.println("[环绕通知]" + className + "." + methodName + "方法执行的总时长是:" + executeTime);
}
}
测试结果
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
使用@Order注解可以控制切面的优先级:
实际开发时,如果有多个切面嵌套的情况,要慎重考虑。例如:如果事务切面优先级高,那么在缓存中命中数据的情况下,事务切面的操作都浪费了。
此时应该将缓存切面的优先级提高,在事务操作之前先检查缓存中是否存在目标数据。
动态代理分成两种:
第一种是JDK内置的动态代理,这种动态代理需要被代理者实现接口,如果被代理者没有实现接口,那么则无法使用JDK的动态代理
第二种是CGLIB的动态代理,在被代理类没有实现任何接口的情况下,Spring会自动使用cglib技术实现代理。
如果要创建代理对象的类实现了接口,那么就使用JDK的动态代理;如果要创建代理对象的类没有实现接口,那么就使用CGLIB的动态代理
和基于注解的AOP时一样
把基于注解的Module复制一份,修改Module名,并导入到工程中,然后去除所有AOP注解。
<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
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<context:component-scan base-package="com.atguigu"/>
<aop:config>
<aop:aspect id="myAspect" ref="logAspect">
<aop:before method="printLogBeforeCore" pointcut-ref="calculatorPoint"/>
<aop:after-returning method="printLogAfterReturning" pointcut-ref="calculatorPoint" returning="result"/>
<aop:after-throwing method="printLogAfterThrowing" pointcut-ref="calculatorPoint" throwing="throwable"/>
<aop:after method="printLogFinallyEnd" pointcut-ref="calculatorPoint"/>
<aop:around method="printLogAround" pointcut-ref="calculatorPoint"/>
<aop:pointcut id="calculatorPoint"
expression="execution(* com.atguigu.component.CalculatorPureImpl.*(..))"/>
aop:aspect>
aop:config>
beans>
package com.atguigu;
import com.atguigu.component.Calculator;
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(locations = "classpath:spring-application.xml")
public class TestAop {
@Autowired
private Calculator calculator;
@Test
public void testAdd(){
//调用CalculatorPureImpl对象的add()方法
System.out.println("调用完目标方法之后获取返回值是:"+calculator.sub(5, 3));
}
}
目标:
1. 将目标方法中的非核心业务抽取出来制作成通知
2. 在调用目标方法的核心业务的时候,底层动态自动将非核心业务套在核心业务上执行
实现目标:
识别出哪些地方可以抽取
准备一个切面类:
2.1 IOC:@Component
2.2 让他称为切面类:@Aspect
将抽取出来的代码封装成方法(通知),方法放在切面类中
让切面类中的方法称为通知
4.1 前置通知: Before
4.2 返回通知:AfterReturning
4.3 异常通知:AfterThrowing
4.4 后置通知:After
4.5 环绕通知Around
指定通知的作用位置(切入点):就是引用切入点
5.1 如果切入点和通知在同一个类中:根据方法名引用
5.2 如果切入点跟通知不在同一个类中:根据类的全限定名.方法名来引用
6. 声明切入点以及切入点的语法