Spring学习之==>AOP

一、概述

  AOP(Aspect Oriented Programming)称为面向切面编程,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等等,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。在不改变原有的逻辑的基础上,增加一些额外的功能。代理也是这个功能,读写分离也能用 AOP 来做。

  AOP可以说是 OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

  AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为"Aspect",即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。

  使用"横切"技术,AOP把软件系统分为两个部分:核心关注点和横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证、日志、事物。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

二、问题解决方案

前面我们提到了,使用 AOP 思想能够为我们解决日志、事物、权限等等的问题。现在有一个需求:为老的业务系统添加计算所有核心业务中方法执行的时间并记录日志。

public interface IInfoService {

  String addInfo(String info) throws Throwable;

  String updateInfo(String info);

  String delInfo(String info);

  String queryInfo(String info);
}

IInfoService 接口

public class InfoServiceImpl implements IInfoService {
  @Override
  public String addInfo(String info) {

    System.out.println("InfoServiceImpl.addInfo");

    return "InfoServiceImpl.addInfo111";
  }

  @Override
  public String updateInfo(String info) {

    System.out.println("InfoServiceImpl.updateInfo");

    return "InfoServiceImpl.updateInfo";
  }

  @Override
  public String delInfo(String info) {

    System.out.println("InfoServiceImpl.delInfo");

    return "InfoServiceImpl.delInfo";
  }

  @Override
  public String queryInfo(String info) {

    System.out.println("InfoServiceImpl.queryInfo");

    return "InfoServiceImpl.queryInfo";
  }
}

InfoServiceImpl 实现类

IInfoService 接口,当中有 addInfo、updateInfo、delInfo、queryInfo 四个方法,InfoServiceImpl 类中实现了这四个方法,假如我们要统计 InfoServiceImpl 类中这四个方法具体的执行时间并为每个方法的执行情况都写入日志,我们应该如何来实现呢?

1、模板设计模式

我们设计了如下一个模板类:

public class ServiceTemplate {

  public static <T> T test(Supplier<T> supplier) {
    // 1.记录开始时间并打印方法名,这里只能打印固定的方法名
    long start = System.currentTimeMillis();
    System.out.println("addInfo start...");

    // 2.获取传入对象supplier的对象实例
    T resp = supplier.get();

    // 3.记录结束时间并打印方法名
    long end = System.currentTimeMillis();
    System.out.println("addInfo end...");
    System.out.println("cost time:" + (end - start));

    return resp;
  }
}

我们使用了一个供给型(无参数,有返回值)的对象 supplier 作为参数传给静态方法 test(),在 supplier.get() 方法执行前和执行后分别记录时间,最后计算出方法执行的时间。

再来看看如何调用 InfoServiceImpl 类中的四个方法:

public class AppTemplate {

  public static void main(String[] args) {  // 生成 Supplier 对象
    Supplier<String> supplier = () -> {
      try {     IInfoService infoService = new InfoServiceImpl(); 
        return infoService.addInfo("");
      } catch (Throwable throwable) {
        throwable.printStackTrace();
      }
      return null;
    };

    /**
     * 模板模式调用
     * 调用 ServiceTemplate.test() 方法会执行supplier.get()方法
     * 从而执行上面的infoService.addInfo("")
     */
    String resp = ServiceTemplate.test(supplier);

    System.out.println(resp);
    System.out.println("---------------模板调用模式---------------");
  }
}

整个执行过程是这样的:首先会生成一个 Supplier 对象作为参数传给 ServiceTemplate.test() 方法,test() 方法执行时会先打印 "addInfo start..." 并记录方法开始执行的时间,然后调用 supplier.get() 方法时会执行 infoService.addInfo("") 方法,最后打印 "addInfo end..." 并计算方法执行的时间,运行结果如下:

但是,我们发现如果要执行其他方法时(比如updateInfo),把 infoService.addInfo("") 改为了 infoService.updateInfo(""),运行结果如下:

执行的方法变了,但是打印的开头日志和结尾的日志并没有变,因为我们在模板中日志的打印是写死的,没办法根据方法名而改变,因为在模板中调用哪个方法我们根本不知道,所以这种实现方式还是不完美。

2、装饰器设计模式

还是使用上面的 IInfoService 接口和 InfoServiceImpl 实现类。然后我们在设计一个 IInfoService 接口的实现类,如下:

public class DeclareInfoServiceImpl implements IInfoService {

  private IInfoService infoService;

  public DeclareInfoServiceImpl(IInfoService infoService) {
    this.infoService = infoService;
  }

  @Override
  public String addInfo(String info) {

    System.out.println("addInfo start...");

    String resp = null;
    try {
      resp = this.infoService.addInfo(info);
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    }

    System.out.println("addInfo end...");

    return resp;
  }

