Spring Aspect to log exceptions [duplicate] - spring

This question already has an answer here:
Exception handling and after advice
(1 answer)
Closed 1 year ago.
I added aspect like below in my spring-boot REST API to log calls to all methods in package "com.leanring.sprint" like so:
#Aspect
#Component
public class LogAdvice {
Logger logger = LoggerFactory.getLogger(LogAdvice.class);
#Pointcut(value = "execution(* com.learning.spring.*.*.*(..))")
public void pointCut() {
}
#Around("pointCut()")
public Object appLogger(ProceedingJoinPoint jp) throws Throwable {
ObjectMapper mapper = new ObjectMapper();
String methodName = jp.getSignature().getName();
String className = jp.getTarget().getClass().toString();
Object[] args = jp.getArgs();
logger.info("Start call: " + className + ":" + methodName + "()" + " with arguments: " + mapper.writeValueAsString(args));
Object object = jp.proceed();
logger.info("End call: " + className + ":" + methodName + "()" + " returned: " + mapper.writeValueAsString(object));
return object;
}
}
This is working fine, but I would also like it to be able to log any exceptions that could occur when a method is called.
How do I do that?

I suppose you could add another #AfterThrowing advice using the same pointcut or wrap jp.proceed(); inside a try-catch block.

Related

Measuring time of #Async annotated method's execution with the asprctj

How I can measure time of #Async annotated method's execution with the help of aspectj in spring boot program
Yes you can achieve this thing in following ways.
You just need to write around aspect for calculating time against a specific function.
Following is the code:
#Component
#Aspect
public class AsynMethodTimeAspect {
#Around(value = "#annotation(org.springframework.scheduling.annotation.Async)")
public Object logTime(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
String myClassname = joinPoint.getSignature().getDeclaringType().getSimpleName() + " : " + joinPoint.getSignature().getName();
long start = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - start;
log.info(myClassname + "." + method + " - end - " + elapsedTime + "ms");
return proceed;
}
}

Error casting object MethodSignature. Spring AOP

