一、动态代理

代理模式是指给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用

代理:

抽象角色(接口类)
定义代理角色和真实角色公共对外方法
真实角色(实现类)
定义抽象角色,定义真实角色所要实现的业务逻辑,让代理角色调用
代理角色(代理实现类,最终使用的对象)

应用场景:

可以再不修改代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强,这样我们屏蔽了对真实角色的直接访问
Spring的AOP机制就是采用动态代理的机制来实现切面编程

代理的分类:

静态代理:

需要我们手动定义静态代理对象

动态代理:

  1. - JDK代理:基于接口的动态代理技术
  2. - cglib代理:基于父类的动态代理技术

静态代理代码实现:

接口类:

  1. package com.smiledog.staticagent;
  2. /**
  3. * @author SmILeDog
  4. */
  5. public interface Animal {
  6. /**
  7. * 唱歌
  8. */
  9. public void sing(double money);
  10. /**
  11. * 吃饭
  12. */
  13. public void eat();
  14. /**
  15. * 睡觉
  16. */
  17. public void sleep();
  18. }

被代理对象:

  1. package com.smiledog.staticagent;
  2. /**
  3. * 被代理对象
  4. * @author SmILeDog
  5. **/
  6. public class ZhouJieLun implements Animal {
  7. @Override
  8. public void sing(double money) {
  9. System.out.println("歌手收到酬劳"+money+",开始唱歌!!!");
  10. }
  11. @Override
  12. public void eat() {
  13. System.out.println("吃饭");
  14. }
  15. @Override
  16. public void sleep() {
  17. System.out.println("睡觉");
  18. }
  19. }

代理对象:

  1. package com.smiledog.staticagent;
  2. /**
  3. * 代理对象
  4. * @author SmILeDog
  5. **/
  6. public class Proxy implements Animal {
  7. private ZhouJieLun zhouJieLun;
  8. public Proxy(ZhouJieLun zhouJieLun) {
  9. this.zhouJieLun = zhouJieLun;
  10. }
  11. @Override
  12. public void sing(double money) {
  13. //经纪人获得投资方报酬,抽取报酬
  14. System.out.println("经纪人拿到报酬"+money);
  15. money = money/2;
  16. System.out.println("提取报酬"+money+",安排歌手!!!");
  17. //把报酬剩余部分给歌手,安排歌手工作
  18. zhouJieLun.sing(money);
  19. }
  20. @Override
  21. public void eat() {
  22. System.out.println("吃饭");
  23. }
  24. @Override
  25. public void sleep() {
  26. System.out.println("睡觉");
  27. }
  28. }

测试类:

  1. package com.smiledog.staticagent;
  2. /**
  3. * 静态代理
  4. * @author SmILeDog
  5. **/
  6. public class StaticProxy {
  7. public static void main(String[] args) {
  8. //创建被代理对象
  9. ZhouJieLun zhouJieLun = new ZhouJieLun();
  10. //创建代理对象,把被代理对象直接给代理对象
  11. Proxy proxy = new Proxy(zhouJieLun);
  12. //通过代理对象,调用被代理对象的方法
  13. proxy.sing(1000);
  14. }
  15. }

动态代理代码实现:

接口类:省略,同上
被代理类:

  1. package com.smiledog.dynamicagent;
  2. /**
  3. * 被代理对象
  4. * @author SmILeDog
  5. **/
  6. public class ZhouJieLun implements Animal {
  7. @Override
  8. public String sing(double money) {
  9. System.out.println("歌手收到酬劳"+money+",准备唱歌!!!");
  10. return "代理对象说:马上开始了!";
  11. }
  12. @Override
  13. public void eat() {
  14. System.out.println("吃饭");
  15. }
  16. @Override
  17. public void sleep() {
  18. System.out.println("睡觉");
  19. }
  20. }