  @Override
  public String updateInfo(String info) {
    System.out.println("updateInfo start...");

    String resp = this.infoService.updateInfo(info);

    System.out.println("updateInfo end...");

    return resp;
  }

  @Override
  public String delInfo(String info) {
    System.out.println("delInfo start...");

    String resp = this.infoService.delInfo(info);

    System.out.println("delInfo end...");

    return resp;
  }

  @Override
  public String queryInfo(String info) {
    System.out.println("queryInfo start...");

    String resp = this.infoService.queryInfo(info);

    System.out.println("queryInfo end...");

    return resp;
  }
}

DeclareInfoServiceImpl 实现类

该类实现自IInfoService 接口,构造方法参数为 IInfoService 对象,实现的所有方法除了必要的日志打印和时间计算外,再用IInfoService 对象调用一次同名的 addInfo、updateInfo 等方法,这样如果我们传入的是 InfoServiceImpl 类的对象,就能在执行方法的前后加上日志及计算时间。测试代码如下:

public class AppDeclare {

  public static void main(String[] args) throws Throwable {

    // 装饰器模式调用
    IInfoService infoService = new DeclareInfoServiceImpl(new InfoServiceImpl());
    infoService.addInfo("");
    System.out.println("---------------装饰器调用模式---------------");
  }
}

初看装饰器调用模式,发现很像 IO 文件操作体系中的缓冲流操作。没错,缓冲流操作也是用装饰器模式来设计的。下面来看下运行结果:

但是这种模式缺点也十分明显,我们需要对 DeclareInfoServiceImpl 类的所有实现方法都手动加上日志,如果方法非常多的情况下是不太可能的。

3、动态代理设计模式

动态代理模式使用的是JDK自带的类 Proxy 的 newProxyInstance() 方法生成一个代理对象,然后再用代理对象去执行对应的方法。

public class ServiceProxy {

  public static void main(String[] args) throws Throwable {

    IInfoService infoService = new InfoServiceImpl();

    /**
     * 构建Proxy,生成代理对象proxyInfoService
     * ClassLoader loader,
     * Class<?>[] interfaces,
     * InvocationHandler h
     */
    IInfoService proxyInfoService = (IInfoService) Proxy.newProxyInstance(
        // ClassLoader loader:类加载器
        infoService.getClass().getClassLoader(),
        // Class<?>[] interfaces:被代理对象的接口
        infoService.getClass().getInterfaces(),
        // InvocationHandler h:
        new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              // 1.入口log
              long start = System.currentTimeMillis();
              System.out.println(String.format("%s start...", method.getName()));

              // 2. 执行业务代码
              // 相当于是执行 InfoServiceImpl.addInfo方法的逻辑
              Object response = method.invoke(infoService, args);

              // 3.出口log
              long end = System.currentTimeMillis();
              System.out.println(String.format("%s end...", method.getName()));
              System.out.println("cost time:" + (end - start));

              // 返回业务代码执行后的结果
              return response;}});

    // 通过代理对象调用具体的方法
    proxyInfoService.addInfo("");
    proxyInfoService.updateInfo("");
  }
}

newProxyInstance() 方法有三个参数:

  • 第一个参数 ClassLoader 是类加载器,通过 infoService.getClass().getClassLoader() 来获取被代理对象 infoService 的类加载器;
  • 第二个参数 Class<?>[] interfaces 是被代理对象 infoService 所属接口的字节码对象数组,通过 infoService.getClass().getInterfaces() 来获取;
  • 第三个参数 InvocationHandler 对象重写了 InvocationHandler 类的 invoke() 方法,我们可以在里面增加时间统计、日志打印等功能。在 invoke() 方法内是通过 method.invoke() 方法执行被代理对象 infoService 对应的 addInfo()、updateInfo()等方法。

生成的代理对象 proxyInfoService 类似下面这个类的对象,只不过是虚拟的,我们看不到。

public class ProxyInfoServiceImpl implements IInfoService {

  private IInfoService infoService;

  private InvocationHandler h;

  public ProxyInfoServiceImpl(IInfoService infoService) {
    this.infoService = infoService;
  }

  @Override
  public String addInfo(String info) throws Throwable {

    System.out.println("start");

    String resp = infoService.addInfo(info);

    System.out.println("end");

    // 大概模拟下InvocationHandler的调用过程
    return (String) h.invoke(this.infoService, null, new Object[]{info});
  }

  @Override
  public String updateInfo(String info) {
    return null;
  }

  @Override
  public String delInfo(String info) {
    return null;
  }

  @Override
  public String queryInfo(String info) {
    return null;
  }
}

生成的虚拟代理类

这个类看上去很像装饰器模式的 DeclareInfoServiceImpl 实现类,它也是实现自接口 IInfoService,只不过在每个方法都会调一次 invike() 方法。

运行结果如下:

