Mittwoch, 20. Juli 2011

Merkzettel: @NotNull Check mit aspectj

Möchte man nicht mit assert arbeiten um Parameter auf null zu prüfen, bietet sich aspectj an um einen komfortablen NotNull-Check zu implementieren. Vor allem wenn aspectj schon im Projekt vorhanden ist.

Anforderung: Alle Parameter in Methoden und Konstruktoren, die die Annotation @NotNull besitzen sollen geprüft werden. Wenn ein so markierter Parameter null ist, soll eine IllegalArgumentException ausgelöst werden.

Hier die Beispielimplementierung:

Als erstes die Annotation @NotNull für die zu prüfenden Parameter:

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {

  String parameterName() default "";
}

Die Annotation @NotNull besitzt das Attribut parameterName. Hier kann der Name des Parameters übergeben werden. Dieser wird für die Fehlermeldung verwendet.



Dann benötigen wir einen Aspect, der alle Methoden und Konstruktoren mit der Annotation @NotNull "trifft".

Pointcut für alle Methoden mit der Annotation @NotNull: execution(* *(..,@NotNull (*),..))

Pointcut für alle Konstruktoren mit der Annotation @NotNull: execution(new(..,@NotNull (*),..))

@Aspect
public class CheckArgumentsAspect {

  /**
   * Der Pointcut trifft auf alle Methoden in allen Packages zu, die
   * mindestens einen Parameter mit der Annotation {@link NotNull} besitzen.
   */
  @Around("execution(* *(..,@NotNull (*),..))")
  public Object checkArgsForMethod(ProceedingJoinPoint joinPoint)
      throws Throwable {

    if (joinPoint.getSignature() instanceof MethodSignature) {

      MethodSignature methodSignature = (MethodSignature) joinPoint
          .getSignature();
      Method method = methodSignature.getMethod();
      Class<?>[] parameterTypes = method.getParameterTypes();
      Annotation[][] parameterAnnotationArray = method
          .getParameterAnnotations();

      System.out.println("Prüfe Parameter der Methode '"
          + methodSignature.toLongString() + "' mit den Parameterwerten '"
          + joinPoint.getArgs() + "'.");

      // Prüft alle Parameter
      checkParameter(joinPoint.getArgs(), parameterTypes,
          parameterAnnotationArray, methodSignature.toLongString());
    }

    return joinPoint.proceed();
  }

  /**
   * Der Pointcut trifft auf alle Konstruktoren zu, die mindestens einen
   * Parameter mit der Annotation {@link NotNull} besitzen.
   */
  @Around("execution(new(..,@NotNull (*),..))")
  public Object checkArgsForConstructor(ProceedingJoinPoint joinPoint)
      throws Throwable {

    if (joinPoint.getSignature() instanceof ConstructorSignature) {

      ConstructorSignature constructorSignature = 
          (ConstructorSignature) joinPoint.getSignature();
      Constructor<?> constructor = constructorSignature.getConstructor();
      Class<?>[] parameterTypes = constructor.getParameterTypes();
      Annotation[][] parameterAnnotationArray = constructor
          .getParameterAnnotations();

      System.out.println("Prüfe Parameter des Konstruktors '"
          + constructorSignature.toLongString()
          + "' mit den Parameterwerten '" + joinPoint.getArgs() + "'.");

      // Prüft alle Parameter
      checkParameter(joinPoint.getArgs(), parameterTypes,
          parameterAnnotationArray, constructorSignature.toLongString());
    }

    return joinPoint.proceed();
  }

  /**
   * Prüft alle Parameter.
   * 
   * @param parameter
   *          Alle Parameter.
   * @param parameterTypes
   *          Die Typen zu den Parametern.
   * @param parameterAnnotationArray
   *          Die Annotations zu den Parametern.
   * @param signature
   *          Die Signatur der Methode oder des Konstruktors.
   */
  private void checkParameter(Object[] parameter, Class<?>[] parameterTypes,
      Annotation[][] parameterAnnotationArray, String signature) {

    for (int i = 0; i < parameterTypes.length; i++) {

      Annotation[] parameterAnnotations = parameterAnnotationArray[i];

      for (Annotation annotation : parameterAnnotations) {

        if (annotation instanceof NotNull) {

          // Hier wird das Argument auf null geprüft
          checkNotNull(parameter[i], parameterTypes[i], i,
              ((NotNull) annotation).parameterName(), signature);
        }
      }
    }
  }

