zoukankan      html  css  js  c++  java
  • Spring AOP 详解

    Spring 有三大核心思想,其目的都是为了解耦。

    我们日常开发中总能不知不觉用到其中两种,分别是控制反转(Inversion of Control, IOC)和依赖注入(Dependency Injection, DI)。

    而面向切面编程(Aspect Oriented Programming, AOP)却时常被人所忽略,但它的作用却不可忽视。

    AOP 的实现的目标是保证开发者在不修改原代码的前提下,去为系统中的业务组件添加某种通用功能。

    在使用AOP时,我们能常常听到以下术语:Aspect、Pointcut、Advice、JoinPoint、Weaving。

    • Pointcut:切点,表示一组JoinPoint,它定义了Advice将要发生的地方,
    • JoinPoint:连接点,表示程序执行过程中能够插入切面的点,可以是方法的调用或异常的抛出。
    • Advice:增强,包括处理时机和处理内容。通俗的将就是什么时候该做什么事。
    • Aspect:切面,由同一类Pointcut和Advice组成。
    • Weaving:织入,就是通过动态代理,在目标对象中执行处理内容的过程。

    如果术语描述的不太明白,请允许我在介绍AOP之前讲一个故事:

    在很久很久以前,有一个国家叫M国,其国力昌盛,但皇帝大限将至,便将皇位传给太子。

    不久后,皇帝驾崩,太子正式登基。但太子念其国力昌盛,整日无所作为,朝事荒废。

    十几年后,边境事犯,皇帝欲选取文武兼备的人率军去平定叛乱,但此时朝廷人才寥落,只有一人能担此任。

    皇帝为了犒劳该将军,在其出征之前,将其亲子委以朝廷命官,辅佐在自己身边。

    经过数年苦战,该将军荣耀而归,平定叛乱。皇帝高兴万分,赏其千金,封为万户侯。

    经此事后,皇帝重整朝廷,不久后,国家便重回巅峰。

    听完如上故事,请思考并类比术语的含义:

    Pointcut:文武兼备的人

    JoinPoint:上文中的将军

    Advice:将军出征前皇帝留任其亲子在身边、将军回来后皇帝对其进行赏赐。

    Aspect:文武兼备的人出征这个事件

    Weaving:对文武兼备的人出征前后干的事的过程。

    本故事只能使你理解它这些术语的含义,并不能描述AOP的目标,也就是解耦,相信解耦大家都清除,这里就不说了。

    接下来开始详细介绍AOP的概念以及使用,首先介绍Pointcut,下面是一段官方介绍:

    @Pointcut("execution(* transfer(..))")// the pointcut expression
    private void anyOldTransfer() {}// the pointcut signature

    上面的例子定义了一个名为'anyOldTransfer'的切入点,它将匹配任何名为'transfer'的方法的执行。

    Spring AOP支持以下AspectJ切入点指示符(PCD),用于切入点表达式中:

    切入点指示符 描述
    execution 限制匹配连接点的方法
    within 限制匹配连接点的包或者类
    @within 限制匹配连接点的类带有指定注解
    arg 限制匹配连接点的参数类型
    @args 限制匹配连接点的参数带有指定注解
    target 限制匹配连接点目标对象的类型
    @target 与@within的功能类似,但注解的保留策略须为RUNTIME
    this 限制匹配连接点的AOP代理类的类型
    @annotation 限制匹配连接点的方法带有指定的注解
    bean 限制连接点是指定的Bean,或一组命名Bean(使用通配符时)

    看完上述切点表达式不理解的话很正常,下面给出详细介绍:

    1、@Pointcut是创建切入点,切入点不用写代码,返回类型为void。

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                throws-pattern?)
    execution(方法修饰符(可选)  返回类型  类路径 方法名  参数  异常模式(可选))
    • 修饰符匹配(modifiers-pattern?)
    • 返回值匹配(ret-type-pattern)
    • 类路径匹配(declaring-type-pattern?)
    • 方法名匹配(name-pattern)
    • 参数匹配(param-pattern),可以指定具体参数类型,多个参数用","隔开。
    • 异常类型匹配(throws-pattern?)
    • 以上匹配中后面跟着"?"的表示是可选项。

    多个匹配之间我们可以使用链接符 &&||来表示 “且”、“或”、“非”的关系。

    但是在使用 XML 文件配置时,这些符号有特殊的含义,所以我们使用 “and”、“or”、“not”来表示。

    示例:

    // 匹配AccountService的任意方法
    execution(* com.xyz.service.AccountService.*(..))
    // 匹配服务包下的任意方法
    execution(* com.xyz.service.*.*(..))
    //匹配服务包或其子包下的任意方法
    execution(* com.xyz.service..*.*(..))
    // 匹配位于service包下任意类型
    within(com.xyz.service.*)
    // 匹配代理实现AccountSercice接口的任意类
    this(com.xyz.service.AccountService)
    // 匹配目标对象实现AccountService接口的任意类
    target(com.xyz.service.AccountService)

    2、Advice是增强通知,其有五种通知类型,分别如下:

    • @Before,在目标方法调用前执行
    • @After,在目标方法调用后执行
    • @AfterReturning,在目标方法返回后调用
    • @AfterThrowing,在目标方法抛出异常后调用
    • @Around,将目标方法封装起来

     首先介绍一下@AfterReturning,官网带参数示例介绍如下:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.AfterReturning;
    
    @Aspect
    public class AfterReturningExample {
    
        @AfterReturning(
            pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
            returning="retVal")
        public void doAccessCheck(Object retVal) {
            // ...
        }
    
    }

    returning属性中使用的名称必须与通知方法中的参数名称相对应。

    当方法执行返回时,该返回值将作为相应的参数值传递到通知方法。

    另外returning子句也限制了只能匹配返回指定类型的值,如果是Object类型,将可以匹配任何返回值。

     其次是@AfterThrowing的带参数示例:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.AfterThrowing;
    
    @Aspect
    public class AfterThrowingExample {
    
        @AfterThrowing(
            pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
            throwing="ex")
        public void doRecoveryActions(DataAccessException ex) {
            // ...
        }
    
    }

    throwing属性中使用的名称必须和通知方法中的参数名称相对应。

    当方法抛出异常时,该异常将作为相应的参数传递给通知方法。

    另外throwing子句也限制了只能匹配到指定异常类型。

    接下来介绍@Around示例:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    @Aspect
    public class AroundExample {
    
        @Around("com.xyz.myapp.SystemArchitecture.businessService()")
        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
            // start stopwatch
            Object retVal = pjp.proceed();
            // stop stopwatch
            return retVal;
        }
    
    }
    @Around("execution(List<Account> find*(..)) && " +
            "com.xyz.myapp.SystemArchitecture.inDataAccessLayer() && " +
            "args(accountHolderNamePattern)")
    public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
            String accountHolderNamePattern) throws Throwable {
        String newPattern = preProcess(accountHolderNamePattern);
        return pjp.proceed(new Object[] {newPattern});
    }

    @Around可以在方法执行之前和之后工作,并能决定该方法何时执行以及如何调用。

    该通知方法的第一个参数必须为ProceedingJoinPoint类型。

    在通知方法中,调用ProceedingJoinPoint的proceed方法来执行匹配的方法。

    proceed方法也可能被调用解析Object[]数组,它被用于匹配方法执行的参数。

    通知方法返回的值将是匹配方法调用者看到的返回值。

    最后,任何通知方法都可以将JoinPoint作为它的第一个参数,@Around除外,它第一个参数必须是ProceedingJoinPoint。

    JoinPoint可以提供许多有用的参数,比如getArgs、getThis、getTarget等等。

    目前我们已经看到了如何绑定返回值或异常值。如果要使参数用于通知接收,可以使用绑定形式的args。

    如果在args表达式中使用参数名替代类型名称,则在调用通知方法时,将相应参数的值作为参数值传递就可以了。

    示例如下:

    @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
    public void validateAccount(Account account) {
        // ...
    }

    arg(account,..)切入点表达式有两个作用,第一它将匹配限制为方法需要有至少一个参数。

    第二它限制了参数的类型为Account,并且使该对象可以用于通知方法。

    另外它也可以用如下方式表示:

    @Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
    private void accountDataAccessOperation(Account account) {}
    
    @Before("accountDataAccessOperation(account)")
    public void validateAccount(Account account) {
        // ...
    }

    代理对象、目标对象、和注解等都可以像如下示例使用:

    首先定义一个注解:

    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    public @interface Auditable {
        AuditCode value();
    }

    然后是与@Auditable相匹配的通知:

    @Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
    public void audit(Auditable auditable) {
        AuditCode code = auditable.value();
        // ...
    }

    通知参数和泛型:(不适用于集合泛型)

    public interface Sample<T> {
        void sampleGenericMethod(T param);
        void sampleGenericCollectionMethod(Collection<T> param);
    }
    @Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
    public void beforeSampleMethod(MyType param) {
        // Advice implementation
    }

    通知方法中的参数绑定依赖于切入点表达式中使用的名称。

    因为参数名称无法通过java反射活动,因此Sping AOP使用如下策略确定参数名称:

    @Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
            argNames="bean,auditable")
    public void audit(Object bean, Auditable auditable) {
        AuditCode code = auditable.value();
        // ... use code and bean
    }

    如果通知方法第一个参数是JoinPoint,ProceedingJoinPoint等,则可以省去参数。

    @Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
    public void audit(JoinPoint jp) {
        // ... use jp
    }

    JoinPoint、ProceedingJoinPoint类型特别方便,可以通过它的getArgs方法获取参数。

    当多个通知都希望在同一连接点运行时,除非另外指定,否则执行顺序是不确定的。

    可以通过org.springframework.core.Ordered的Order注解来确定顺序,值越低,优先级越高。

    3、引入(Introductions)

    这个注解感觉用起来作用不大,就不介绍了。 

  • 相关阅读:
    Ext.FormPanel-----FieldSet的用法
    DAO层,Service层,Controller层、View层
    PageProxy分页的实现
    Layout布局(补充)
    Ext--Layout(布局)
    DirectEvents用法
    Linq的使用
    字符串注入攻击
    winform(C#)里弹出“确定”“取消”对话框
    C#的数组
  • 原文地址:https://www.cnblogs.com/M-Anonymous/p/14736726.html
Copyright © 2011-2022 走看看