三、

四、

原文地址:https://www.cnblogs.com/L-Test/p/11609338.html

时间: 2024-10-16 11:28:16

Spring学习之==>AOP的相关文章

spring学习(二) ———— AOP之AspectJ框架的使用

前面讲解了spring的特性之一,IOC(控制反转),因为有了IOC,所以我们都不需要自己new对象了,想要什么,spring就给什么.而今天要学习spring的第二个重点,AOP.一篇讲解不完,所以这篇文章主要介绍一下什么是AOP,如何去理解AOP.理解完之后,在spring中如何使用AspectJ AOP框架的.看得懂,写的出spring配置的那么就学的差不多了.加油.建议都自己手动实现一遍,这样才能更好的理解. --WH 一.什么是AOP? AOP:面向切面编程,采用横向抽取机制,取代了传

Spring学习笔记AOP(四)

鲁春利的工作笔记,好记性不如烂笔头 基于XML配置方式声明切面 Spring使用org.springframework.aop.Advisor接口表示切面的概念,Advisor表示只有一个通知(org.aopalliance.aop.Advice)和一个切入点(org.springframework.aop.Pointcut)的切面.Advisor可以使用<aop:config>标签下的<aop:advisor>标签定义. <aop:advisor id="标识&q

Spring学习之AOP

一.概述 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. 简而言之,aop的作用就是在不修改源码的情况下对程序进行增强,如权限校验,日志记录,性

Spring学习笔记AOP(二)

鲁春利的工作笔记,好记性不如烂笔头 要进行AOP编程,首先我们要在spring的配置文件中引入aop命名空间: 引入后AOP命名空间并启动对@AspectJ注解的支持(spring-context-aop-annotation.xml): <beans xmlns="http://www.springframework.org/schema/beans"     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance&q

Spring学习笔记-AOP前传之动态代理

假设有如下需求: 写一个计算器类,里面包含加减乘除四个方法.在每个方法开始前打印出该方法开始的消息,在每个方法结束前打印出该方法结束的消息和计算的结果. 普通方法,先写一个借口,然后在接口里实现四个方法.在每个方法里加上要打印的语句.实现代码如下. ArithmeticCalculator接口 package com.spring.aop.helloworld; public interface ArithmeticCalculator { int add(int i, int j); int 

Spring学习笔记----AOP编程

先用代码讲一下什么是传统的AOP(面向切面编程)编程 需求:实现一个简单的计算器,在每一步的运算前添加日志.最传统的方式如下: Calculator.java package cn.limbo.spring.aop.calculator; /** * Created by Limbo on 16/7/14. */ public interface Calculator { int add(int i , int j); int sub(int i , int j); int mul(int i

Spring学习之Aop的各种增强方法

AspectJ允许使用注解用于定义切面.切入点和增强处理,而Spring框架则可以识别并根据这些注解来生成AOP代理.Spring只是使用了和AspectJ 5一样的注解,但并没有使用AspectJ的编译器或者织入器,底层依然使用SpringAOP来实现,依然是在运行时动态生成AOP代理,因此不需要增加额外的编译,也不需要AspectJ的织入器支持.而AspectJ采用编译时增强,所以AspectJ需要使用自己的编译器来编译Java文件,还需要织入器. 为了启用Spring对@AspectJ切面

Spring学习之Aop的基本概念

转自:http://my.oschina.net/itblog/blog/209067 AOP的基本概念 AOP从运行的角度考虑程序的流程,提取业务处理过程的切面.AOP面向的是程序运行中的各个步骤,希望以更好的方式来组合业务逻辑的各个步骤.AOP框架并不与特定的代码耦合,AOP框架能处理程序执行中特定切入点,而不与具体某个类耦合(即在不污染某个类的情况下,处理这个类相关的切点).下面是一些AOP的一些术语: 切面(Aspect):业务流程运行的某个特定步骤,也就是应用运行过程的关注点,关注点通

spring学习 注解AOP 通知传递参数

我们在对切点进行增强时,不建议对切点进行任何修改,因此不加以使用@PointCut注解打在切点上,尽量只在Advice上打注解(Before,After等),如果要在通知中接受切点的参数,可以使用JoinPoint或者ProceedingJoinPoint 在Spring AOP中可以通过两种方式传递参数给Advice(通知) (1)通过接受JoinPoint(非环绕通知)或ProceedingJoinPoint(环绕通知)参数,其实ProceedingJoinPoint是JointPoint的

spring学习之aop(FactoryBean代理)

实验结构: MyAspect.java 1 package com.itheima.factorybean; 2 3 import org.aopalliance.intercept.MethodInterceptor; 4 import org.aopalliance.intercept.MethodInvocation; 5 6 //切面类 7 public class MyAspect implements MethodInterceptor { 8 9 @Override 10 publ