Spring AOP(Aspect Oriented Programming)模块

AOP在Java里是利用反射机制实现(你也可以认为是动态代理,不过动态代理也是反射机制实现的。

问题

有两个流程,取款流程和查询余额流程

两者有一个共同的验证流程:

有没有想过可以把这个验证用户的代码是提取出来,不放到主流程里去呢,这就是AOP的作用了,有了AOP,你写代码时不要把这个验证用户步骤写进去,即完全不考虑验证用户,你写完之后,在另外一个地方,写好验证用户的代码,然后告诉Spring你要把这段代码加到哪几个地方,Spring就会帮你加过去,而不要你自己Copy过去,这里还是两个地方,如果你有多个控制流呢,这个写代码的方法可以大大减少你的时间,不过AOP的目的不是这样,这只是一个“副作用”,真正目的是,你写代码的时候,事先只需考虑主流程,而不用考虑那些不重要的流程,懂C的都知道,良好的风格要求在函数起始处验证参数,如果在C上可以用AOP,就可以先不管校验参数的问题,事后使用AOP就可以隔山打牛的给所有函数一次性加入校验代码,而你只需要写一次校验代码。不知道C的没关系,举一个通用的例子,经常在debug的时候要打log吧,你也可以写好主要代码之后,把打log的代码写到另一个单独的地方,然后命令AOP把你的代码加过去,注意AOP不会把代码加到源文件里,但是它会正确的影响最终的机器代码。

术语解释

切面(Aspect):就是具有共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通java类,之所以能被AOP容器识别成切面,是在配置中实现的。
通知(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。
连接点(Joincut):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但Spring只支持方法级的连接点。
切入点(Poincut):用于定义通知应该切入到哪些连接点上,不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。
目标对象(Target):就是哪些即将切入切面的对象,也就是那些被通知的对象,这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的公共功能代码等待AOP容器的切入。
代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。
织入(Weaving):将切面应用到目标对象从而创建一个新代理对象的过程。这个过程可以在编译器、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。

不使用代理

public class Account {

  private static float MONEY=500.00f;

  /**
   * 显示余额
   * */
  public void showAccount(){
    //验证用户
    Authentication.auth();
    System.out.println("您的余额:"+MONEY);
  }

  /**
   * 取款
   * */
  public void withdrawCash(float count){
    //验证用户
    Authentication.auth();
    if(count>MONEY)
      System.out.println("余额不足!");
    else{
      MONEY=MONEY-count;
      System.out.println("您已成功取款:"+count);
    }
  } 

}
public class Authentication{

  /**
   * 验证用户
   * */
  public static void auth(){
    System.out.println("验证成功,请继续操作......");
  }


}
public class Client {

  static Scanner input = new Scanner(System.in);

  public static void main(String[] args) {
    Account account = new Account();
    boolean exist = false;
    while (!exist) {
      System.out.println("查询余额请按1");
      System.out.println("取款请按2");
      System.out.println("退出请按0");
      int flag = input.nextInt();
      switch (flag) {
        case 1:
          account.showAccount();
          break;
        case 2:
          System.out.println("请输入取款金额:");
          float c = input.nextFloat();
          account.withdrawCash(c);
          break;
        default:
          System.exit(0);
          break;
      }
      System.out.println("=========================");
    }
  }

}

使用代理

静态代理(代理模式)

/**
 * 抽象主题角色:声明了真实主题和代理主题的共同接口
 */
public interface CommonAccount {

  public void showAccount();

  public void withdrawCash(float count);
}
/**
 * 真实主题角色:定义真实的对象
 * */
public class Account implements CommonAccount{

  private static float MONEY=500.00f;

  /**
   * 显示余额
   * */
  public void showAccount(){
    System.out.println("您的余额:"+MONEY);
  }

  /**
   * 取款
   * */
  public void withdrawCash(float count){
    if(count>MONEY)
      System.out.println("余额不足!");
    else{
      MONEY=MONEY-count;
      System.out.println("您已成功取款:"+count);
    }
  } 

}
public class Authentication{

  /**
   * 验证用户
   * */
  public static void auth(){
    System.out.println("验证成功,请继续操作......");
  }

}
/**
 * 代理主题角色:内部包含对真实主题的引用,并且提供和真实主题相同的接口
 */
public class ProxyAccount implements CommonAccount {
  private CommonAccount commonAccount;

  public ProxyAccount(CommonAccount commonAccount) {
    this.setCommonAccount(commonAccount);
  }

  public void showAccount() {
    // 验证用户
    Authentication.auth();
    commonAccount.showAccount();
  }

  public void withdrawCash(float count) {
    // 验证用户
    Authentication.auth();
    commonAccount.withdrawCash(count);
  }

  public CommonAccount getCommonAccount() {
    return commonAccount;
  }

  public void setCommonAccount(CommonAccount commonAccount) {
    this.commonAccount = commonAccount;
  }

}
import java.util.Scanner;

public class Client {

  static Scanner input = new Scanner(System.in);

  public static void main(String[] args) {
    Account account = new Account();
    CommonAccount commonAccount=new ProxyAccount(account);
    boolean exist = false;
    while (!exist) {
      System.out.println("查询余额请按1");
      System.out.println("取款请按2");
      System.out.println("退出请按0");
      int flag = input.nextInt();
      switch (flag) {
        case 1:
          commonAccount.showAccount();
          break;
        case 2:
          System.out.println("请输入取款金额:");
          float c = input.nextFloat();
          commonAccount.withdrawCash(c);
          break;
        default:
          System.exit(0);
          break;
      }
      System.out.println("=========================");
    }
  }

}

总结:
1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:
因为代理对象需要与目标对象实现一样的接口,所以会有很多代理类,类太多.同时,一旦接口增加方法,目标对象与代理对象都要维护.

JDK动态代理

/**
 * 抽象主题角色:声明了真实主题和代理主题的共同接口
 */
public interface CommonAccount {

  public void showAccount();

  public void withdrawCash(float count);
}
/**
 * 真实主题角色:定义真实的对象
 * */
public class Account implements CommonAccount{

  private static float MONEY=500.00f;

  /**
   * 显示余额
   * */
  public void showAccount(){
    System.out.println("您的余额:"+MONEY);
  }

  /**
   * 取款
   * */
  public void withdrawCash(float count){
    if(count>MONEY)
      System.out.println("余额不足!");
    else{
      MONEY=MONEY-count;
      System.out.println("您已成功取款:"+count);
    }
  } 

}
public class Authentication{

  /**
   * 验证用户
   * */
  public static void auth(){
    System.out.println("验证成功,请继续操作......");
  }

}
/**
 * 动态代理
 */
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class DynamicProxy implements InvocationHandler {

  private Object target;

  /**
   * 写法固定:AOP专用,绑定委托对象并返回一个代理类
   * */
  public Object bind(Object target) {
    this.target = target;
    return Proxy.newProxyInstance(target.getClass().getClassLoader(),
        target.getClass().getInterfaces(), this);
  }

  /**
   * @param Object
   *                        proxy:指被代理的对象
   * @param Method
   *                        methos:要调用的方法
   * @param Object
   *                        [] args:方法调用时所需要的参数
   * */
  public Object invoke(Object proxy,
                       Method method,
                       Object[] args)
      throws Throwable {
    Object result=null;
    //切面之前执行
    System.out.println("切面之前执行");
    // 验证用户
    Authentication.auth();
    // 执行业务
    result=method.invoke(target, args);
    //切面之后执行
    System.out.println("切面之后执行");
    return result;
  }

}
import java.util.Scanner;

import org.dynamic.proxy.DynamicProxy;

public class Client {

  static Scanner input = new Scanner(System.in);

  public static void main(String[] args) {
    Account account = new Account();
    CommonAccount commonAccount = (CommonAccount) new DynamicProxy().bind(account);
    boolean exist = false;
    while (!exist) {
      System.out.println("查询余额请按1");
      System.out.println("取款请按2");
      System.out.println("退出请按0");
      int flag = input.nextInt();
      switch (flag) {
        case 1:
          commonAccount.showAccount();
          break;
        case 2:
          System.out.println("请输入取款金额:");
          float c = input.nextFloat();
          commonAccount.withdrawCash(c);
          break;
        default:
          System.exit(0);
          break;
      }
      System.out.println("=========================");
    }
  }

}

总结:
1.代理对象,不需要实现接口
2.代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)
3.动态代理也叫做:JDK代理,接口代理

Cglib动态代理

public class Account{

  private static float MONEY=500.00f;

  /**
   * 显示余额
   * */
  public void showAccount(){
    System.out.println("您的余额:"+MONEY);
  }

  /**
   * 取款
   * */
  public void withdrawCash(float count){
    if(count>MONEY)
      System.out.println("余额不足!");
    else{
      MONEY=MONEY-count;
      System.out.println("您已成功取款:"+count);
    }
  } 

}
public class Authentication{

  /**
   * 验证用户
   * */
  public static void auth(){
    System.out.println("验证成功,请继续操作......");
  }

}
import java.lang.reflect.Method;

import org.noaop.Authentication;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

public class CglibProxy implements MethodInterceptor {

  private Object target;

  /**
   * 创建代理对象
   * */
  public Object getInstance(Object target){
    this.target=target;
    //工具类
    Enhancer enhancer=new Enhancer();
    //设置代理对象的父类
    enhancer.setSuperclass(this.target.getClass());
    //回调方法
    enhancer.setCallback(this);
    //创建代理对象
    return enhancer.create();
  }

  public Object intercept(Object proxy,
                          Method method,
                          Object[] args,
                          MethodProxy methodProxy)
      throws Throwable {
    Object result=null;
    System.out.println("事务开始");
    //验证用户
    Authentication.auth();
    result=methodProxy.invoke(target, args);
    System.out.println("事务结束");
    return result;
  }

}
import org.cglib.proxy.CglibProxy;

public class Client {

  static Scanner input = new Scanner(System.in);

  public static void main(String[] args) {
    Account account = (Account) new CglibProxy().getInstance(new Account());
    boolean exist = false;
    while (!exist) {
      System.out.println("查询余额请按1");
      System.out.println("取款请按2");
      System.out.println("退出请按0");
      int flag = input.nextInt();
      switch (flag) {
        case 1:
          account.showAccount();
          break;
        case 2:
          System.out.println("请输入取款金额:");
          float c = input.nextFloat();
          account.withdrawCash(c);
          break;
        default:
          System.exit(0);
          break;
      }
      System.out.println("=========================");
    }
  }

}

Spring AOP的几种使用方式

实现接口

import java.lang.reflect.Method;

import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;

/**
 * 经典的基于代理的AOP
 * Before(之前) org.springframework.aop.MethodBeforeAdvice
 * After-returning(返回后) org.springframework.aop.AfterReturningAdvice
 * After-throwing(抛出后) org.springframework.aop.ThrowsAdvice
 * Arround(周围) org.springframework.intercept.MethodInterceptor (相当于将前三个结合起来)
 * Introduction(引入) org.springframework.aop.IntroductionInterceptor
 * */
public class SleepHelper implements MethodBeforeAdvice, AfterReturningAdvice {

  public void afterReturning(Object returnValue,
                             Method method,
                             Object[] args,
                             Object target)
      throws Throwable {
    System.out.println("起床后,完成新变更的需求");
  }

  public void before(Method method,
                     Object[] args,
                     Object target)
      throws Throwable {
    System.out.println("吃午饭");
  }

}
public interface Sleepable {
  void sleep();
}
public class Human implements Sleepable {

  public void sleep() {
    System.out.println("等老夫吃完午饭就回来睡觉");
  }

}

两种配置方式:
第一种:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

  public static void main(String[] args) {
    ApplicationContext appCtx=new ClassPathXmlApplicationContext("classpath:context.xml");
    Sleepable sleeper=(Sleepable) appCtx.getBean("human");
    sleeper.sleep();
  }

}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

    <!-- 切面 -->
    <bean id="sleepHelper" class="org.Springaop.baseonProxy.SleepHelper"></bean>

    <!-- 通知 -->
    <bean id="sleepHelperAdvisor"
        class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
        <property name="advice" ref="sleepHelper" />
        <property name="pattern" value=".*sleep" />
    </bean>

    <bean id="human" class="org.Springaop.baseonProxy.Human" />

    <bean
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" />

