免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 946 | 回复: 0
打印 上一主题 下一主题

AOP2 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-06-19 16:20 |只看该作者 |倒序浏览
6.2.4. Declaring advice
Advice is associated with a pointcut expression, and runs before, after, or
           around method executions matched by the pointcut. The pointcut expression may be
           either a simple reference to a named pointcut, or a pointcut expression declared
           in place.
6.2.4.1. Before advice
Before advice is declared in an aspect using the @Before annotation:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
  @Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doAccessCheck() {
    // ...
  }
}
If using an in-place pointcut expression we could rewrite the above example as:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
  @Before("execution(* com.xyz.myapp.dao.*.*(..))")
  public void doAccessCheck() {
    // ...
  }
}6.2.4.2. After returning advice
After returning advice runs when a matched method execution returns
           normally. It is declared using the @AfterReturning annotation:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
  @AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doAccessCheck() {
    // ...
  }
}Note: it is of course possible to have multiple advice declarations, and other
        members as well, all inside the same aspect. We're just showing a single advice declaration
        in these examples to focus on the issue under discussion at the time.
       

        Sometimes you need access in the advice body to the actual value that was returned. You
        can use the form of @AfterReturning that binds the return
        value for this:
       
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) {
    // ...
  }
  
}
The name used in the returning attribute must correspond to
        the name of a parameter in the advice method. When a method execution returns, the
        return value will be passed to the advice method as the corresponding argument value.
        A returning clause also restricts matching to only those method
        executions that return a value of the specified type (Object
        in this case, which will match any return value).
       
Please note that it is not possible to
                   return a totally different reference when using after-returning advice.
6.2.4.3. After throwing advice
After throwing advice runs when a matched method execution exits by throwing
        an exception. It is declared using the @AfterThrowing annotation:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
  @AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doRecoveryActions() {
    // ...
  }
}
Often you want the advice to run only when exceptions of a given type are
        thrown, and you also often need access to the thrown exception in the advice
        body. Use the throwing attribute to both restrict matching
        (if desired, use Throwable as the exception type
        otherwise) and bind the thrown exception to an advice parameter.
       
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) {
    // ...
  }
}
The name used in the throwing attribute must correspond to
        the name of a parameter in the advice method. When a method execution exits by
        throwing an exception, the
        exception will be passed to the advice method as the corresponding argument value.
        A throwing clause also restricts matching to only those method
        executions that throw an exception of the specified type
        (DataAccessException in this case).
       
6.2.4.4. After (finally) advice
After (finally) advice runs however a matched method execution exits.
        It is declared using the @After annotation. After
        advice must be prepared to handle both normal and exception return conditions.
        It is typically used for releasing resources, etc.
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
  @After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
  public void doReleaseLock() {
    // ...
  }
}6.2.4.5. Around advice
The final kind of advice is around advice. Around advice runs "around" a
        matched method execution. It has the opportunity to do work both before and
        after the method executes, and to determine when, how, and even if, the method
        actually gets to execute at all. Around advice is often used if you need to
        share state before and after a method execution in a thread-safe manner (starting
        and stopping a timer for example). Always use the least powerful form of
        advice that meets your requirements (i.e. don't use around advice if simple
        before advice would do).
       
Around advice is declared using the @Around annotation. The first parameter
        of the advice method must be of type ProceedingJoinPoint. Within the body of the
        advice, calling proceed() on the ProceedingJoinPoint causes the
        underlying method to execute. The proceed method may also be
        called passing in an Object[] - the values in the array will be used as the arguments
        to the method execution when it proceeds.
       
The behavior of proceed when called with an Object[] is a little different
        than the behavior of proceed for around advice compiled by the AspectJ compiler. For
        around advice written using the traditional AspectJ language, the number of arguments
        passed to proceed must match the number of arguments passed to the around advice (not the
        number of arguments taken by the underlying join point), and the value passed to proceed
        in a given argument position supplants the original value at the join point for the entity
        the value was bound to. (Don't worry if this doesn't make sense right now!) The
        approach taken by Spring is simpler and a better match to its proxy-based, execution only
        semantics. You only need to be aware of this difference if you compiling @AspectJ aspects
        written for Spring and using proceed with arguments with the AspectJ compiler and weaver.
        There is a way to write such aspects that is 100% compatible across both Spring AOP and
        AspectJ, and this is discussed in the following section on advice parameters.
       
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;
  }
}
        The value returned by the around advice will be the return value seen by the caller
        of the method. A simple caching aspect for example could return a value from a cache
        if it has one, and invoke proceed() if it does not. Note that proceed may be invoked
        once, many times, or not at all within the body of the around advice, all of these
        are quite legal.
       
