高手的存在,就是让服务10亿人的时候,你感觉只是为你一个人服务......

Spring AOP

目录
  1. 1. 动态代理
    1. 1.1. 使用动态代理实现日志打印
  • AOP
    1. 1. @Before前置通知
    2. 2. @After 后置通知
    3. 3. @AfterReturning 返回通知
    4. 4. 异常通知
    5. 5. 从动态代理到AOP
    6. 6. AspectJ的优先级
  • AOP(Aspect Oriented Programming)- 面向切面编程,是Spring的两大特征之一(IOC&&AOP)。

    Alt text


    动态代理

    在AOP之前,先聊聊动态代理。AOP是建立在动态代理的基础上实现的。

    举个例子,在业务处理前打印日志。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    import org.springframework.context.ApplicationContext;

    public class Test {

    public static void main(String[] args) {

    ServiceWorkImpl servicework = new Test().new ServiceWorkImpl();

    System.out.println("log.....");

    servicework.doSometing();

    System.out.println("log.....");

    servicework.doSometing();

    }



    public interface ServiceWork {

    public void doSometing();

    }


    public class ServiceWorkImpl implements ServiceWork {

    public void doSometing() {
    System.out.println("doSometing");

    }

    }

    }

    通过上面的方式,如果日志内容发生变化的话,修改起来比较麻烦。


    使用动态代理实现日志打印

    同过动态代理类进行日志打印,实现一个LoggingProxy类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;

    import org.aopalliance.intercept.Invocation;

    public class LoggingProxy {

    private Test.ServiceWork target;

    public LoggingProxy(Test.ServiceWork target) {
    this.target = target;
    }

    public Test.ServiceWork getLoggingProxy() {

    Test.ServiceWork proxy = null;

    // 代理对象的类加载器
    ClassLoader loader = target.getClass().getClassLoader();

    // 代理对象的类型
    Class[] interfaces = new Class[] { Test.ServiceWork.class };

    // 当调用代理对象其中的方法时,该执行的代码
    InvocationHandler hander = new InvocationHandler() {

    /**
    * proxy:正在返回的代理对象 method:被调用的方法 args:传入的参数
    */
    public Object invoke(Object proxy, Method method, Object[] args)
    throws Throwable {
    // 日志打印
    System.out.println("log.....");

    return method.invoke(target, args);
    }
    };

    proxy = (Test.ServiceWork) Proxy.newProxyInstance(loader, interfaces,
    hander);
    return proxy;

    }

    }

    使用动态代理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    import org.springframework.context.ApplicationContext;

    public class Test {

    public static void main(String[] args) {

    // ServiceWorkImpl servicework = new Test().new ServiceWorkImpl();
    //
    // System.out.println("log.....");
    //
    // servicework.doSometing();

    ServiceWork target = new Test().new ServiceWorkImpl();

    ServiceWork serviceWork = new LoggingProxy(target).getLoggingProxy();

    serviceWork.doSometing();

    serviceWork.doSometing();

    }

    public interface ServiceWork {

    public void doSometing();

    }

    public class ServiceWorkImpl implements ServiceWork {

    public void doSometing() {
    System.out.println("doSometing");

    }

    }

    }

    开发的时候写动态代理类,也是比较麻烦,spring提供了AOP的方式。


    AOP

    Alt text

    术语:

    切面(Aspect): 横切关注点(跨越应用程序多个模块的功能)被模块化的特殊对象
    通知(Advice): 切面必须要完成的工作
    目标(Target): 被通知的对象
    代理(Proxy): 向目标对象应用通知之后创建的对象
    连接点(Joinpoint):程序执行的某个特定位置:如类某个方法调用前、调用后、方法抛出异常后等。连接点由两个信息确定:方法表示的程序执行点;相对点表示的方位。例如 ArithmethicCalculator#add() 方法执行前的连接点,执行点为 ArithmethicCalculator#add(); 方位为该方法执行前的位置
    切点(pointcut):每个类都拥有多个连接点:例如 ArithmethicCalculator 的所有方法实际上都是连接点,即连接点是程序类中客观存在的事务。AOP 通过切点定位到特定的连接点。类比:连接点相当于数据库中的记录,切点相当于查询条件。切点和连接点不是一对一的关系,一个切点匹配多个连接点,切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。

    srping2.0以后AOP通常使用AspectJ注解的方式。切面只是一个带有 @Aspect 注解的 Java 类。

    通知是标注有某种注解的简单的 Java 方法,AspectJ 支持 5 种类型的通知注解:
    @Before: 前置通知, 在方法执行之前执行
    @After: 后置通知, 在方法执行之后执行
    @AfterReturning: 返回通知, 在方法返回结果之后执行
    @AfterThrowing: 异常通知, 在方法抛出异常之后
    @Around: 环绕通知, 围绕着方法执行


    @Before前置通知

    在方法开始前执行,编写aspectj类进行前置通知:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;

    //将此类声明为一个切面,把该类放入IOC容器中,再声明为一个切面
    @Aspect
    @Component
    public class LoggingAspect {

    //声明该方法是一个前置通知:在目标方法开始之前执行
    @Before("execution(public void com.spring.test.test.ServiceWorkImpl.doSometing())")
    public void beforeMethod(JoinPoint joinPoint) {
    String methodName = joinPoint.getSignature().getName();
    System.out.println("beforeMethod logging....." + methodName);
    }


    }

    配置文件中增加aspectj的配置:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/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"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">


    <context:component-scan base-package="com.spring.test"/>


    <!--aspect config-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    </beans>

    测试方法:

    1
    2
    3
    4
    5
    6
    7
    ApplicationContext ctx = new ClassPathXmlApplicationContext(
    "spring-component-scan.xml");


    ServiceWorkImpl target = (ServiceWorkImpl) ctx
    .getBean("serviceWorkImpl");

    target.doSometing();

    结果:

    beforeMethod logging…..doSometing
    doSometing

    在业务处理前,会打印beforeMethod logging…..日志信息。


    @After 后置通知

    在方法结束后执行,在aspectj类中增加@after方法即可:

    1
    2
    3
    4
    5
    6
    //在方法结束后执行
    @After("execution(* *.doSometing())")
    public void afterMethod(JoinPoint joinPoint){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("afterMethod logging....." + methodName);
    }

    @AfterReturning 返回通知

    返回通知, 在方法返回结果之后执行.

    1
    2
    3
    4
    5
    6
    7
    //在方法返回结果之后执行,void方法也会执行
    //返回通知可以访问到方法的返回值
    @AfterReturning(value="execution(* *.doSometing())",returning="result")
    public void afterReturnMethod(JoinPoint joinPoint,Object result){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("afterReturnMethod logging....." + methodName+result);
    }

    异常通知

    异常通知, 在方法抛出异常之后

    1
    2
    3
    4
    5
    6
    //方法抛出异常时,通知
    @AfterThrowing(value="execution(* *.doSometing())" ,throwing="exception")
    public void afterThrowingMethod(JoinPoint joinPoint,Exception exception){
    String methodName = joinPoint.getSignature().getName();
    System.out.println("afterThrowingMethod logging....." + methodName+exception);
    }


    从动态代理到AOP

    动态代理的方法执行前后设置通知。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    try{

    //前置通知
    method.invoke(target, args);
    //返回通知,可以访问到方法的返回值
    }catch(Exception e){

    e.printStackTrace();
    //异常通知,可以访问到出现的异常
    }

    //后置通知,因为方法可能出现异常,所以访问不到方法的返回值。

    不能看出,AOP是基于动态代理来实现的。


    AspectJ的优先级

    同一个连接点上有多个切面时, 除非明确指定, 否则它们的优先级是不确定的。

    切面的优先级可以通过@Order 注解指定,值越小优先级越高。

    Alt text