最爱Java

书山有路勤为径,学海无涯苦作舟

《AspectJ Cookbook》读书笔记二十: 应用类和组件级方面

一.验证传递给方法的参数
        创建一个模块化参数检查逻辑的方面。声明一个切入点,用于捕获其中将检查参数的方法的执行。切入点应该把参数展示给相应的通知使得它可以执行检查。
        依赖于参数检查的结果,通知将继续执行方法,或者每当参数不合适时都重写方法。
        传统的参数验证和检查代码:
package com.aspectj;

import java.net.URL;
import java.net.MalformedURLException;

public class TraditionalMainApplication
{
   
private static final String COMMAND_LINE_USAGE = "MyAppliction usage :\n\n"
         
+ "\tjava MainApplication <url>";

   
public static void main(String[] args)
   
{
      
if (args.length == 1)
      
{
         
try
         
{
            
// Assuming that the first argument supplied is a url
            
// Concentrating on the business logic, not validation which
            
// is handled by the aspect.
            URL url = new URL(args[0]);

            System.out.println(
"Application Started, doing stuff with " + url);
         }

         
catch (MalformedURLException mue)
         
{
            System.err.println(COMMAND_LINE_USAGE);
            System.err.println(
"Please enter a valid URL for <url>");
         }

      }

      
else
      
{
         System.err.println(COMMAND_LINE_USAGE);
      }

   }

}

    通过方面的应用,可以改成如下:
package com.aspectj;

import java.net.URL;
import java.net.MalformedURLException;

public aspect VerifyMethodArgsAspect
{
   
private static final String COMMAND_LINE_USAGE = "MyAppliction usage :\n\n" +
   
"\tjava MainApplication <url>";
   
   
public pointcut captureMain(String[] arguments) : 
      execution(
void MainApplication.main(String[])) && args(arguments);

   
public pointcut createURLCalledinMainMethod() : call(java.net.URL.new(..)) && withincode(public void MainApplication.main(String[]));
   
   
void around(String[] arguments) : captureMain(arguments)
   
{
      
if (arguments.length == 1)
      
{
          
// Test that the host and port are valid
         try
         
{
            URL url 
= new URL(arguments[0]);
            proceed(arguments);
         }

         
catch(MalformedURLException mfe)
         
{
            System.err.println(COMMAND_LINE_USAGE);
            System.err.println(
"Please enter a valid URL for <url>");
         }

      }

      
else
      
{
         System.err.println(COMMAND_LINE_USAGE);
      }

   }

   
   
// If necessary soften the exception that would normally have been raised
   
// if the url parameter was badly formed, but only in the validated main method
   declare soft : MalformedURLException : createURLCalledinMainMethod();

}


package com.aspectj;

import java.net.URL;

public class MainApplication
{

   
public static void main(String[] args)
   
{
      
// Assuming that the first argument supplied is a url
      
// Concentrating on the business logic, not validation which
      
// is handled by the aspect.
      URL url = new URL(args[0]);
      
      System.out.println(
"Application Started, doing stuff with " + url);
   }

}

二. 重写在构造函数调用上实例化的类
public static void traditionalObjectOrentedImplementationSelection() {
    MyInterface myObject 
= new MyClass() //Specifies the MyClass implementation of the MyInterface interface
    System.out.println(myObject);
    myObject.foo();
}

    以上代码为了改变MyInterface的实现,必须把代码改成:
        MyIntrface myObject = new AnotherClass();
    通过使用AspectJ的call(Signature)切入点和around()通知,可以使得事情更简单,更整洁。如:
package com.aspectj;

public aspect ControlClassSelectionAspect 
{
   
public pointcut myClassConstructor() : call(MyClass.new());
   
   Object around() : myClassConstructor()
   
{
      
return new AnotherClass();
   }

   
   
// Runtime selection variation
   /*
   Object around() : myClassConstructor() && 
                     if (System.getProperty("select_class").equals("AnotherClass"))
   {
      return new AnotherClass();
   }
   
*/

}
        ControlClassSelectionAspect声明了myClassConstructor()切入点,它会截获调用,以实例化MyClass对象。相应的around()通知然后会返回重写AnotherClass类的一个新实例。
        进一步,可以检查用户定义的运行时参数:
        Object aound():myClassConstructor()&&if (System.getProperty("select_class")).equals("AnotherClass") {
            return new AnotherClass();
        }

三.添加持久性到类中
        可以把这个抽象方面扩展成一些特殊化的子方面,他们将会应用程序内的每个持久对象集合实现一个合适的持久性机制。
package com.aspectj;

public abstract aspect PersistenceAspect
{
   
public interface ObjectStore
   
{
      
public void persist();
      
public void restore();
   }

   
   
protected abstract pointcut restoreStorage(ObjectStore store);
   
   after(ObjectStore store) : restoreStorage(store)
   
{
      store.restore();
   }

   
   
protected abstract pointcut persistStorage(ObjectStore store);
   
   after(ObjectStore store) : persistStorage(store)
   
{  
      store.persist();
   }

}


        PersistenceAspect抽象方面把ObjfctStore角色定义为接口,可以将其应用与任何类,来管理对象集合的持久性。restoreStorage(ObjectStore)和persistStorage(ObjectStore)抽象切入点通过特殊化的子方面来实现,用于触发对应的after()通知块,它将恢复或保持指定的ObjectStore.
        ObjectStore接口中指定的restore()和persist()方法是依据特地ingde持久性策略实现的,该策略用于应用程序内的对象集合,使得可以基于每个ObjectStore来改变持久性策略。