JDK代理测试:

  1. package com.smiledog.dynamicagent;
  2. import java.lang.reflect.InvocationHandler;
  3. import java.lang.reflect.InvocationTargetException;
  4. import java.lang.reflect.Method;
  5. import java.lang.reflect.Proxy;
  6. /**
  7. * @author SmILeDog
  8. **/
  9. public class DynamicAgent {
  10. public static void main(String[] args) {
  11. //创建歌手
  12. ZhouJieLun zhouJieLun = new ZhouJieLun() ;
  13. //创建动态代理对象
  14. /**
  15. * 参数解释
  16. * 参数一: 真实对象getClass().getClassLoader()
  17. * 参数二: 真实对象getClass().getInterfaces()
  18. * 参数三: 处理器new InvocationHandler
  19. * 注意:此上为固定写法
  20. *
  21. */
  22. Animal proxyObject = (Animal) Proxy.newProxyInstance(zhouJieLun.getClass().getClassLoader(), zhouJieLun.getClass().getInterfaces(), new InvocationHandler() {
  23. /**
  24. * proxy:为代理对象
  25. * method:代理对象调用的方法,被封装为Method对象
  26. * args:代理对象调用方法时,传递的参数
  27. * return:代理操作后的对象
  28. */
  29. @Override
  30. public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException {
  31. /**
  32. * 代理操作,增强方法sing
  33. * 判断调用的是否为sing方法
  34. */
  35. if (method.getName().equals("sing")){
  36. //对参数对象进行强化,获取报酬
  37. double money = (double)args[0];
  38. //提取代理费
  39. System.out.println("代理拿到报酬"+money);
  40. money = money/2;
  41. System.out.println("代理提取报酬"+money+",安排歌手");
  42. //调用歌手和报酬安排工作
  43. Object obj = method.invoke(zhouJieLun, money);
  44. //对返回值进行增强
  45. return obj + "\n开始之前,给大家准备了一些小礼物!";
  46. }else {
  47. // 其他方法,还是执行原有的逻辑
  48. Object obj = method.invoke(zhouJieLun, args);
  49. return obj;
  50. }
  51. }
  52. });
  53. String sing = proxyObject.sing(66666.888);
  54. System.out.println(sing);
  55. }
  56. }

二、AOP概述

AOP为(Aspect Oriented Programming)的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术
AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生泛型,利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高开发效率

  1. 1、在程序运行期间,不修改源码的情况下对方法进行功能增强
  2. 2、逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
  3. 3、减少重复代码,提高开发效率,便于后期维护

1、AOP的底层实现

实际上,AOP的底层是通过动态代理技术实现的,在运行期间,Spring通过动态代理技术生成代理对象,代理对象方法执行时进行增强功能介入,在去调用目标对象的方法,从而完成功能的增强

2、AOP的动态代理技术

JDK代理:基于接口的动态代理技术
cglib代理:基于父类的动态代理技术

3、AOP的相关专业术语

目标对象(target)

  1. - 目标对象指将要被增强的对象,即包含主业务逻辑的类对象