6.2.4.6. Advice parameters
Spring 2.0 offers fully typed advice - meaning that you declare the parameters
                   you need in the advice signature (as we saw for the returning and throwing examples
                   above) rather than work with Object[] arrays all the time.
                   We'll see how to make argument        and other contextual values available to the advice
                   body in a moment. First let's take a look at how to write generic advice that can find
                   out about the method the advice is currently advising.
6.2.4.6.1. Access to the current JoinPoint
                       Any advice method may declare as its first parameter, a parameter of
                       type org.aspectj.lang.JoinPoint (please note that
                       around advice is required to declare a first parameter of type
                       ProceedingJoinPoint, which is a subclass of
                       JoinPoint. The JoinPoint
                       interface provides a number of useful methods such as getArgs()
                       (returns the method arguments), getThis() (returns the proxy
                       object), getTarget() (returns the target object),
                       getSignature() (returns a description of the method that is
                       being advised) and toString() (prints a useful description of
                       the method being advised). Please do consult the Javadocs for full details.
6.2.4.6.2. Passing parameters to advice
We've already seen how to bind the returned value or exception value (using
                       after returning and after throwing advice). To make argument values available to
                       the advice body, you can use the binding form of args. If
                       a parameter name is used in place of a type name in an args expression, then the
                       value of the corresponding argument will be passed as the parameter value when the
                       advice is invoked. An example should make this clearer. Suppose you want to advise
                       the execution of dao operations that take an Account object as the first parameter,
                       and you need access to the account in the advice body. You could write the following:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" +
        "args(account,..)")
public void validateAccount(Account account) {
  // ...
}
The args(account,..) part of the pointcut expression serves
                   two purposes: firstly, it restricts matching to only those method executions where
                   the method takes at least one parameter, and the argument passed to that parameter
                   is an instance of Account; secondly, it makes the actual
                   Account object available to the advice via the
                   account parameter.
Another way of writing this is to declare a pointcut that "provides" the
                   Account object value when it matches a join point, and then
                   just refer to the named pointcut from the advice. This would look as follows:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() &&" +
          "args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
  // ...
}
The interested reader is once more referred to the AspectJ programming guide for
            more details.
The proxy object (this), target object (target),
                   and annotations (@within, @target, @annotation, @args) can all
                   be bound in a similar fashion. The following example shows how you could match
                   the execution of methods annotated with an @Auditable
                   annotation, and extract the audit code.
                  
First the definition of the @Auditable annotation:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
        AuditCode value();
}
And then the advice that matches the execution of @Auditable methods:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && " +
        "@annotation(auditable)")