</beans>

第二种:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

  public static void main(String[] args) {
    ApplicationContext appCtx=new ClassPathXmlApplicationContext("classpath:context.xml");
    Sleepable sleeper=(Sleepable) appCtx.getBean("humanProxy");
    sleeper.sleep();
  }

}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">

    <!-- 切面 -->
    <bean id="sleepHelper" class="org.Springaop.baseonProxy.SleepHelper" />

    <!-- 切入点:定义了故事发生的地点 -->
    <!-- 1.正则表达式 2.AspectJ表达式 -->

    <!-- 正则表达式 -->
    <bean id="sleepPointcut" class="org.springframework.aop.support.JdkRegexpMethodPointcut">
        <property name="pattern" value=".*sleep" />
    </bean>

    <!-- 通知 -->
    <bean id="sleepHelperAdvisor" class="org.springframework.aop.support.DefaultPointcutAdvisor">
        <property name="advice" ref="sleepHelper" />
        <property name="pointcut" ref="sleepPointcut" />
    </bean>

    <bean id="human" class="org.Springaop.baseonProxy.Human">
    </bean>

    <bean id="humanProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="target" ref="human"></property>
        <property name="interceptorNames" value="sleepHelperAdvisor"></property>
        <property name="proxyInterfaces" value="org.Springaop.baseonProxy.Sleepable" />
    </bean>