package com.aspectj;

import java.io.*;

public aspect EmployeePersistenceAspect extends PersistenceAspect
{
   declare parents : EmployeeCollection 
implements ObjectStore;

   
protected pointcut restoreStorage(ObjectStore store) : 
      execution(EmployeeCollection.
new(..)) && 
      target(store);
   
   
protected pointcut persistStorage(ObjectStore store) : 
      call(
* java.util.List.add(..)) && 
      target(EmployeeCollection) 
&&
      target(store);

   declare parents : Employee 
extends Serializable;
   
   
private File EmployeeCollection.employeesFile = new File("employees.ser");
   
   
public void EmployeeCollection.persist()
   
{
      
try
      
{
         ObjectOutput out 
= new ObjectOutputStream(new FileOutputStream(this.employeesFile));
         Object[] objectsToStore 
= this.toArray();
         out.writeObject(objectsToStore);
         out.flush();
         out.close();
      }

      
catch (Exception e)
      
{
         System.err.println(
"Couldn't store employees to " + this.employeesFile);
      }

   }

   
   
public void EmployeeCollection.restore()
   
{
      
// Check that the serialized accounts file exists
      if (this.employeesFile.exists() && this.employeesFile.canRead())
      
{
         
try
         
{
            ObjectInput input 
= new ObjectInputStream(new FileInputStream(this.employeesFile));
            
            Object[] objectsToRestore 
= (Object[]) input.readObject();
            
for(int x = 0; x < objectsToRestore.length; x++)
            
{
               
this.add(objectsToRestore[x]);
            }

            
            input.close();
         }

         
catch (Exception e)
         
{
            System.err.println(
"Couldn't restore employees due to a corrupt " + this.employeesFile + " file");
            e.printStackTrace();
         }

      }

   }

}


        EmployeePersistenceAspect将ObjectStore接口应用于EmployeeCollection类。然后实现restoreStorage(ObjectStore)方面,以捕获何时构造EmployeeCollection,并使用target(TypePattern || Identifier)切入点将EmployeeCollection展示为要存储的ObjectStore.
        实现persistStorage(ObjectStore)切入点,用以捕获无论何时更改EmployeeCollection,并在此时保持ObjectStore的内容。EmployeeCollection是ArrayList的特殊化;为了避免直接织入到Java标准库中,使用call(Signature)切入点来捕获何时在List上调用add(...)方法。
        不过,call(Signature)切入点定义本质上过于普通,因此必须将通过persistStorage(ObjectStore)切入点捕获的连接点限制于只捕获何时在EmployeeCollection上调用add(...)。为了应用这种限制,第一个target(TypePattern || Identifier)切入点使用TypePattern来指定你只对其目标是EmployeeCollection类的连接点感兴趣。第二个target(TypePattern || Identifier)切入点使用一个标识符将当前ObjectStore传递给通知块,作为persistStorage(ObjectStore)切入点的单一参数。
        最后,将直观的对象串行化持久性策略应用于EmployeeCollction。EmployeeCollection中的每个对象都是Employee类的一个实例,并且扩展这个类以实现Serializable接口,使得可对它应用标准的Java对象串行化技术。串行化的Employee对象将存储在一个文件中;因此,将以employeesFile属性的形式把这个文件信息添加到EmployeeCollection类中。
        为了完成图形,将把persist()和restore()方法实现添加到EmployeeCollection类中,使得它可以满足ObjectStore接口所需的行为。这些方法会执行Employee对象的串行化和恢复,这是通过employeesFile属性指定的文件来进行的。
        以下显示了抽象的PersistenceAspect替代实现的一部分,它只会在关闭应用程序关闭时保持其对应的ObjectStore.
package com.aspectj;

import java.io.*;

