二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。
先来看一个需求:
设计一个计算器程序,要求在计算的前后输出日志
先定义一个接口
- package com.atguigu.spring6.aop.annoaop;
-
- 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.atguigu.spring6.aop.annoaop;
-
- import org.springframework.stereotype.Component;
-
- //基本实现类
- @Component
- public class CalculatorImpl implements Calculator {
-
- @Override
- public int add(int i, int j) {
-
- int result = i + j;
-
- System.out.println("方法内部 result = " + result);
-
- return result;
- }
-
- @Override
- public int sub(int i, int j) {
-
- int result = i - j;
-
- System.out.println("方法内部 result = " + result);
-
- return result;
- }
-
- @Override
- public int mul(int i, int j) {
-
- int result = i * j;
-
- System.out.println("方法内部 result = " + result);
-
- return result;
- }
-
- @Override
- public int div(int i, int j) {
-
- int result = i / j;
-
- System.out.println("方法内部 result = " + result);
-
- return result;
- }
- }
在程序里面设计一个在计算前后输出日志的功能
- package com.atguigu.spring6.aop.example;
-
- //带日志
- public class CalculatorLogImpl implements Calculator{
-
- @Override
- public int add(int i, int j) {
-
- System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
-
- int result = i + j;
-
- System.out.println("方法内部 result = " + result);
-
- System.out.println("[日志] add 方法结束了,结果是:" + result);
-
- return result;
- }
-
- @Override
- public int sub(int i, int j) {
-
- System.out.println("[日志] sub 方法开始了,参数是:" + i + "," + j);
-
- int result = i - j;
-
- System.out.println("方法内部 result = " + result);
-
- System.out.println("[日志] sub 方法结束了,结果是:" + result);
-
- return result;
- }
-
- @Override
- public int mul(int i, int j) {
-
- System.out.println("[日志] mul 方法开始了,参数是:" + i + "," + j);
-
- int result = i * j;
-
- System.out.println("方法内部 result = " + result);
-
- System.out.println("[日志] mul 方法结束了,结果是:" + result);
-
- return result;
- }
-
- @Override
- public int div(int i, int j) {
-
- System.out.println("[日志] div 方法开始了,参数是:" + i + "," + j);
-
- int result = i / j;
-
- System.out.println("方法内部 result = " + result);
-
- System.out.println("[日志] div 方法结束了,结果是:" + result);
-
- return result;
- }
- }
①现有代码缺陷
针对带日志功能的实现类,我们发现有如下缺陷:
②解决思路
解决这两个问题,核心就是:解耦。我们需要把附加功能从业务功能代码中抽取出来。
③困难
解决问题的困难:要抽取的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。
在没有使用代理之前,我们调用的逻辑是这样的:
下面我们要使用代理,代理的调用逻辑是这样的:
使用代理以后得代码
- package com.atguigu.spring6.aop.example;
-
- public class CalculatorStaticProxy implements Calculator{
-
- //被代理目标对象传递过来
- private Calculator calculator;
- public CalculatorStaticProxy(Calculator calculator) {
- this.calculator = calculator;
- }
-
- @Override
- public int add(int i, int j) {
- //输出日志
- System.out.println("[日志] add 方法开始了,参数是:" + i + "," + j);
-
- //调用目标对象的方法实现核心业务
- int addResult = calculator.add(i, j);
-
- System.out.println("[日志] add 方法结束了,结果是:" + addResult);
- return addResult;
- }
-
- @Override
- public int sub(int i, int j) {
- return 0;
- }
-
- @Override
- public int mul(int i, int j) {
- return 0;
- }
-
- @Override
- public int div(int i, int j) {
- return 0;
- }
- }
上面就是使用了代理技术,不过使用的是静态代理。
静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。
提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。这就需要使用动态代理技术了。
动态代理的过程:
实现代码
- package com.atguigu.spring6.aop.example;
-
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.util.Arrays;
-
- public class ProxyFactory {
-
- //目标对象
- private Object target;
- public ProxyFactory(Object target) {
- this.target = target;
- }
-
- //返回代理对象
- public Object getProxy() {
- /**
- * Proxy.newProxyInstance()方法
- * 有三个参数
- * 第一个参数:ClassLoader: 加载动态生成代理类的来加载器
- * 第二个参数: Class[] interfaces:目录对象实现的所有接口的class类型数组
- * 第三个参数:InvocationHandler:设置代理对象实现目标对象方法的过程
- */
- //第一个参数:ClassLoader: 加载动态生成代理类的来加载器
- ClassLoader classLoader = target.getClass().getClassLoader();
- //第二个参数: Class[] interfaces:目录对象实现的所有接口的class类型数组
- Class>[] interfaces = target.getClass().getInterfaces();
- //第三个参数:InvocationHandler:设置代理对象实现目标对象方法的过程
- InvocationHandler invocationHandler =new InvocationHandler() {
-
- //第一个参数:代理对象
- //第二个参数:需要重写目标对象的方法
- //第三个参数:method方法里面参数
- @Override
- public Object invoke(Object proxy,
- Method method,
- Object[] args) throws Throwable {
-
- //方法调用之前输出
- System.out.println("[动态代理][日志] "+method.getName()+",参数:"+ Arrays.toString(args));
-
- //调用目标的方法
- Object result = method.invoke(target, args);
-
- //方法调用之后输出
- System.out.println("[动态代理][日志] "+method.getName()+",结果:"+ result);
- return result;
- }
- };
- return Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);
- }
- }
- package com.atguigu.spring6.aop.example;
-
- public class TestCal {
-
- public static void main(String[] args) {
- //创建代理对象(动态)
- ProxyFactory proxyFactory = new ProxyFactory(new CalculatorImpl());
- Calculator proxy = (Calculator)proxyFactory.getProxy();
- //proxy.add(1,2);
- proxy.mul(2,4);
- }
- }
AOP(Aspect Oriented Programming)是一种面向切面编程的思想,切面指的是系统的某一个方面。AOP是针对程序中的某一类(某个方面)的功能做统一处理,比如用户登录权限的效验。Spring AOP是一个框架,提供了AOP思想的实现,AOP和Spring AOP的关系就像IoC和DI。
AOP可以实现一下功能:
统一日志记录、统一方法执行时间统计、统一格式返回格式设置、统一的异常处理、事务的开启和提交等,AOP编程是对OOP编程的补充和完善。
在登录授权中AOP的作用:

