如何使用AspectJ有条件地记录不同的数据?

时间:2016-09-16 12:28:18

标签: java logging aspectj

我对AspectJ和AOP很新。我知道AspectJ有许多Annotations(After,AfterReturning等)在调用方法之前,调用方法之后,返回之后,抛出异常等时执行代码。

我想将它用于日志记录,这是一个非常典型的用例。我一直在看这篇文章,我认为这是我需要的大部分内容。它使用AspectJ以及" jcambi方面"执行记录。

但我想做类似以下的事情:

public void login(User user) {
  String userType = user.getType();

  if (!user.isActive()) {
    // point cut 1 -- log inactive user
  } else if (!user.isPasswordValid()) {
    // point cut 2 -- log wrong password  
  } else {
      // point cut 3 -- log successful login
  }  
}

我们有一个既定的日志格式。类似的东西:

<actor>|<action_code>|<error_code>|<extra_info>

所有Actor类型,操作和错误代码都包含在枚举中。

有没有办法告诉AspectJ:

记录&#39; ifs&#39;和 记录不同的信息,取决于发生了什么?例如,在切入点1中记录下列之一:

admin|login|001|Admin user inactive
user|login|001|Normal user inactive

...并在第2点中记录以下其中一项:

admin|login|002|Invalid Admin password
user|login|002|Invalid normal user password

...并在第3点中记录下列其中一项:

admin|login|000|Successful Admin login
user|login|000|Successful Normal user login

有些东西告诉我这是不可能的。或者至少不容易。但我不确定它是否值得尝试。所以我被撕裂了。一方面,我希望&#34;消毒&#34;我的所有日​​志记录的代码。另一方面,我不确定实施这项工作是否会花费太多。

有什么想法吗?

***************************************编辑******* *********

谢谢你们的回答!我现在意识到两件事:1。我在我之前做了很多工作。 2.我认为我过分强调&#34;登录&#34;示例

登录只是一个小用例。我的任务是在很多很多类中的一堆方法中添加无处不在的 ...基本上我在应用程序的任何地方看到LOG.debug()或LOG.info(),用Aspect日志替换它。这也意味着,尽管我喜欢,但我不能重构所有代码以使我的生活更轻松。我喜欢使用例外登录,但它超出了我的任务范围:添加日志记录。

当然,在每种方法中,业务逻辑都是不同的,因此,日志记录也是如此。所以我的问题变成了:做这个的最佳做法是什么?我的意思是,每个方法都有自己的逻辑,它的ifs ......并且会有条件地记录不同的东西。因此,我是否继续为这些用例中的每一个创建一个方面类,并且基本上具有相同的&#34; ifs&#34;那还有?

一个例子(没有登录!):一种导入数据的方法。

public void import(String type) {
      if (type.equals("people")) {
        try {
          int result = importPeople();
          if (result > 0) {
            // do some stuff
            LOG.info("ok");
          } else {
            // do some stuff
            LOG.info("problem");
          }
        } catch (Exception e) {
          // do some stuff
          LOG.debug("exception ...");
        }
      } else if (type.equals("places")) {
        try {
          int result = importPlaces();
          if (result > 0) {
            // do some stuff
            LOG.info("ok");
          } else {
            // do some stuff
            LOG.info("problem");
          }
        } catch (Exception e) {
          // do some stuff
          LOG.debug("exception ...");
        }  
      }  
    }

请注意,这是一个废话的例子,重复的代码等等。但是你明白了。我是否还应该创建一个&#34; import&#34;方面,用于记录此方法...所有随附的&#34; ifs&#34;记录&#34; ok&#34;,&#34;问题&#34;,&#34;例外&#34; ?并为每个用例执行此操作?

我全部都是为了摆脱侵入式日志记录代码,但是......似乎需要有一些代码气味,必须要有逻辑,以及它的&#34; ifs&#34;等等。在原始方法中(因为该方法是&#34;做更多的东西&#34;而不是记录)以及相应的方面......