</beans>

基于AspectJ注解的AOP

package org.Springaop.baseonAnnotation;

public interface Sleepable {
  void sleep();
}
public class Test {

  public static void main(String[] args) {
    ApplicationContext appCtx=new ClassPathXmlApplicationContext("classpath:context-annotation.xml");
    Sleepable sleeper=(Sleepable) appCtx.getBean("human");
    sleeper.sleep();
  }

}

两种拦截方式:
第一种:

public class Human implements Sleepable {

  public void sleep() {
    System.out.println("等老夫吃完午饭就回来睡觉");
  }

}

两种配置方式:
第一种:

import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class SleepHelper {

  @Pointcut("execution(* *.sleep())")
  public void sleeppoint(){}

  @Before("sleeppoint()")
  public void beforeSleep() {
    System.out.println("吃午饭");
  }

  @AfterReturning("sleeppoint()")
  public void afterSleep() {
    System.out.println("起床后,完成新变更的需求");
  }

}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <context:component-scan base-package="org.Springaop.baseonAnnotation" />

    <aop:aspectj-autoproxy />

    <bean id="human" class="org.Springaop.baseonAnnotation.Human" />
</beans>

第二种:

import org.springframework.stereotype.Component;

@Component
public class SleepHelper {

  public void beforeSleep() {
    System.out.println("吃午饭");
  }