连接点(JoinPoint)

  1. - 程序执行过程中明确的点,如方法的调用或特定的异常被抛出
  2. - 连接点由两个信息确定:
  3. - 方法(表示程序执行点,即在哪个目标方法)
  4. - 相对点(表示方位,即目标方法的什么位置,比如调用前,后等)
  • 简单来说,连接点就是被拦截到程序执行点,因为Spring只支持方法类型的连接点,所以在Spring中连接点就是被拦截到的方法

    代理对象(Proxy)

    • AOP中会通过代理的方式,对目标对象生成一个代理对象,代理对象中会加入需要增强功能,通过代理对象来间接的方式目标对象,起到增强目标对象的效果

      通知/增强(Advice)

    • 需要在目标对象中实现增强的功能

      切入点(Pointcut)

    • 用来指定需要通知使用到哪些地方,比如需要用到哪些类的哪些方法上,切入点就是做这个配置的

      切面(Aspect)

    • 通知(Advice)和切入点(Pointcut)的组合,切面来定义在哪些地方(Pointcut)执行什么操作(Advice)

      织入(Weaving)

    • 把Advice加到Target上,被创建出代理对象的过程

      三、AOP快速入门

      SpringAOP的约束和命名空间

      1. <beans xmlns="http://www.springframework.org/schema/beans"
      2. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      3. xmlns:context="http://www.springframework.org/schema/context"
      4. xmlns:aop ="http://www.springframework.org/schema/aop"
      5. xsi:schemaLocation="http://www.springframework.org/schema/beans
      6. http://www.springframework.org/schema/beans/spring-beans.xsd
      7. http://www.springframework.org/schema/context
      8. http://www.springframework.org/schema/context/spring-context.xsd
      9. http://www.springframework.org/schema/aop
      10. http://www.springframework.org/schema/aop/spring-aop.xsd
      11. ">

      大致使用如下:
      在我们做某些功能前或者后,或者功能能出错,或者是功能完整走完, 在上述几点我们可以做一些格外的操作,在不改变核心代码的情况加,增加一些额外的功能或者是增强原功能 ```java

  1. <a name="bulgZ"></a>
  2. ## 四、基于xml的AOP
  3. <a name="31ZTN"></a>
  4. ### 1、切点表达式
  5. ```java
  6. 切点表达式语法:
  7. execution(<修饰符>?<返回类型> <返回值类型>?<方法名>(<参数>)<异常>?)
  8. 1、访问修饰可以省略
  9. 2、返回值类型,包名,类名,方法名可以使用星号*代替,代表任意
  10. 3、包名与类名之间的一个点.代表当前包写的类,两个点..表示当前包及其子包下的类
  11. 4、参数列表可以使用两个点..表示任意个数,任意类型的参数列表

返回值类型void且public修饰的所有方法

  1. execution(public void com.smiledog.service.impl.EmployeeServiceImpl.*(..))

任意修饰符任意返回值的所有方法

  1. execution(* com.smiledog.service.impl.EmployeeServiceImpl.*(..))

控制service层所有对象的方法【最常用】

  1. execution(* com.smiledog.service..*.*(..))

抽取切点表达式

  1. 定义切点表达式:
  2. <aop:pointcut id="myPointcut" expression="execution(* com.smiledog.service..*.*(..))"></aop:pointcut>
  1. 引用切点表达式:
  2. method="before" pointcut-ref="myPointcut" />