public privileged aspect AccountPersistenceAspect extends PersistenceAspect
{
   declare parents : MainApplication 
implements ObjectStore, Runnable;

   
protected pointcut restoreStorage(ObjectStore store) : 
      execution(MainApplication.
new(..)) && 
      target(store);
   
   
// Selects all join points where it is necessary to persist the store
   protected pointcut persistStorage(ObjectStore store) : 
      execution(
public void MainApplication.run()) && 
      
this(store);
   
   declare parents : Account 
extends Serializable;

   
private File MainApplication.accountsFile = new File("accounts.ser");
   
   after(MainApplication mainApplication) : 
      restoreStorage(ObjectStore) 
&& 
      target(mainApplication)
   
{
      
// Register a shutdown hook
      Thread shutdownThread = new Thread(mainApplication);
      Runtime.getRuntime().addShutdownHook(shutdownThread);
   }

   
   
public void MainApplication.run()
   
{
      
// Do nothing, merely provides the trigger that the shutdown hook has been
      
// executed so as to persist the store on shutdown.
   }

   
   
public void MainApplication.persist()
   
{
      
try
      
{
         ObjectOutput out 
= new ObjectOutputStream(
               
new FileOutputStream(this.accountsFile));
         
         Object[] objectsToStore 
= this.accounts.toArray();
         out.writeObject(objectsToStore);
         out.flush();
         out.close();
      }

      
catch (Exception e)
      
{
         System.err.println(
"Couldn't store accounts to " + this.accountsFile);
      }

   }

   
   
public void MainApplication.restore()
   
{
      
// Check that the serialized accounts file exists
      if (this.accountsFile.exists() && this.accountsFile.canRead())
      
{
         
try
         
{
            ObjectInput input 
= new ObjectInputStream(
                  
new FileInputStream(this.accountsFile));
            
            Object[] objectsToRestore 
= (Object[]) input.readObject();
            
for(int x = 0; x < objectsToRestore.length; x++)
            
{
               
this.accounts.add(objectsToRestore[x]);
            }

            
            input.close();
         }

         
catch (Exception e)
         
{
            System.err.println(
"Couldn't restore accounts due to a corrupt " + this.accountsFile + " file");
            e.printStackTrace();
         }

      }

   }

}


        persistStore(ObjectStore)切入点修改成捕获Runnable的执行,它在MyApplication类上强制执行public void run()方法。然后,AccountPersistenceAspect把必要的run()方法实现添加到MainApplication类中,以满足Runnable接口的需要,但是,这只会提供一个标记,而不需要任何实现。
        增加Runnable接口和MainApplication类上的run()存根方法意味着:可以利用JVM把MainApplication注册为关闭挂钩,使得在整个应用程序正常完成时将调用MainApplication.run()方法。在恢复ObjectStore(它是一个MainApplication类)时,通过执行around(MainApplication)通知块,来完成把MainApplication类注册为关闭挂钩的任务。
        通过把MainApplication用作关闭挂钩,当触发关闭挂钩时,persistStoreage(ObjectStore)切入点将触发MainApplication对象的保持操作。因此,一旦干净利索地关闭应用程序,就会保持MainApplication中存储的Account类的集合。

四. 应用模拟组件支持单元测试

        创建组件所依赖的外部组件的模拟实现。创建一个方面,应用模拟组件实现来代替真实的组件。单元测试完成时,使用单独的AspectJ构建配置文件装换出测试文件,使得可以再次使用真实的实现。

        以下为一种典型情况,MyComponent是要测试的组件,并且它具有与ThirdPartyComponentInterface的外部实现的依赖性。ThirdPartyComponentInterface的真实实现是通过调用工厂方法ThirdPartyFactory.getThirdPartyComponent()来获得的。

package com.aspectj;

import com.thirdparty.ThirdPartyComponentFactory;
import com.thirdparty.ThirdPartyComponentInterface;

public class MyComponent implements MyComponentInterface
{
    
private ThirdPartyComponentInterface thirdPartyComponent;
    
    
public MyComponent()
    
{
        
this.thirdPartyComponent = ThirdPartyComponentFactory.getThirdPartyComponent();
        System.out.println(
"Component found " + thirdPartyComponent);
    }

    
    
public void foo()
    
{
        System.out.println(
"Inside MyComponent.foo()");
        
this.thirdPartyComponent.bar();
    }

}


        为了孤立地对MyComponent运行单元测试,将需要通过在测试中包括真实的外部组件,重写ThirdPartyComponent实现,从而不会混淆测试结果。一种策略是:手动应用重写真实组件实现的模拟组件。如:

package test.com.aspectj;

import com.aspectj.ThirdPartyComponentInterface;

public class MockThirdPartyComponent implements ThirdPartyComponentInterface {

    
/* (non-Javadoc)
     * @see com.thirdparty.ThirdPartyComponentInterface#bar()
     
*/

    
public void bar() 
    
{
        System.out.println(
"Inside MockThirdPartyComponent.bar()");

    }

}


        以上方法如果用在组件较多的接口,可能难以管理。使用面向方面的替代方法,可以创建一个方面,用于截获ThirdPartyComponent的创建,并用模拟对象实现重写返回的对象。

package test.com.aspectj;

import com.aspectj.*;

public aspect MockThirdPartyComponentAspect 
{
    
public pointcut catchThirdPartyConstructor() : 
       call(ThirdPartyComponentInterface ThirdPartyComponentFactory.
             getThirdPartyComponent());
    
    Object around() : catchThirdPartyConstructor()
    
{
        
return new MockThirdPartyComponent();
    }

}


        可以通过创建两个不同的AspectJ构建配置文件,来区分真实实现和模拟实现。


posted on 2008-08-29 11:18 Brian 阅读(328) 评论(0)  编辑  收藏 所属分类: 《AspectJ Cookbook》读书笔记


只有注册用户登录后才能发表评论。


网站导航:
 

公告


导航

<2008年8月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

统计

常用链接

留言簿(4)

随笔分类

随笔档案

收藏夹

搜索

最新评论

阅读排行榜

评论排行榜