  public void afterSleep() {
    System.out.println("起床后,完成新变更的需求");
  }

}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <context:component-scan base-package="org.Springaop.baseonAnnotation" />

    <aop:config>
        <aop:aspect ref="sleepHelper">
            <aop:before method="beforeSleep" pointcut="execution(* *.sleep())" />
            <aop:after method="afterSleep" pointcut="execution(* *.sleep())" />
        </aop:aspect>
    </aop:config>

    <bean id="human" class="org.Springaop.baseonAnnotation.Human" />
</beans>

第二种:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Action {
  String name();
}
public class Human implements Sleepable {

  @Action(name="注解式拦截")
  public void sleep() {
    System.out.println("等老夫吃完午饭就回来睡觉");
  }

}
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

@Component
@Aspect
public class SleepHelper {

  @Pointcut("@annotation(org.Springaop.baseonAnnotation.Action)")
  public void sleeppoint(){}

  @Before("sleeppoint()")
  public void beforeSleep() {
    System.out.println("吃午饭");
  }

  @AfterReturning("sleeppoint()")
  public void afterSleep() {
    System.out.println("起床后,完成新变更的需求");
  }

}
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context"
    xmlns="http://www.springframework.org/schema/beans"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.2.xsd">

    <context:component-scan base-package="org.Springaop.baseonAnnotation" />

    <aop:aspectj-autoproxy />

    <bean id="human" class="org.Springaop.baseonAnnotation.Human" />
</beans>

猜你喜欢

转载自blog.csdn.net/aimomo007/article/details/80179815