AOP: (Aspect Oriented Programming) 面向切面编程
OOP:(Object Oriented Programming) 面向对象编程

面向切面编程: 基于oop基础的新编程思想;
指在程序运行期间, 将某段代码动态的切入指定方法指定位置进行运行的编程方式, 面向切面编程
场景: 计算器运行计算方法的时候进行日志记录;
加日志记录:
1) 直接编写在方法内部; 不推荐, 维护麻烦.
日志记录: 系统的辅助功能;
业务逻辑: 核心功能;
耦合度高
2) 使用动态代理

  1. public class CalculatorProxy {
  2. /*
  3. * @description 为传入参数对象创建一个动态代理对象
  4. * @param calculator
  5. * @return com.yguilai.inter.Calculator
  6. */
  7. public static Calculator getProxy(Calculator calculator) {
  8. ClassLoader classLoader = calculator.getClass().getClassLoader();
  9. Class<?>[] classes = calculator.getClass().getInterfaces();
  10. // 方法执行器, 帮助目标对象执行目标方法
  11. InvocationHandler invocationHandler = new InvocationHandler() {
  12. /*
  13. * @description
  14. * @param proxy 代理对象
  15. * @param method
  16. * @param args
  17. * @return java.lang.Object
  18. */
  19. @Override
  20. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  21. Object invoke = null;
  22. try {
  23. System.out.println("[" + method.getName() + "]方法执行, 参数" + Arrays.asList(args));
  24. invoke = method.invoke(calculator, args);
  25. System.out.println("[" + method.getName() + "]方法正常执行完成, 结果[" + invoke + "]");
  26. } catch (Exception e) {
  27. System.out.println("[" + method.getName() + "]方法执行异常, 异常信息: " + e.getCause());
  28. //e.printStackTrace();
  29. } finally {
  30. System.out.println("[" + method.getName() + "]方法结束");
  31. }
  32. return invoke;
  33. }
  34. };
  35. // Proxy为目标对象创建代理对象
  36. Object proxy = Proxy.newProxyInstance(classLoader, classes, invocationHandler);
  37. return (Calculator) proxy;
  38. }
  39. }

jdk默认动态代理缺陷: 如果目标对象没有实现任何接口是无法为他创建代理对象的. 代理对象和被代理对象唯一能产生的关联就是实现了相同的接口.

3) 利用SpringAOP创建动态代理, 实现简单, 没有强制要求目标对象必须实现接口

AOP专业术语:

  • 切面类

    • 横切关注点
    • 通知方法
    • 连接点

      • 切入点: 真正动态切入代码的位置
      • 切入点表达式

AOP使用步骤:

  1. 导包 spring-aspects (基础版)

    • 基础版xml <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.0.0.RELEASE</version> </dependency>
  • 加强版```xml cglib cglib 2.2 aopalliance aopalliance 1.0 org.aspectj aspectjweaver 1.6.8 ```

2.配置
1> 将目标类和切面类加入到容器中
2> 告诉spring哪个是切面类
3> 告诉spring, 切面类的通知方法何时何地运行
1)添加注解:
@Before: 在目标方法之前运行 前置通知
@After: 在目标方法结束之后 后置通知
@AfterReturning: 在目标方法正常返回之后 返回通知
@AfterThrowing: 在目标方法抛出异常之后运行 异常通知
@Around: 环绕 环绕通知
2)写切入点表达式
execution(访问权限符 返回值类型 方法签名)
@Before("execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))")
3)开启基于注解的AOP模式
3.测试

细节零. 从ioc容器拿到目标对象, 注意:如果目标对象的本类实现了接口, 想要用类型获取, 一定要用它的接口类型, 不能用它本类

细节一. AOP底层就是动态代理, 容器中保存的组件是目标对象的代理对象, 不是其本类; 如果目标类实现了接口, Spring就会使用动态代领创建代理对象, 如果没有实现接口则会使用cglib创建代理对象

