wheeleruniverse
wheeleruniverse

Reputation: 1625

How to determine a method signature dynamically when throwing custom exceptions?

I am following a standard that requires me to log the class#method that originates an exception for easy debugging from our UI. Without pulling the logs I know what method spawned the exception and can (usually) easily find the root cause.

However, I would like a dynamic way of determining the method signature because I am currently hard-coding it everywhere I am throwing an exception. Is there a better way.

 @Service
 public class UserService {

     @Autowired
     private UserRepository userRepository;

     public User findUser(Long id){

          if(id == null || id < 1){
               throw new BadRequestException("UserService#findUser", String.format(
                                             "Invalid Id: %d", id));
          }

          User user = userRepository.findOne(id);

          if(user == null){
               throw new DataNotFoundException("UserService#findUser", String.format(
                                               "Can't Find User for Id: %d", id));
          }
          return user;
     }
 }

--

The goal is that I can create any custom exception and within that constructor I can infer the method signature by where the exception was created from.

 public BadRequestException(String issue){
      super(String.format("%s | Bad Request | %s", <<signature>>, issue);
 }

--

 UserService class --> findUser method --> BadRequest("UserService#findUser")
 UserService class --> createUser method --> BadRequest("UserService#createUser")

Upvotes: 2

Views: 400

Answers (2)

davidxxx
davidxxx

Reputation: 131336

You could introduce a helper method that retrieves the method and the class names from the current thread :

private static MethodAndClassName retrieveMethodAndClassName() {
       StackTraceElement[] allStackTraces = Thread.currentThread().getStackTrace();
       StackTraceElement currentMethod = allStackTraces[2]; 
       return new MethodAndClassName(currentMethod.getMethodName(), currentMethod.getClassName());      
}

Note that the method invocations are stacked. I get the third last nested call (allStackTraces[2]) because the last call (allStackTraces[0]) is Thread.getStackTrace(), the before last call (allStackTraces[1]) is retrieveMethodAndClassName() and the before before last call (allStackTraces[2]) is what you look for : the method that invoked retrieveMethodAndClassName().

MethodAndClassName is a custom class that defines needed information to trace :

public class MethodAndClassName {

    private String method;
    private String clazz;

    public MethodAndClassName(String method, String clazz) {
        this.method = method;
        this.clazz = clazz;
    }

    public String getMethodName() {
        return method;
    }

    public String getClassName() {
        return clazz;
    }
}

And in your client code use it such as :

 public User findUser(Long id){

      if(id == null || id < 1){             
           throw new BadRequestException(ReflectionHelper.retrieveMethodAndClassName(), String.format(
                                         "Invalid Id: %d", id));
      }

      User user = userRepository.findOne(id);

      if(user == null){
           throw new DataNotFoundException(ReflectionHelper.retrieveMethodAndClassName(), String.format(
                                           "Can't Find User for Id: %d", id));
      }
      return user;
 }

Upvotes: 3

Steve11235
Steve11235

Reputation: 2923

Use Throwable.getStackTrace(). This will return an array of StackTraceElement, and the first entry will point to where the the Throwable was instantiated.

Upvotes: 0

Related Questions