2、通知类型

  • 前置通知(aop:before)
    • 再被增强方法之前执行(权限控制,日志记录等)
  • 后置通知(aop:afterReturning)
    • 被增强方法正常执行完毕后执行,执行过程中无异常(提交事务/统计分析结果等)
  • 异常通知(aop:afterThrowing)
    • 被增强方法出现异常时执行(回滚事务/记录异常的日志信息等)
  • 最终通知(aop:after)
    • 被增强方法无论是否发生异常,最终都执行的一种操作(释放资源)
  • 环绕通知(aop:around)

    • 可以自定义增强方法的什么时机执行(返回值Object,参数processedingJoinpoint)(缓存。性能日志,权限,事务管理)

      四大通知:

      ```java public class TransactionManagerAdvice {

    // 前置通知 public void before() { System.out.println(“前置增强………”); }

    // 后置增强 public void afterReturning(){ System.out.println(“后置通知…”); }

    // 异常增强 public void afterThrowing(){ System.out.println(“异常通知…”); }

    // 最终增强 public void after(){ System.out.println(“最终通知…”); } }

    1. <aop:config proxy-target-class="true">
    2. <aop:pointcut id="myPointcut" expression="execution(* com.smiledog.service..*.*(..))"></aop:pointcut>
    3. <!--配置AOP切面-->
    4. <aop:aspect ref="txManager">
    5. <!--织入(通知+方法)
    6. <aop:before> 前置通知,特点在目标方法执行之前,进行增强
    7. 具体执行的增强方法 method="通知的具体方法"
    8. 切点:pointcut="切点表达式"
    9. -->
    10. <!--前置通知-->
    11. <aop:before method="before" pointcut-ref="myPointcut" />
    12. <!--后置通知-->
    13. <aop:after-returning method="afterReturning" pointcut-ref="myPointcut" />
    14. <!--异常通知-->
    15. <aop:after-throwing method="afterThrowing" pointcut-ref="myPointcut" />
    16. <!--最终通知-->
    17. <aop:after method="after" pointcut-ref="myPointcut" />
    18. </aop:aspect>
    19. </aop:config>
    20. ```
    21. <a name="LAA2n"></a>
    22. #### 环绕通知:
    23. ```java
    24. /**
    25. * Proceeding(运行)JoinPoint(连接点) = 切点
    26. * @param pjp
    27. */
    28. public void around(ProceedingJoinPoint pjp) {
    29. try {
    30. // 做前置通知
    31. System.out.println("前置通知..........");
    32. // 执行切点(调用目标对象原有的方法)
    33. pjp.proceed();
    34. // 做后置通知
    35. System.out.println("后置通知..........");
    36. } catch (Throwable throwable) {
    37. throwable.printStackTrace();
    38. // 做异常通知
    39. System.out.println("异常通知..........");
    40. } finally {
    41. // 做最终通知
    42. System.out.println("最终通知..........");
    43. }
    44. }

    1. <a name="ucqAg"></a>
    2. ## 四、基于注解的AOP
    3. <a name="rsaGA"></a>
    4. ### 1、开启AOP注解
    5. ```java
    6. <!--开启Spring的AOP注解支持-->
    7. <aop:aspectj-autoproxy/>

    2、四大通知版注解

    1. @Component
    2. @Aspect // 切面类
    3. public class TransactionManagerAdvice {
    4. // 抽取切点表达式
    5. @Pointcut("execution(* com.smiledog.service..*.*(..))")
    6. public void myPointcut(){}
    7. // 前置通知
    8. // 引用切点
    9. @Before("myPointcut()")
    10. public void before() {
    11. System.out.println("前置增强.........");
    12. }
    13. // 后置通知
    14. @AfterReturning("myPointcut()")
    15. public void afterReturning(){
    16. System.out.println("后置通知...");
    17. }
    18. // 异常通知
    19. @AfterThrowing("myPointcut()")
    20. public void afterThrowing(){
    21. System.out.println("异常通知...");
    22. }
    23. // 最终增强
    24. @After("myPointcut()")
    25. public void after(){
    26. System.out.println("最终通知...");
    27. }
    28. }

    :四大通知在一起使用时,后置通知和异常通知也是互斥,而且使用注解版的通知,执行的顺序跟xml版的执行顺序是不一样的

    3、环绕通知版注解

    1. @Component
    2. @Aspect // 切面类
    3. public class TransactionManagerAdvice {
    4. // 抽取切点表达式
    5. @Pointcut("execution(* com.smiledog.service..*.*(..))")
    6. public void myPointcut(){}
    7. // 环绕通知
    8. /**
    9. * Proceeding(运行)JoinPoint(连接点) = 切点
    10. * @param pjp
    11. */
    12. @Around("myPointcut()")
    13. public void around(ProceedingJoinPoint pjp) {
    14. try {
    15. // 做前置通知
    16. System.out.println("前置通知..........");
    17. // 执行切点(调用目标对象原有的方法)
    18. pjp.proceed();
    19. // 做后置通知
    20. System.out.println("后置通知..........");
    21. } catch (Throwable throwable) {
    22. throwable.printStackTrace();
    23. // 做异常通知
    24. System.out.println("异常通知..........");
    25. } finally {
    26. // 做最终通知
    27. System.out.println("最终通知..........");
    28. }
    29. }
    30. }