细节二. 切入点不表达式的写法: 固定格式execution(访问权限符 返回值类型 方法全类名(参数表)), 具体参数名不写
通配符:

  • : 1)匹配任意个字符 2)匹配任意类型参数 3)如果*放在路径位置只能匹配一层路径 4)*不能放在访问权限符位置, 权限位置不写即为任意类型(实际上也只能切入public)
    .. : 1)匹配任意个任意类型参数execution(public int com.yguilai.impl.MyMathCalculator.*(..)) 2)匹配任意多层路径execution(public int com.yguilai..MyMathCalculator.*(..))
    其他特殊符: &&,||,!

细节三. 通知方法的执行顺序
正常执行: @Before —> @After —> @AfterReturning
异常执行: @Before —> @After —> @AfterThrowing

细节四. 通知方法运行时, 使用JoinPoint拿到目标方法的详细信息

细节五. 指定通知方法的参数来接收returning, throwing的返回值和异常值
接收返回值:
@AfterReturning(value = "execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))", returning = "result") public static void logReturn(JoinPoint joinPoint, Object result)
接收异常值:
@AfterThrowing(value = "execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))", throwing = "e") public static void logException(JoinPoint joinPoint, Exception e)

细节六. Spring对通知方法的约束
1.Spring对通知方法的要求不严格.
2.唯一的要求是方法的参数猎豹不能乱写! 通知方法是spring利用反射调用的, 每次方法调用得确定这个方法的参数表的值, 因此参数表上的每一个参数都必须让spring能够识别出
3.接收异常或返回值时,应该将接收范围扩大, 如使用Exception, Object

细节七. 抽取可重用的切入点表达式:
1.随便生命一个没有实现的返回void的空方法
2.给方法标注@Pointcut注解
3.原通知方法注解的value值改为空方法名的调用

@After(value = "pointCut()")
public static void logFinish(JoinPoint joinPoint) {
    System.out.println("[" + joinPoint.getSignature().getName() + "]方法结束");
}

@Pointcut("execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))")
public void pointCut(){}

细节八. @Around环绕通知: Spring中最强大的通知方法(根本上就是动态代理)
环绕通知就是: 前置通知,后置通知,返回通知,异常通知四合一. 环绕通知中有一个参数
环绕通知是优先于普通通知的

@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    // 利用反射调用目标方法
    Object proceed = null;//相当于method.invoke(obj, args)
    try {
        //@Before()
        proceed = pjp.proceed(pjp.getArgs());
        //@AfterReturning
    } catch (Throwable throwable) {
        //@AfterThrowing
        throwable.printStackTrace();
    } finally {
        //@After
    }
    return proceed;
}

细节九. 多切面运行顺序
先进后出, 后进先出
@Order(int) 通过注解修改切面运行顺序, 数值越小 优先级越高
环绕通知只影响当前切面的通知方法运行顺序


AOP使用场景:
1> AOP加日志保存到数据库
2> AOP做权限验证
3> AOP做安全检查
4> AOP做事务控制


基于注解的AOP模式小结:

  1. 将目标类和切面类加入到ioc容器中(添加@Component) )
  2. 告诉spring哪个是切面类(添加@Aspect) )
  3. 在切面类内配置通知方法的注解(@Before, @After, @AfterReturning, @AfterThrowing, @Around) )
  4. 在spring配置文件中开启基于注解的AOP功能(aop:aspectj-autoproxy/)

基于配置的AOP模式:

<aop:config>
    <!--指定切面类, 相当于@Aspect-->
    <aop:aspect ref="logUtils">
        <!--指定通知方法-->
        <aop:before method="logStart" pointcut-ref="pointcut"/>
        <aop:after method="logFinish" pointcut-ref="pointcut"/>
        <aop:after-returning method="logReturn" returning="result" pointcut-ref="pointcut"/>
        <aop:after-throwing method="logException" throwing="e" pointcut-ref="pointcut"/>
        <aop:pointcut id="pointcut"
                      expression="execution(public int com.yguilai.impl.MyMathCalculator.*(int, int))"/>
    </aop:aspect>
</aop:config>

注解: 快速方便
配置: 功能完善; 重要的用配置, 不重要的用注解