Thanks in advance for your support.
Currently I´m stuck in the next problem. I developed an Aspect class to validate my input JSON from al the pkg of RestController.
Complying with certain characteristics.
Each method of my controllers returns a different DTO object.
I created a new generic object to return it from my aspect, when my logic is not fulfilled. When I do tests, I get an error of CannotCastClass "xxxxDTO" to newErrorResponseDTO.
Currently I already can obtain the method signature or the object type. My idea is to cast the return type (from methodSignature) to my new DTOResponse. The object response is always different.
I mention that the architecture and design of the total project was already developed. I only did the aspect
At the moment, I have not succeeded.
I attach evidence. Thanks
I tried ResponseAdvice, and multiple ways to cast objects.
I prefer to stay in the aspect. I get the solution changing all the response DTO in controller to Object generic. Asumming that doing is bad practice, i prefer real solution
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
// Other imports missing...
#Aspect
#Component("validateParameterAspect")
public class ValidatorParameterAspect {
public static final Logger logger = Logger.getLogger(ValidatorParameterAspect.class);
#Autowired
ServiciosRest servicio;
#Pointcut("execution(* com.actinver.rest.*.* (..))")
public void executeController() {}
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void logRequestMapping() {}
#Around("logRequestMapping() && executeController() && args(..,#RequestBody requestBody) ")
public Object logRequestBody(ProceedingJoinPoint joinPoint, Object requestBody) throws Throwable {
String vlDataDecrypt = "";
try {
// output = joinPoint.proceed();
System.out.println("--------------123------------");
logger.warn("Entering in Method : " + joinPoint.getSignature().getName());
logger.warn("Class Name : " + joinPoint.getSignature().getDeclaringTypeName());
logger.warn("Arguments : " + Arrays.toString(joinPoint.getArgs()));
logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
SimpleJSONDataContainer args = (SimpleJSONDataContainer) joinPoint.getArgs()[0];
MethodSignature sign = (MethodSignature) joinPoint.getSignature();
Class<?> ret = sign.getReturnType();
String returnString = sign.getReturnType().getName();
logger.warn("Signature : " + ret);
vlDataDecrypt = AESHelper.decrypt(servicio.getSeedWord(), args.getData());
logger.info(" Decrypt -> " + vlDataDecrypt);
logger.info("args " + args.getData());
ErrorDataResponse res = validDataEmpty(args.getData());
if (res.getResult() == "2") {
return res; // or cast Class<?>
//return ret.cast(res);
}
} catch (Exception e) {
logger.error("Stack trace -> ", e);
}
return joinPoint.proceed();
}
public ErrorDataResponse validDataEmpty(String vlDataDecrypt) {
ErrorDataResponse errorDto = new ErrorDataResponse();
if (vlDataDecrypt == null || vlDataDecrypt.hashCode() == "77631826690E45839D7B49B932CBC81B".hashCode()
&& vlDataDecrypt.equalsIgnoreCase("77631826690E45839D7B49B932CBC81B")) {
errorDto.setResult("2");
errorDto.setMensaje(RestValidatorUtil.EnumErrors.ERROR_INPUT.getMsg());
logger.info("JSON null" + errorDto.getResult());
return errorDto;
}
return errorDto;
}
}
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
// Other imports missing...
#RestController
#RequestMapping("inicio")
public class Bursanet {
public final static Logger logger = Logger.getLogger(Bursanet.class);
#RequestMapping(
value = "cashByDate",
method = { RequestMethod.GET, RequestMethod.POST },
consumes = "application/json",
produces = "application/json"
)
public CashByDateDTO cashByDate(
#RequestBody SimpleJSONDataContainer simpleJSONDataContainer,
Authentication authentication
) {
String vlDataDecrypt = "";
CashByDateDTO outJson = new CashByDateDTO();
CashByDateRequest request = null;
try {
UsernamePasswordAuthenticationToken userPasswordAuthenticationToken =
(UsernamePasswordAuthenticationToken)
((OAuth2Authentication) authentication).getUserAuthentication();
//////example
return outJson;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
It is very difficult to analyse your code because you are not providing an MCVE:
There are no package names in your classes.
There are no imports either.
You use several project-specific classes (not part of the Spring Framework) the code of which you also don't share here.
There is no Spring configuration either.
So I have to make some educated guesses here. From what I can see, I can tell you this:
If you expect ValidatorParameterAspect.logRequestBody(..) to intercept execution of Bursanet.cashByDate(..), it should not work because
in args(.., #RequestBody requestBody) you are expecting that parameter to be the last one in the target method's signature, but actually in Bursanet.cashByDate(..) it is the first one. So the pointcut should never match.
Again in args(.., #RequestBody requestBody) you ought to use a fully qualified class name, i.e. args(.., #org.springframework.web.bind.annotation.RequestBody requestBody).
Please also note that execution(* com.actinver.rest.*.* (..)) only matches methods in classes residing directly in the com.actinver.rest package, not in any subpackages. If you want to include those too, you need to change the pointcut to execution(* com.actinver.rest..* (..)).
In your question you mention you only want to intercept REST controllers, but you do not limit pointcut matching to classes with a #RestController annotation. You could do that via #within(org.springframework.web.bind.annotation.RestController). Right now you are doing it indirectly by only relying on methods with #annotation(org.springframework.web.bind.annotation.RequestMapping), which will also work as long as those methods only occur in #RequestController classes. Probably this is the case in your application, I am just mentioning it as a detail.
Instead of SimpleJSONDataContainer args = (SimpleJSONDataContainer) joinPoint.getArgs()[0];, why don't you bind the first argument to a SimpleJSONDataContainer parameter via args() and then just use the currently unused requestBody advice method parameter in your code? Something like this:
#Around("logRequestMapping() && executeController() && args(#org.springframework.web.bind.annotation.RequestBody requestBody, ..)")
public Object logRequestBody(ProceedingJoinPoint joinPoint, SimpleJSONDataContainer requestBody) throws Throwable {
// (...)
vlDataDecrypt = AESHelper.decrypt(servicio.getSeedWord(), requestBody.getData());
logger.info(" Decrypt -> " + vlDataDecrypt);
logger.info("args " + requestBody.getData());
ErrorDataResponse res = validDataEmpty(requestBody.getData());
// (...)
}
You define MethodSignature sign = (MethodSignature) joinPoint.getSignature(); but don't use it above several times where you repeatedly call joinPoint.getSignature(), too. Instead you could just reorganise the code like this:
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
System.out.println("--------------123------------");
logger.warn("Entering in Method : " + methodSignature.getName());
logger.warn("Class Name : " + methodSignature.getDeclaringTypeName());
logger.warn("Arguments : " + Arrays.toString(joinPoint.getArgs()));
logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
Class<?> ret = methodSignature.getReturnType();
String returnString = methodSignature.getReturnType().getName();
I never understood why so many people call many JoinPoint methods in order to extract details for logging if instead they could simply log the joinpoint instance. This would show the type of pointcut (e.g. execution()) as well as the target method signature. Okay, if you want to list all method arguments, you can do this additionally, but how about this, wouldn't that be enough?
logger.warn(joinPoint);
// logger.warn("Entering in Method : " + methodSignature.getName());
// logger.warn("Class Name : " + methodSignature.getDeclaringTypeName());
logger.warn("Arguments : " + Arrays.toString(joinPoint.getArgs()));
// logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
This whole code block I guess you can also remove. It even prints wrong information and calls the return type "signature":
Class<?> ret = methodSignature.getReturnType();
String returnString = methodSignature.getReturnType().getName();
logger.warn("Signature : " + ret);
Now for the part which is probably your problem:
ErrorDataResponse res = validDataEmpty(requestBody.getData());
if (res.getResult() == "2") {
return res; // or cast Class<?>
//return ret.cast(res);
}
Here you are making the aspect advice skip the joinPoint.proceed() call and return another object instead. The method you intercept has the signature public CashByDateDTO cashByDate(..), i.e. it returns a specific DTO type. If you want to return an ErrorDataResponse instead, this would only work if ErrorDataResponse was a subtype of CashByDateDTO, which probably it is not. From the class names I would even say that a *Response and a *DTO are completely different object types. Your advice cannot just change or ignore the method signature. You have to return a CashByDateDTO object, no matter what. If you cannot do that here, maybe you are intercepting the wrong method or trying to do the wrong thing in your aspect.
Sorry for the lengthy reply, but there is so much chaos in your code, I had to point out some details.

Spring Expression Directly access map keys in Selection

Consider that I have parsed a JSON String to a Map<String, Object>, which is arbitrarily nested
My JSON looks like:
{
"root": [
{"k":"v1"},
{"k":"v2"}
]
}
I tried the expression root.?[k == 'v1'], however received the following error:
EL1008E: Property or field 'k' cannot be found on object of type 'java.util.LinkedHashMap' - maybe not public?
The evaluation context needs a MapAccessor:
public static void main(String[] args) throws Exception {
String json = "{\n" +
" \"root\": [\n" +
" {\"k\":\"v1\"},\n" +
" {\"k\":\"v2\"}\n" +
" ]\n" +
"}";
ObjectMapper mapper = new ObjectMapper();
Object root = mapper.readValue(json, Object.class);
Expression expression = new SpelExpressionParser().parseExpression("root.?[k == 'v1']");
StandardEvaluationContext ctx = new StandardEvaluationContext();
ctx.addPropertyAccessor(new MapAccessor());
System.out.println(expression.getValue(ctx, root));
}
result:
[{k=v1}]
Without a MapAccessor, you need
"['root'].?[['k'] == 'v1']"
A MapAccessor will only work with map keys that don't contain periods.

GWT & Java EE SessionScoped bean not persisting

I'm playing w/ EE and want to persist a user session state. I have the session bean here:
#Stateful(mappedName = "UserSessionState")
#Named("UserSessionState")
#SessionScoped
#StatefulTimeout(value = 5, unit = TimeUnit.MINUTES)
public class UserSessionState implements Serializable
{
private boolean hasPlayerId = false;
private String playerId = "";
public void setRandomPlayerId()
{
playerId = UUID.uuid();
hasPlayerId = true;
}
public boolean hasPlayerId()
{
return hasPlayerId;
}
public String getPlayerId()
{
return playerId;
}
}
And a servlet here (GameState is an Application Scoped bean that is working as expected, CustomExtendedHttpServlet is just a simple extension of HttpServlet)
public class NewUserJoined extends CustomExtendedHttpServlet
{
#Inject
protected GameState gameState;
#Inject
protected UserSessionState user;
#Override
protected String doGetImpl(HttpServletRequest request, HttpServletResponse response, UserContext userLoginContext)
{
if (!user.hasPlayerId())
{
user.setRandomPlayerId();
}
String userId = user.getPlayerId();
if (!gameState.hasUser(userId))
{
gameState.addUser(userId, user);
return "Hi, your ID is: " + user.getPlayerId() + ", there are " + gameState.getUserCount() + " other players here";
}
else
{
return user.getPlayerId() + " you're already in the game, there are: " + gameState.getUserCount() + " other players here";
}
}
}
I'm not sure what's going on, but whenever I call the New User Joined servlet from the same HTTP session, I get this response on the first call (as expected):
"Hi, your ID is: , there are 1 other players here"
Repeating the same servlet call in the same session gives me the same message:
"Hi, your ID is: , there are 2 other players here"
...
It looks like a new instance of User Session State is getting created over and over. Am I doing this correctly?
EDIT 1: Here the code I use to send a request. It appears I'm getting a new session ID with each request, what could cause that?
RequestCallback callback = new RequestCallback()
{
#Override
public void onResponseReceived(Request request, Response response)
{
log(response.getText());
}
#Override
public void onError(Request request, Throwable exception)
{
log(
"Response Error | "
+ "Exception: " + exception);
}
};
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, SERVLET_URL);
rb.setCallback(callback);
try
{
rb.send();
}
catch (RequestException e)
{
log("Response Error | "
+ "Exception: " + e);
}
Figured out the issue,
Turns out I had an old workaround in the GWT client that was changing the host to get around a CORS issue. Because the response didn't match up to the origin, the cookie wasn't getting sent with future servlet GET calls.
Have you tried a call to request.getSession(true) to make sure an EE HTTPSession is established here?

Deserializing an interface using Gson?

I'm trying to use Gson with an interface:
public interface Photo {
public int getWidth();
}
public class DinosaurPhoto implements Photo {
...
}
public class Wrapper {
private Photo mPhoto; // <- problematic
}
...
Wrapper wrapper = new Wrapper();
wrapper.setPhoto(new DinosaurPhoto());
Gson gson = new Gson();
String raw = gson.toJson(wrapper);
// Throws an error since "Photo" can't be deserialized as expected.
Wrapper deserialized = gson.fromJson(raw, Wrapper.class);
Since the Wrapper class has a member variable that is of type Photo, how do I go about deserializing it using Gson?
Thanks
Custom deserialization is necessary.
Depending on the larger problem to be solved, either a ["type adapter"] 1 or a "type hierarchy adapter" should be used. The type hierarchy adapter "is to cover the case when you want the same representation for all subtypes of a type".
Simply put, you can't do that with GSON.
I was troubled by the same problem when I stumbled upon Jackson.
With it it is very easy:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
And then you can go about de/serializing your Java objects and interfaces without having to write additional custom de/serializers, annotaions and really no added code whatsoever.
This was not a part of the question, but may prove useful if you decide to port from Gson to Jackson.
Gson supports private fields by default but for Jackson you have to include this in your code.
mapper.setVisibilityChecker(g.getVisibilityChecker().with(Visibility.ANY));
Sample implementation for your code in main:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
mapper.setVisibilityChecker(g.getVisibilityChecker().with(Visibility.ANY));
Wrapper wrapper = new Wrapper();
wrapper.setPhoto(new DinosaurPhoto());
String wrapper_json = mapper.writeValueAsString(wrapper);
Wrapper wrapper_from_json = mapper.readValue(wrapper_json,Wrapper.class);
Gson promised they will work on this problem in future versions, but they haven't solved it so far.
If this is very important for you application I would suggest that you port to Jackson.
I have built a primitive interface shim generator by way of compiling a groovy properties class to interoperate with a GWT Autobeans model. this is a really rough method to sidestep the ASM/cglib learning curve for now. background on this: with Autobeans, you may only use interfaces, and the sun.* proxies are incapable of gson interop for all the access attempts I have experimented with. BUT, when groovy classloader is local to GsonBuilder, things get a tiny bit easier. note, this fails unless the gsonBuilder registration is actually called from within the groovy itself.
to access the shim factory create one as a singleton names JSON_SHIM and call
JSON_SHIM.getShim("{}",MyInterface.class)
to register if needed and create a [blank] instance. if you have interfaces in your interfaces, you must pre-register those too ahead of use. this is just enough magic to use flat Autobeans with gson, not a whole framework.
there is no groovy code in this generator, so someone with javassist-foo can repeat the experiment.
import com.google.gson.GsonBuilder;
import com.google.gson.InstanceCreator;
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
import groovy.lang.GroovyClassLoader;
import org.apache.commons.beanutils.PropertyUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
public class GroovyGsonShimFactory {
private Map<Class, Method> shimMethods = new LinkedHashMap<>();
private void generateGroovyProxy(Class ifaceClass) {
String shimClassName = ifaceClass.getSimpleName() + "$Proxy";
String ifaceClassCanonicalName = ifaceClass.getCanonicalName();
String s = "import com.google.gson.*;\n" +
"import org.apache.commons.beanutils.BeanUtils;\n" +
"import java.lang.reflect.*;\n" +
"import java.util.*;\n\n" +
"public class "+shimClassName+" implements "+ifaceClassCanonicalName+" {\n" ;
{
PropertyDescriptor[] propertyDescriptors = PropertyUtils.getPropertyDescriptors(ifaceClass);
for (PropertyDescriptor p : propertyDescriptors) {
String name = p.getName();
String tname = p.getPropertyType().getCanonicalName();
s += "public " + tname + " " + name + ";\n";
s += " " + p.getReadMethod().toGenericString().replace("abstract", "").replace(ifaceClassCanonicalName + ".", "") + "{return " + name + ";};\n";
Method writeMethod = p.getWriteMethod();
if (writeMethod != null)
s += " " + writeMethod.toGenericString().replace("abstract", "").replace(ifaceClassCanonicalName + ".", "").replace(")", " v){" + name + "=v;};") + "\n\n";
}
}
s+= " public static "+ifaceClassCanonicalName+" fromJson(String s) {\n" +
" return (" +ifaceClassCanonicalName+
")cydesign.strombolian.server.ddl.DefaultDriver.gson().fromJson(s, "+shimClassName+".class);\n" +
" }\n" +
" static public interface foo extends InstanceCreator<"+ifaceClassCanonicalName+">, JsonSerializer<"+ifaceClassCanonicalName+">, JsonDeserializer<"+ifaceClassCanonicalName+"> {}\n" +
" static {\n" +
" cydesign.strombolian.server.ddl.DefaultDriver.builder().registerTypeAdapter("+ifaceClassCanonicalName+".class, new foo() {\n" +
" public "+ifaceClassCanonicalName+" deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {\n" +
" return context.deserialize(json, "+shimClassName+".class);\n" +
" }\n" +
"\n" +
" public "+ifaceClassCanonicalName+" createInstance(java.lang.reflect.Type type) {\n" +
" try {\n" +
" return new "+shimClassName+"();\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace(); \n" +
" }\n" +
" return null;\n" +
" }\n" +
"\n" +
" #Override\n" +
" public JsonElement serialize("+ifaceClassCanonicalName+" src, Type typeOfSrc, JsonSerializationContext context) {\n" +
" LinkedHashMap linkedHashMap = new LinkedHashMap();\n" +
" try {\n" +
" BeanUtils.populate(src, linkedHashMap);\n" +
" return context.serialize(linkedHashMap);\n" +
" } catch (Exception e) {\n" +
" e.printStackTrace(); \n" +
" }\n" +
"\n" +
" return null;\n" +
" }\n" +
" });\n" +
" }\n\n" +
"};";
System.err.println("" + s);
ClassLoader parent = DefaultDriver.class.getClassLoader();
GroovyClassLoader loader = new GroovyClassLoader(parent);
final Class gClass = loader.parseClass(s);
try {
Method shimMethod = gClass.getMethod("fromJson", String.class);
shimMethods.put(ifaceClass, shimMethod);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public <T> T getShim(String json, Class<T> ifaceClass) {
if (!shimMethods.containsKey(ifaceClass))
generateGroovyProxy(ifaceClass);
T shim = null;//= gson().shimMethods(json, CowSchema.class);
try {
shim = (T) shimMethods.get(ifaceClass).invoke(null, json);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
return shim;
}
}

Resources