  /**
   * Prüft den Parameter auf null.
   * 
   * @param parameter
   *          Der Parameter.
   * @param parameterType
   *          Der Typ des Parameters.
   * @param parameterIndex
   *          Der Index des Parameters.
   * @param parameterName
   *          Der Name des Parameters.
   * @param signature
   *          Die Signatur der Methode oder des Konstruktors.
   */
  private void checkNotNull(Object parameter, Class<?> parameterType,
      int parameterIndex, String parameterName, String signature) {

    // Hier wird das Argument auf null geprüft
    if (parameter == null) {

      if (StringUtils.isBlank(parameterName)) {
        parameterName = "-";
      }

      String longMsg = MessageFormat.format(
          "Fehler: Der {0}. Parameter (Name: {1}, Typ: {2}) von {3} "
              + "ist null.", parameterIndex + 1, parameterName,
          parameterType.getName(), signature);
      throw new IllegalArgumentException(longMsg);
    }
  }
}


Eine Klasse mit zu prüfenden Parametern könnte zum Beispiel so aussehen:

public class TestClass {

  public TestClass() {
    super();
  }

  public TestClass(@NotNull(parameterName = "kNotNull") Integer kNotNull) {
    super();
    System.out.println("    kNotNull: " + kNotNull);
  }

  public void methodWithOneNotNullArg(
      @NotNull(parameterName = "aNotNull") Integer aNotNull) {
    System.out.println("    aNotNull: " + aNotNull);
  }

  public void methodWithOneNotNullArg(Long b,
      @NotNull(parameterName = "cNotNull") Integer cNotNull) {
    System.out.println("    b       : " + b);
    System.out.println("    cNotNull: " + cNotNull);
  }

  public void methodWithTwoNotNullArg(
      @NotNull(parameterName = "dNotNull") Integer dNotNull,
      @NotNull Integer eNotNull) {
    System.out.println("    dNotNull: " + dNotNull);
    System.out.println("    eNotNull: " + eNotNull);
  }

  public void methodWithoutNotNullArg(Long f) {
    System.out.println("    f       : " + f);
  }
}


Jetzt sollten alle Parameter mit der Annotation @NotNull geprüft werden. Hier noch der passende JUnit-Test dazu:

public class TestClassTest {

  private TestClass testClass = new TestClass();

  @Test
  public void testMethodWithOneNotNullArgInteger() {
    testClass.methodWithOneNotNullArg(1);
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerNull() {
    try {
      testClass.methodWithOneNotNullArg(null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerInteger() {
    testClass.methodWithOneNotNullArg(11l, 11);
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerIntegerNull1() {
    testClass.methodWithOneNotNullArg(null, 11);
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerIntegerNull2() {
    try {
      testClass.methodWithOneNotNullArg(11l, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerIntegerNullNull() {
    try {
      testClass.methodWithOneNotNullArg(null, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithTwoNotNullArg() {
    testClass.methodWithTwoNotNullArg(111, 111);
  }

  @Test
  public void testMethodWithTwoNotNullArgNull1() {
    try {
      testClass.methodWithTwoNotNullArg(null, 222);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithTwoNotNullArgNull2() {
    try {
      testClass.methodWithTwoNotNullArg(222, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithTwoNotNullArgNullNull() {
    try {
      testClass.methodWithTwoNotNullArg(null, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithoutNotNullArg() {
    testClass.methodWithoutNotNullArg(null);
  }

  @Test
  public void testMethodWithoutNotNullArgNull() {
    testClass.methodWithoutNotNullArg(null);
  }

  @Test
  public void testConstructorWithNotNullArg() {
    new TestClass(999);
  }

  @Test
  public void testConstructorWithNotNullArgNull() {
    try {
      new TestClass(null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }
}

Und jetzt oft @NotNull verwenden und weitere Annotations zum Prüfen von Argumenten erstellen!

Das Projekt befindet sich hier.

Keine Kommentare:

Kommentar veröffentlichen