AOP主要由四点组成,分别是:切面、切点、连接点、通知
切面由切点和通知组成,它是针对某一个功能的具体定义,这个功能可能是登录验证功能,也有可能是日志记录功能,这里可以将AOP形象的比喻为一个数据库,一个AOP就是一个数据库。
切点是一个保存了众多连接点的一个集合,切点是切面中的某个方法,如果将切面比作数据库,那么切点就是数据库里面的表。
我们将切面必须完成工作称为通知,切面的工作就是通知。通知有五种:前置通知(使用@Before)、后置通知(使用@After)、返回之后通知(使用@AfterReturning)、抛出异常通知(使用AfterThrowing)、环绕通知(使用@Around)
所有可能触发AOP(拦截方法的点)就称之为连接点
上面AOP的组成可以用一张图来表示:


Spring AOP的实现步骤如下:
1.添加Spring AOP框架支持;
2.定义切面和切点;
3.定义通知;
在pom.xml文件中插入以下框架
-
- <dependency>
- <groupId>org.springframework.bootgroupId>
- <artifactId>spring-boot-starter-aopartifactId>
- dependency>

- @Aspect //定义切面
- @Component
- public class UserAspect {
- //切点(配置拦截规则)
- @Pointcut("execution(* com.example.myblog.controller.UserController.*(..))")
- public void pointcut(){
- }
- }
对上面代码的解释:
@Aspect这个注解表示这个类是一个AOP类,也就是说这个类是一个切面;
@Component这个注解表示组件,当我们不知道具体该给某个类使用那个注解来注入到Spring框架时,我们就可以使用@Component这个注解;
@Pointcut这个注解表示该方法是一个切点,它后面跟的是AspectJ表达式(切点表达式)。该表达式的语法:execution(<修饰符><返回类型><包.类.方法(参数)><异常>)