无论如何,你们都回答了我原来的问题...但我只能有一个 的答案,所以我要接受kriegaex,因为他似乎有把很多的工作加入其中!

2 个答案:

答案 0 :(得分:0)

它可能。 在login方法中创建一个切入点/内部并在方面类中获取用户对象,一旦获得User对象,就可以有条件地进行日志记录。 要获取用户对象,请查看以下已回答的问题以及它如何获得surveyId.Same的值,以获取User对象。

@Around("updateDate()"
public Object myAspect(final ProceedingJoinPoint pjp) {

    //retrieve the runtime method arguments (dynamic)
    Object returnVal = null;
    for (final Object argument : pjp.getArgs())
    {

        if (argument instanceof SurveyHelper)
        {
            SurveyHelper surveyHelper = (SurveyHelper) argument;
            surveyId = surveyHelper.getSurveyId();

        }

    }
    try
    {
        returnVal = pjp.proceed();
    }
    catch (Throwable e)
    {
        gtLogger.debug("Unable to use JointPoint :(");
    }
    return returnVal;
}

以下是供您参考的完整链接: Spring AOP for database operation

答案 1 :(得分:0)

是的,有可能。但如果我是你,我会以不同的方式模拟整个故事。首先,由于未知或不活动的用户或错误的密码,我会抛出失败登录的异常。或者,login方法可以返回一个布尔值(成功登录时为true,否则为false)。但在我看来,这似乎是老式的C风格,而不是现代的OOP。

这是一个自洽的例子。抱歉,丑陋的UserDB类有很多静态成员和方法。实际上,你不会存储明文密码,而是随机盐和盐渍哈希。但毕竟它只是基于方面的条件记录的概念证明。

用于登录的用户bean:

package de.scrum_master.app;

public class User {
    private String id;
    private String password;

    public User(String id, String password) {
        this.id = id;
        this.password = password;
    }

    public String getId() {
        return id;
    }

    public String getPassword() {
        return password;
    }
}

用户数据库:

为简单起见,有硬编码的DB条目,静态枚举,成员和方法以及静态内部类。抱歉!我希望你可以很容易地想象如何用更好的设计做同样的事情。

package de.scrum_master.app;

import java.util.HashMap;
import java.util.Map;

public class UserDB {
    public static enum Role { admin, user, guest }
    public static enum Action { login, logout, read, write }
    public static enum Error { successful_login, user_inactive, invalid_password, unknown_user }

    private static class UserInfo {
        String password;
        Role role;
        boolean active;

        public UserInfo(String password, Role role, boolean active) {
            this.password = password;
            this.role = role;
            this.active = active;
        }
    }

    private static Map<String, UserInfo> knownUsers = new HashMap<>();

    static {
        knownUsers.put("bruce",   new UserInfo("alm1GHTy", Role.admin, true));
        knownUsers.put("john",    new UserInfo("LetMe_in", Role.user,  true));
        knownUsers.put("jane",    new UserInfo("heLL0123", Role.guest, true));
        knownUsers.put("richard", new UserInfo("dicky",    Role.user,  false));
        knownUsers.put("martha",  new UserInfo("paZZword", Role.admin, false));
    }

    public static class UserDBException extends Exception {
        private static final long serialVersionUID = 7662809670014934460L;

        public final String userId;
        public final Role role;
        public final Action action;
        public final Error error;

        public UserDBException(String userId, Role role, Action action, Error error, String message) {
            super(message);
            this.userId = userId;
            this.role = role;
            this.action = action;
            this.error = error;
        }
    }

    public static boolean isKnown(User user) {
        return knownUsers.get(user.getId()) != null;
    }

    public static boolean isActive(User user) {
        return isKnown(user) && knownUsers.get(user.getId()).active;
    }

    public static boolean isPasswordValid(User user) {
        return isKnown(user) && knownUsers.get(user.getId()).password.equals(user.getPassword());
    }

    public static Role getRole(User user) {
        return isKnown(user) ? knownUsers.get(user.getId()).role : null;
    }

    public static void login(User user) throws UserDBException {
        String userId = user.getId();
        if (!isKnown(user))
            throw new UserDBException(
                userId, getRole(user), Action.login,
                Error.unknown_user, "Unknown user"
            );
        if (!isActive(user))
            throw new UserDBException(
                userId, getRole(user), Action.login,
                Error.user_inactive, "Inactive " + getRole(user)
            );
        if (!isPasswordValid(user))
            throw new UserDBException(
                userId, getRole(user), Action.login,
                Error.invalid_password, "Invalid " + getRole(user) + " password"
            );
    }
}

请注意login(User)方法如何抛出异常,其中包含有助于记录的详细信息。

模拟多个用户/密码组合登录的驱动程序应用程序:

package de.scrum_master.app;

import java.util.Arrays;
import java.util.List;

public class Application {
    public static void main(String[] args) {
        List<User> users = Arrays.asList(
            new User("mr_x",    "foobar"),
            new User("bruce",   "foobar"),
            new User("bruce",   "alm1GHTy"),
            new User("john",    "foobar"),
            new User("john",    "LetMe_in"),
            new User("jane",    "foobar"),
            new User("jane",    "heLL0123"),
            new User("richard", "foobar"),
            new User("richard", "dicky"),
            new User("martha",  "foobar"),
            new User("martha",  "paZZword")
        );

        for (User user : users) {
            try {
                UserDB.login(user);
                System.out.printf("%-8s -> %s%n", user.getId(), "Successful " + UserDB.getRole(user) + " login");
            } catch (Exception e) {
                System.out.printf("%-8s -> %s%n", user.getId(), e.getMessage());
            }
        }
    }
}

请注意,我们只是捕获并记录所有异常,以避免在登录尝试失败后退出应用程序。

控制台日志:

mr_x     -> Unknown user
bruce    -> Invalid admin password
bruce    -> Successful admin login
john     -> Invalid user password
john     -> Successful user login
jane     -> Invalid guest password
jane     -> Successful guest login
richard  -> Inactive user
richard  -> Inactive user
martha   -> Inactive admin
martha   -> Inactive admin

登录记录器方面:

我建议您先在System.out.printf(..)中注释掉两个Application.main(..)来电,以免将其与方面记录混合起来。

package de.scrum_master.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import de.scrum_master.app.User;
import de.scrum_master.app.UserDB;
import de.scrum_master.app.UserDB.Action;
import de.scrum_master.app.UserDB.Error;
import de.scrum_master.app.UserDB.UserDBException;

@Aspect
public class UserActionLogger {
    @Around("execution(void de.scrum_master.app.UserDB.login(*)) && args(user)")
    public void captureLogin(ProceedingJoinPoint thisJoinPoint, User user) throws Throwable {
        try {
            thisJoinPoint.proceed();
            System.out.printf("%s|%s|%d03|%s%n",
                user.getId(), Action.login, Error.successful_login.ordinal(),
                "Successful " + UserDB.getRole(user) + " login"
            );
        } catch (UserDBException e) {
            System.out.printf("%s|%s|%03d|%s%n",
                e.userId, e.action, e.error.ordinal(),
                e.getMessage()
            );
            throw e;
        }
    }
}

方面的控制台日志:

mr_x|login|003|Unknown user
bruce|login|002|Invalid admin password
bruce|login|003|Successful admin login
john|login|002|Invalid user password
john|login|003|Successful user login
jane|login|002|Invalid guest password
jane|login|003|Successful guest login
richard|login|001|Inactive user
richard|login|001|Inactive user
martha|login|001|Inactive admin
martha|login|001|Inactive admin

Etvoilà!我希望这大致是你想要的。