public void audit(Auditable auditable) {
  AuditCode code = auditable.value();
  // ...
}6.2.4.6.3. Determining argument names
The parameter binding in advice invocations relies on matching names used
                in pointcut expressions to declared parameter names in (advice and pointcut) method
                signatures. Parameter names are not available through Java
                reflection, so Spring AOP uses the following strategies to determine parameter
                names:

  • If the parameter names have been specified by the user explicitly,
                        then the specified parameter names are used: both the advice and the pointcut
                        annotations have an optional "argNames" attribute which can be used to specify
                        the argument names of the annotated method - these argument names are
                        available at runtime. For example:
    @Before(
       value="com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)",
       argNames="auditable")
    public void audit(Auditable auditable) {
      AuditCode code = auditable.value();
      // ...
    }If an @AspectJ aspect has been compiled by the AspectJ compiler (ajc) then
                        there is no need to add the argNames attribute as the compiler will do this
                        automatically.

  • Using the 'argNames' attribute is a little clumsy, so if the
                            'argNames' attribute has
                            not been specified, then Spring AOP will look at the debug information for the
                            class and try to determine the parameter names from the local variable table. This
                            information will be present as long as the classes have been compiled with debug
                            information ('-g:vars' at a minimum). The consequences of
                            compiling with this flag on are: (1) your code will be slightly easier to understand
                            (reverse engineer), (2) the class file sizes will be very slightly bigger (typically
                            inconsequential), (3) the optimization to remove unused local variables will not be
                            applied by your compiler. In other words, you should encounter no difficulties building
                            with this flag on.

  • If the code has been compiled without the necessary debug information, then Spring AOP
                            will attempt to deduce the pairing of binding variables to parameters (for example,
                            if only one variable is bound in the pointcut expression, and the advice method only
                            takes one parameter, the pairing is obvious!). If the binding of variables is ambiguous
                            given the available information, then an AmbiguousBindingException
                            will be thrown.

  • If all of the above strategies fail then an
                            IllegalArgumentException will be thrown.
    6.2.4.6.4. Proceeding with arguments
    We remarked earlier that we would describe how to write a proceed call
                with arguments that works consistently across Spring AOP
                and AspectJ. The solution is simply to ensure that the advice signature binds
                each of the method parameters in order. For example:
    @Around("execution(List 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});
    }        
    In many cases you will be doing this binding anyway (as in the example above).
    6.2.4.7. Advice ordering
    What happens when multiple pieces of advice all want to run at the same
                       join point? Spring AOP follows the same precedence rules as AspectJ to determine
                       the order of advice execution. The highest precedence advice runs first "on the way in"
                       (so given two pieces of before advice, the one with highest precedence runs
                       first). "On the way out" from a join point, the highest precedence advice runs last
                       (so given two pieces of after advice, the one with the highest precedence will run
                       second). For advice defined within the same aspect, precedence is established
                       by declaration order. Given the aspect:
    @Aspect
    public class AspectWithMultipleAdviceDeclarations {
      @Pointcut("execution(* foo(..))")
      public void fooExecution() {}
      
      @Before("fooExecution()")
      public void doBeforeOne() {
        // ...
      }
      
      @Before("fooExecution()")
      public void doBeforeTwo() {
        // ...
      }
      
      @AfterReturning("fooExecution()")
      public void doAfterOne() {
        // ...
      }
      @AfterReturning("fooExecution()")
      public void doAfterTwo() {
        // ...
      }
    }
    then for any execution of a method named foo, the doBeforeOne,
                doBeforeTwo, doAfterOne, and doAfterTwo
                advice methods all need to run. The precedence rules are such that the advice will execute
                in declaration order. In this case the execution trace would be:
    doBeforeOne
    doBeforeTwo
    foo
    doAfterOne
    doAfterTwo
    In other words doBeforeOne has precedence over doBeforeTwo, because it was defined
            before doBeforeTwo, and doAfterTwo has precedence over doAfterOne because it was defined
            after doAfterOne. It's easiest just to remember that advice runs in declaration order ;) -
            see the AspectJ Programming Guide for full details.
    When two pieces of advice defined in different aspects both need
            to run at the same join point, unless you specify otherwise the order of execution is
            undefined. You can control the order of execution by specifying precedence. This is done in
            the normal Spring way by either implementing the org.springframework.core.Ordered
            interface in the aspect class or annotating it with the Order annotation.
            Given two aspects, the aspect returning the lower value from
            Ordered.getValue() (or the annotation value) has the higher precedence.
    6.2.5. Introductions
    Introductions (known as inter-type declarations in AspectJ) enable an aspect
            to declare that advised objects implement a given interface, and to provide an implementation
            of that interface on behalf of those objects.
    An introduction is made using the @DeclareParents annotation. This annotation is
            used to declare that matching types have a new parent (hence the name). For example, given
            an interface UsageTracked, and an implementation of that interface
            DefaultUsageTracked, the following
            aspect declares that all implementors of service interfaces also implement the
            UsageTracked interface.
            (In order to expose statistics via JMX for example.)
           
    @Aspect
    public class UsageTracking {
      @DeclareParents(value="com.xzy.myapp.service.*+",
                      defaultImpl=DefaultUsageTracked.class)
      public static UsageTracked mixin;
      
      @Before("com.xyz.myapp.SystemArchitecture.businessService() &&" +
              "this(usageTracked)")
      public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
      }
      
    }
            The interface to be implemented is determined by the type of the annotated field. The
            value attribute of the @DeclareParents annotation
            is an AspectJ type pattern :- any bean of a matching type will implement the UsageTracked
            interface. Note that in the before advice of the above example, service
            beans can be directly used as implementations of the UsageTracked interface. If accessing
            a bean programmatically you would write the following:
           
    UsageTracked usageTracked = (UsageTracked) context.getBean("myService");6.2.6. Aspect instantiation models(This is an advanced topic, so if you are just starting out with AOP you can safely skip it
         until later.)

    By default there will be a single instance of each aspect within the application
            context. AspectJ calls this the singleton instantiation model. It is possible to define
            aspects with alternate lifecycles :- Spring supports AspectJ's perthis
            and pertarget instantiation models (percflow, percflowbelow,
            and pertypewithin are not currently supported).
           
    A "perthis" aspect is declared by specifying a perthis clause
            in the @Aspect annotation. Let's look at an example, and then
            we'll explain how it works.
           
    @Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
    public class MyAspect {
      private int someState;
           
      @Before(com.xyz.myapp.SystemArchitecture.businessService())
      public void recordServiceUsage() {
        // ...
      }
             
    }
    The effect of the 'perthis' clause is that one aspect
            instance will be created for each unique service object executing a business
            service (each unique object        bound to 'this' at join points matched by the pointcut expression).
            The aspect instance is created
            the first time that a method is invoked on the service object. The aspect goes out
            of scope when the service object goes out of scope. Before the aspect instance is created,
            none of the advice within it executes. As soon as the aspect instance has been created,
            the advice declared within it will execute at matched join points, but only when the service
            object is the one this aspect is associated with. See the AspectJ programming guide for
            more information on per-clauses.
           
    The 'pertarget' instantiation model works in exactly the same way as perthis, but
            creates one aspect instance for each unique target object at matched join points.
    6.2.7. Example
          Now that you have seen how all the constituent parts work, let's put them together
          to do something useful!
    The execution of business services can sometimes fail
          due to concurrency issues (for example, deadlock loser).
          If the operation is retried, it is quite likely to succeed next time round. For
          business services where it is appropriate to retry in such conditions (idempotent
          operations that don't need to go back to the user for conflict resolution), we'd like
          to transparently retry the operation to avoid the client seeing a
          PessimisticLockingFailureException. This is a requirement that clearly cuts across
          multiple services in the service layer, and hence is ideal for implementing via an aspect.
          
    Because we want to retry the operation, we will need to use around advice so
          that we can call proceed multiple times. Here's how the basic aspect implementation
          looks:
          
    @Aspect
    public class ConcurrentOperationExecutor implements Ordered {
       
       private static final int DEFAULT_MAX_RETRIES = 2;
       private int maxRetries = DEFAULT_MAX_RETRIES;
       private int order = 1;
       public void setMaxRetries(int maxRetries) {
          this.maxRetries = maxRetries;
       }
       
       public int getOrder() {
          return this.order;
       }
       
       public void setOrder(int order) {
          this.order = order;
       }
       
       @Around("com.xyz.myapp.SystemArchitecture.businessService()")
       public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
          int numAttempts = 0;
          PessimisticLockingFailureException lockFailureException;
          do {
             numAttempts++;
             try {
                return pjp.proceed();
             }
             catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
             }
          }
          while(numAttempts
    Note that the aspect implements the Ordered interface so we can set the precedence
          of the aspect higher than the transaction advice (we want a fresh transaction
          each time we retry). The maxRetries and orderdoConcurrentOperation around advice. Notice that for the moment
          we're applying the retry logic to all businessService()s. We
          try to proceed, and if we fail with an PessimisticLockingFailureException
          properties will both be configured by Spring. The main action happens in the
          
          we simply try again unless we have exhausted all of our retry attempts.
          
    The corresponding Spring configuration is:
         
          
    To refine the aspect so that it only retries idempotent operations, we might
           define an Idempotent annotation:
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Idempotent {
      // marker annotation
    }
    and use the annotation to annotate the implementation of service operations.
           The change to the aspect to only retry idempotent operations simply involves
           refining the pointcut expression so that only @Idempotent operations match:
    @Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
            "@annotation(com.xyz.myapp.service.Idempotent)")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
      ...       
    }
                   
                   
                   

    本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/24141/showart_324459.html
  • 您需要登录后才可以回帖 登录 | 注册

    本版积分规则 发表回复

      

    北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
    未成年举报专区
    中国互联网协会会员  联系我们:huangweiwei@itpub.net
    感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

    清除 Cookies - ChinaUnix - Archiver - WAP - TOP