上面的包和类一般情况下是要有的,但可以省略;异常可省略一般不写。
AspectJ支持三种通配符:
*:匹配任意字符,只匹配一个元素(这个元素可以是包、类、或者方法、
方法参数)。
..:匹配任意字符,可以匹配多个元素,在表示类时,必须和*联合使用。
+:表示按照类型匹配指定类的所有类,必须跟在类名后面,如com.cad.Car+,表示继承该类的所有子类包括本身。
表达式示例:
execution(* addUser(String,int)):匹配addUser方法,且第一个参数类型是String,第二个参数是int。
excution(* com.cad.demo.User.*(..)):匹配User类里面的所有方法。
excution(* com.cad.demo.User+.*(..)):匹配该类的子类包括该类的所有方法。
excution(* com.cad.*.*(..):匹配com.cad包下面的所有类的所有方法。
excution(* com.cad..*.*(..)):匹配com.cad包下、子孙包下所有类的所有方法。
通知定义的是被拦截的方法具体要执行的业务,比如用户登录权限验证就是具体要执行的业务。Spring AOP中,可以在方法上使用以下注解,会设置方法为通知方法,在满足条件后会通知本方法进行调用:
前置通知使用@Before:通知方法会在目标方法执行调用之前执行。
后置通知使用@After:通知方法会在目标方法返回或者抛出异常后调用。
返回之后通知使用@AfterRuturning:通知方法会在目标方法返回后调用。
抛出异常后通知使用@AfterThrowing:通知方法会在目标方法抛出异常后调用。
环绕通知使用@Around:通知包裹了被通知的方法,在被通知的方法通知之前和调用之后执行自定义的行为。
执行前置通知:
- @Aspect //定义切面
- @Component
- public class UserAspect {
- //切点(配置拦截规则)
- @Pointcut("execution(* com.example.myblog.controller.UserController.*(..))")
- public void pointcut(){
- }
-
- //前置通知
- @Before("pointcut()")
- public void doBefore(){
- //业务代码...
- System.out.println();
- System.out.println("执行了前置通知");
- System.out.println();
- }
- }
演示结果:

执行后置通知:
- @Aspect //定义切面
- @Component
- public class UserAspect {
- //切点(配置拦截规则)
- @Pointcut("execution(* com.example.myblog.controller.UserController.*(..))")
- public void pointcut(){
- }
-
- //后置通知
- @After("pointcut()")
- public void doAfter(){
- //业务代码...
- System.out.println();
- System.out.println("执行了后置通知");
- System.out.println();
- }
- }


执行返回之后通知:
- @Aspect //定义切面
- @Component
- public class UserAspect {
- //切点(配置拦截规则)
- @Pointcut("execution(* com.example.myblog.controller.UserController.*(..))")
- public void pointcut(){
- }
-
- //前置通知
- @Before("pointcut()")
- public void doBefore(){
- //业务代码...
- System.out.println();
- System.out.println("执行了前置通知");
- System.out.println();
- }
-
- //后置通知
- @After("pointcut()")
- public void doAfter(){
- //业务代码...
- System.out.println();
- System.out.println("执行了后置通知");
- System.out.println();
- }
-
-
- //return之前通知
- @AfterReturning("pointcut()")
- public void doAfterReturning(){
- //业务代码...
- System.out.println();
- System.out.println("执行了doAfterReturning通知");
- System.out.println();
- }
- }


可以看到执行返回之后通知它是在返回的时候之后调用的,在返回通知之前调用。
执行抛出异常后通知:
- @Aspect //定义切面
- @Component
- public class UserAspect {
- //切点(配置拦截规则)
- @Pointcut("execution(* com.example.myblog.controller.UserController.*(..))")
- public void pointcut(){
- }
-
- //前置通知
- @Before("pointcut()")
- public void doBefore(){
- //业务代码...
- System.out.println();
- System.out.println("执行了前置通知");
- System.out.println();
- }
-
- //后置通知
- @After("pointcut()")
- public void doAfter(){
- //业务代码...
- System.out.println();
- System.out.println("执行了后置通知");
- System.out.println();
- }
-
-
- //return之前通知
- @AfterReturning("pointcut()")
- public void doAfterReturning(){
- //业务代码...
- System.out.println();
- System.out.println("执行了doAfterReturning通知");
- System.out.println();
- }
-
- //抛出异常之前通知
- @AfterThrowing("pointcut()")
- public void doAfterThrowing(){
- //业务代码...
- System.out.println();
- System.out.println("执行了doAfterThrowing通知");
- System.out.println();
- }
- }

执行环绕通知:
- //添加环绕通知
- @Around("pointcut()")
- public Object doAround(ProceedingJoinPoint joinPoint){
- Object o = null;
- System.out.println("Around 方法开始执行");
- try {
- o = joinPoint.proceed();
- } catch (Throwable e) {
- e.printStackTrace();
- }
- System.out.println("Around方法结束执行");
- return o;
- }


可以看到环绕通知是在所有通知方法之前和之后调用,因此可以用来统计方法的执行时间。
- @Aspect //定义切面
- @Component
- public class UserAspect {
- //切点(配置拦截规则)
- @Pointcut("execution(* com.example.myblog.controller.UserController.*(..))")
- public void pointcut(){
- }
-
- //添加环绕通知
- @Around("pointcut()")
- public Object doAround(ProceedingJoinPoint joinPoint){
- Object o = null;
- StopWatch stopWatch = new StopWatch();
- try {
- stopWatch.start();
- o = joinPoint.proceed();
- stopWatch.stop();
- } catch (Throwable e) {
- e.printStackTrace();
- }
-
- System.out.println(joinPoint.getSignature().getDeclaringTypeName()+"."+
- joinPoint.getSignature().getName()+" 方法执行花费的时间: "+
- stopWatch.getTotalTimeMillis()+"ms");
- return o;
- }
- }

- package com.atguigu.spring6.aop.xmlaop;
-
- 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.atguigu.spring6.aop.xmlaop;
-
- import org.springframework.stereotype.Component;
-
- //基本实现类
- @Component
- public class CalculatorImpl implements Calculator {
-
- @Override
- public int add(int i, int j) {
-
- int result = i + j;
-
- System.out.println("方法内部 result = " + result);
-
- //为了测试,模拟异常出现
- // int a = 1/0;
- return result;
- }
-
- @Override
- public int sub(int i, int j) {
-
- int result = i - j;
-
- System.out.println("方法内部 result = " + result);
-
- return result;
- }
-
- @Override
- public int mul(int i, int j) {
-
- int result = i * j;
-
- System.out.println("方法内部 result = " + result);
-
- return result;
- }
-
- @Override
- public int div(int i, int j) {
-
- int result = i / j;
-
- System.out.println("方法内部 result = " + result);
-
- return result;
- }
- }
- package com.atguigu.spring6.aop.xmlaop;
-
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.*;
- import org.springframework.stereotype.Component;
-
- import java.util.Arrays;
-
- //切面类
- @Component //ioc容器
- public class LogAspect {
-
- //前置通知
- public void beforeMethod(JoinPoint joinPoint) {
- String methodName = joinPoint.getSignature().getName();
- Object[] args = joinPoint.getArgs();
- System.out.println("Logger-->前置通知,方法名称:"+methodName+",参数:"+Arrays.toString(args));
- }
-
- // 后置通知
- public void afterMethod(JoinPoint joinPoint) {
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->后置通知,方法名称:"+methodName);
- }
-
- // 返回通知,获取目标方法的返回值
- public void afterReturningMethod(JoinPoint joinPoint,Object result) {
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->返回通知,方法名称:"+methodName+",返回结果:"+result);
- }
-
- // 异常通知 获取到目标方法异常信息
- //目标方法出现异常,这个通知执行
- public void afterThrowingMethod(JoinPoint joinPoint,Throwable ex) {
- String methodName = joinPoint.getSignature().getName();
- System.out.println("Logger-->异常通知,方法名称:"+methodName+",异常信息:"+ex);
- }
-
- // 环绕通知
- public Object aroundMethod(ProceedingJoinPoint joinPoint) {
- String methodName = joinPoint.getSignature().getName();
- Object[] args = joinPoint.getArgs();
- String argString = Arrays.toString(args);
- Object result = null;
- try {
- System.out.println("环绕通知==目标方法之前执行");
-
- //调用目标方法
- result = joinPoint.proceed();
-
- System.out.println("环绕通知==目标方法返回值之后");
- }catch (Throwable throwable) {
- throwable.printStackTrace();
- System.out.println("环绕通知==目标方法出现异常执行");
- } finally {
- System.out.println("环绕通知==目标方法执行完毕执行");
- }
- return result;
- }
-
- //重用切入点表达式
- @Pointcut(value = "execution(* com.atguigu.spring6.aop.xmlaop.CalculatorImpl.*(..))")
- public void pointCut() {}
- }
- "1.0" encoding="UTF-8"?>
"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">
-
-
-
package="com.atguigu.spring6.aop.xmlaop"> -
-
-
-
-
"logAspect"> -
-
"pointcut" expression="execution(* com.atguigu.spring6.aop.xmlaop.CalculatorImpl.*(..))"/> -
-
-
"beforeMethod" pointcut-ref="pointcut"> -
-
-
"afterMethod" pointcut-ref="pointcut"> -
-
-
"afterReturningMethod" returning="result" pointcut-ref="pointcut"> -
-
-
"afterThrowingMethod" throwing="ex" pointcut-ref="pointcut"> -
-
-
"aroundMethod" pointcut-ref="pointcut"> -
-
- package com.atguigu.spring6.aop.xmlaop;
-
- import org.junit.jupiter.api.Test;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
-
- public class TestAop {
-
- @Test
- public void testAdd() {
- ApplicationContext context =
- new ClassPathXmlApplicationContext("beanaop.xml");
- Calculator calculator = context.getBean(Calculator.class);
- calculator.add(4,3);
- }
- }
相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序。
使用@Order注解可以控制切面的优先级:
记录用户的登录和退出:
- //定义切面
- @Aspect
- @Component
- public class UserAspect {
- /**
- * 定义切点,切点是一个包含了众多拦截点的一个集合
- * @param joinPoint 所有需要aop处理的方法都称为连接点
- */
- //配置代理规则
- @Before("execution(* com.example.demo.controller.UserController.*(..))")
- public void logBefore(JoinPoint joinPoint) {
- String methodName = joinPoint.getSignature().getName();
- String className = joinPoint.getTarget().getClass().getSimpleName();
- System.out.println("Executing " + className + "." + methodName + "()");
- // 记录日志到日志文件或数据库
- }
-
- }
用户的登录和注销操作在UserController类里面,当用户请求登录和注销操作时:

Spring AOP是构建在动态代理基础上的,因此Spring对AOP的支持局限于方法级别的拦截。Spring AOP支持JDK Proxy和CGLIB方式实现动态代理,而这两类方式底层都是通过反射来实现的。
动态代理分两种,基于接口的动态代理和基于类的动态代理。
基于接口的动态代理使用Java的反射机制,在运行时动态地创建代理对象。代理对象实现了与原始对象相同的接口,并将方法调用转发给原始对象,同时还可以在方法调用前后执行其他逻辑。使用`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口可以实现基于接口的动态代理。
基于类的动态代理是通过继承或扩展来实现的。在运行时,它创建一个类来继承原始类,并覆盖其中的方法以添加额外的业务逻辑。使用第三方库,如CGLIB(Code Generation Library)可以实现基于类的动态代理。
AspectJ是AOP的框架, Spring依赖AspectJ的注解实现AOP的功能,AspectJ本质是一个静态代理。SpringAOP属于运行时增强,而AspectJ是编译时增强,SpringAOP基于代理,而AspectJ基于字节码操作。