Spring AOP pass String argument of a controller method - spring

I have a controller class with one RequestMapping method which is taking String arguments. I want to pass this argument by using Spring AOP but its failing, I am getting null value when I am printing the value.
Tried with the below provided solution but its working with map but not with String.
Spring AOP pass argument of a controller method
#Controller
public class WelcomeController {
#Autowired
private FamilyService familyService;
#RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView welcomePage(String welcomeMessage) {
FamilyVO allFamilyMembers = familyService.getAllFamilyMembers();
ModelAndView modelAndView = new ModelAndView("Index", "family", allFamilyMembers);
List<String> familyMemberAges = new ArrayList<String>();
for (int i = 0; i <= 100; i++) {
familyMemberAges.add("" + i);
}
modelAndView.addObject("familyMemberAges", familyMemberAges);
System.out.println(welcomeMessage);
return modelAndView;
}
}
#Component
#Aspect
public class WelcomeControllerAspect {
#Before("execution(* com.kalavakuri.webmvc.web.controller.WelcomeController.welcomePage(..))")
public void beforeWelcomePage(JoinPoint joinPoint) {
joinPoint.getArgs()[0] = "Hellow";
System.out.println(joinPoint.getArgs().length);
System.out.println("Before welcomepage");
}
}
I am expecting the value "Hello" when I print it in Controller class but printing null.

A #Before advice is not meant to manipulate method parameters. In the example you linked to it only works because the argument is a mutable object, namely a Map. A String is immutable, though, you cannot edit it.
Having said that, what should you do? Use an #Around advice which was designed for that kind of thing. There you can decide how you want to proceed, e.g.
call the original method with original parameters,
call the original method with changed parameters,
do something before and/or after calling the original,
don't call the original method but return another result instead,
handle exceptions in the original method
or any mix of the above which makes sense (maybe you have multiple cases and if-else or switch-case).
I also suggest not to work directly on the Object[] of JoinPoint.getArgs() but to bind the relevant method parameter(s) to a named and type-safe advice parameter via args(). Try this:
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class WelcomeControllerAspect {
#Around(
"execution(* com.kalavakuri.webmvc.web.controller.WelcomeController.welcomePage(..)) && " +
"args(welcomeMessage)"
)
public Object beforeWelcomePage(ProceedingJoinPoint joinPoint, String welcomeMessage) throws Throwable {
System.out.println(joinPoint + " -> " + welcomeMessage);
return joinPoint.proceed(new Object[] { "Hello AOP!" });
}
}

You should use an #Around advice instead of a #Before.
See this answer for more details.

Related

Inserting Post Method with Spring Boot

I'm learning Kotlin, part of my project is to integrate JSON as an object and use the POST method to change or add information.
I'm not able to do this, I need help.
package com.example.blog
import org.springframework.web.bind.annotation.*
data class Relatorio(
val titulo: String,
val autor: String,
val serie: String
)
#RestController
#RequestMapping("/Bradesco")
class BradescoController {
#GetMapping()
public fun relatorio(): Relatorio {
val result = Relatorio(
"Investimentos",
"Luis Felipe",
"Bradesco Analises"
)
return result
}
#PostMapping
#RequestMapping( #RequestBody "/empiricus")
public fun relatorio2() {
"titulo" = "Contra as altas taxas"
return "Atualizado";
}
}
It looks like some annotations are out of place in your relatorio2 method. You want to register a REST-endpoint for the POST-method and the path /empiricus.
This can happen one of two ways:
Annotate the method with #RequestMapping(value = "/empiricus", method = RequestMethod.POST)
Annotate the method with `#PostMapping("/empiricus") (you can omit the method-parameter from the example above, since this a shortcut for exactly that.
The #RequestBody annotation needs to be placed in the parameter of the relatorio2 method since it tells Spring to map the POST request-body to an object.
Therefore the method should look something like this:
#PostMapping("/empiricus")
public fun relatorio2(#RequestBody relatorio: Relatorio) {
"titulo" = "Contra as altas taxas"
return "Atualizado";
}
Since you added a path on class level, the complete path to call the method is /Bradesco/empiricus. When the object is available in the relatorio2 method, you can use it in your business logic.

Annotation aspect for both Class Level and Method Level in Spring Boot (NullPointerException)

I am just facing interesting issue when I want to create an aspect for annotation like #Transactional.
Here is the controller and annotation:
#RestController
#SimpleAnnotation
public class HelloController{
private static final Logger LOGGER = LoggerFactory.getLogger(HelloController.class);
#GetMapping("/hello")
#SimpleAnnotation(isAllowed=true)
public String helloController(){
final String methodName = "helloController";
callAnotherMerhod();
LOGGER.info("HelloController for method : {}", methodName);
return "Hello";
}
private void callAnotherMethod(){
}
}
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface SimpleAnnotation {
boolean isAllowed() default false;
}
Aspect for that annotation is here:
#Aspect
#Component
public class SimpleAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleAspect.class);
#Around(value = "#within(simpleAnnotation) || #annotation(simpleAnnotation)", argNames = "simpleAnnotation")
public Object simpleAnnotation(ProceedingJoinPoint proceedingJoinPoint, SimpleAnnotation simpleAnnotation) throws Throwable{
LOGGER.info("Simple annotation value: {}, ASPECT-LOG {}", simpleAnnotation.isAllowed(),proceedingJoinPoint.getSignature().getName());
return proceedingJoinPoint.proceed();
}
}
When I run the application and hit the http://localhost:8080/hello , everything is fine:
2020-11-09 11:36:48.230 INFO 8479 --- [nio-8080-exec-1] c.s.springaop.aspects.SimpleAspect : Simple annotation value: true, ASPECT-LOG helloController
2020-11-09 11:36:48.246 INFO 8479 --- [nio-8080-exec-1] c.s.s.controller.HelloController : HelloController for method : helloController
However if I removed annotation on the method:
#GetMapping("/hello")
public String helloController(){
final String methodName = "helloController";
callAnotherMethod();
LOGGER.info("HelloController for method : {}", methodName);
return "Hello";
}
Then simpleAnnotation parameter becomes null, and aspect method throws NullPointerException.
After that, I changed the order of the aspect like the below, it start to work:
#Around(value = " #annotation(simpleAnnotation) || #within(simpleAnnotation)", argNames = "simpleAnnotation")
However, in this situation if I remove annotation on class level and just put only the method level, then i am facing the same NPE.
I think, in some way aspect's conditions overwrites the values.
I tried to separate the class level annotation advice and method level advice, but in that case if i have the annotation on both the class and method level, both advices work (which I do not want)
I tried to update like this:
#Around(value = "#within(simpleAnnotation) || #annotation(simpleAnnotation) || #within(simpleAnnotation)", argNames = "simpleAnnotation")
This seems to be working, but is it a good solution?
EDIT: This solution is not working also. If I have annotation both on the class and method level and let's say class level annotation value is false, and method's level is true, then annotation value will be false.
To workaround the NPE , you may refactor the pointcut designators (#within and #annotation) to two different advice methods within the same aspect.
The logic to process based on the isAllowed value can be held in a common method and called from both the advice methods.
To illustrate :
#Aspect
#Component
public class SimpleAspect {
#Around(value = "#annotation(simpleAnnotation) && !#within(my.package.SimpleAnnotation)", argNames = "simpleAnnotation")
public Object simpleAnnotationOnMethod(ProceedingJoinPoint proceedingJoinPoint, SimpleAnnotation simpleAnnotation)
throws Throwable {
System.out.println("Simple annotation value:" + simpleAnnotation.isAllowed() + " , ASPECT-LOG :"
+ proceedingJoinPoint.getSignature().getName());
process(simpleAnnotation);
return proceedingJoinPoint.proceed();
}
#Around(value = "#within(simpleAnnotation)", argNames = "simpleAnnotation")
public Object simpleAnnotationOnType(ProceedingJoinPoint proceedingJoinPoint, SimpleAnnotation simpleAnnotation)
throws Throwable {
System.out.println("Simple annotation value:" + simpleAnnotation.isAllowed() + " , ASPECT-LOG :"
+ proceedingJoinPoint.getSignature().getName());
process(simpleAnnotation);
return proceedingJoinPoint.proceed();
}
private void process(SimpleAnnotation simpleAnnotation) {
// advice logic
}
}
Update : Modified the code as commented by #kriegaex
Your parameter binding is ambiguous due to the || because it is a (non-exclusive) OR rather than an XOR, which means that both conditions could be true at the same time. Imagine both the class and the method have an annotation. Which one should be bound?
See also this answer.
I.e. that, just like R.G said, you want to use two separate pointcuts and advices for method- and class-level annotations. You can still factor out duplicate code into a helper method inside the aspect and call it from both advice methods.

Spring AOP: How to read path variable value from URI template in aspect?

I want to create Spring aspect which would set method parameter, annotated by custom annotation, to an instance of a particular class identified by an id from URI template. Path variable name is parameter of the annotation. Very similar to what Spring #PathVariable does.
So that controller method would look like:
#RestController
#RequestMapping("/testController")
public class TestController {
#RequestMapping(value = "/order/{orderId}/delete", method = RequestMethod.GET)
public ResponseEntity<?> doSomething(
#GetOrder("orderId") Order order) {
// do something with order
}
}
Instead of classic:
#RestController
#RequestMapping("/testController")
public class TestController {
#RequestMapping(value = "/order/{orderId}/delete", method = RequestMethod.GET)
public ResponseEntity<?> doSomething(
#PathVariable("orderId") Long orderId) {
Order order = orderRepository.findById(orderId);
// do something with order
}
}
Annotation source:
// Annotation
#Target(ElementType.PARAMETER)
#Retention(RetentionPolicy.RUNTIME)
public #interface GetOrder{
String value() default "";
}
Aspect source:
// Aspect controlled by the annotation
#Aspect
#Component
public class GetOrderAspect {
#Around( // Assume the setOrder method is called around controller method )
public Object setOrder(ProceedingJoinPoint jp) throws Throwable{
MethodSignature signature = (MethodSignature) jp.getSignature();
#SuppressWarnings("rawtypes")
Class[] types = signature.getParameterTypes();
Method method = signature.getMethod();
Annotation[][] annotations = method.getParameterAnnotations();
Object[] values = jp.getArgs();
for (int parameter = 0; parameter < types.length; parameter++) {
Annotation[] parameterAnnotations = annotations[parameter];
if (parameterAnnotations == null) continue;
for (Annotation annotation: parameterAnnotations) {
// Annotation is instance of #GetOrder
if (annotation instanceof GetOrder) {
String pathVariable = (GetOrder)annotation.value();
// How to read actual path variable value from URI template?
// In this example case {orderId} from /testController/order/{orderId}/delete
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder
.currentRequestAttributes()).getRequest();
????? // Now what?
}
} // for each annotation
} // for each parameter
return jp.proceed();
}
}
UPDATE 04/Apr/2017:
Answer given by Mike Wojtyna answers the question -> thus it is accepted.
Answer given by OrangeDog solves the problem form different perspective with existing Spring tools without risking implementation issue with new aspect. If I knew it before, this question would not be asked.
Thank you!
If you already have access to HttpServletRequest, you can use HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE spring template to select map of all attributes in the request. You can use it like that:
request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE)
The result is a Map instance (unfortunately you need to cast to it), so you can iterate over it and get all the parameters you need.
The easiest way to do this sort of thing is with #ModelAttribute, which can go in a #ControllerAdvice to be shared between multiple controllers.
#ModelAttribute("order")
public Order getOrder(#PathVariable("orderId") String orderId) {
return orderRepository.findById(orderId);
}
#DeleteMapping("/order/{orderId}")
public ResponseEntity<?> doSomething(#ModelAttribute("order") Order order) {
// do something with order
}
Another way is to implement your own PathVariableMethodArgumentResolver that supports Order, or register a Converter<String, Order>, that the existing #PathVariable system can use.
Assuming that it is always the first parameter bearing the annotation, maybe you want to do it like this:
package de.scrum_master.aspect;
import java.lang.annotation.Annotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import de.scrum_master.app.GetOrder;
#Aspect
#Component
public class GetOrderAspect {
#Around("execution(* *(#de.scrum_master.app.GetOrder (*), ..))")
public Object setOrder(ProceedingJoinPoint thisJoinPoint) throws Throwable {
MethodSignature methodSignature = (MethodSignature) thisJoinPoint.getSignature();
Annotation[][] annotationMatrix = methodSignature.getMethod().getParameterAnnotations();
for (Annotation[] annotations : annotationMatrix) {
for (Annotation annotation : annotations) {
if (annotation instanceof GetOrder) {
System.out.println(thisJoinPoint);
System.out.println(" annotation = " + annotation);
System.out.println(" annotation value = " + ((GetOrder) annotation).value());
}
}
}
return thisJoinPoint.proceed();
}
}
The console log would look like this:
execution(ResponseEntity de.scrum_master.app.TestController.doSomething(Order))
annotation = #de.scrum_master.app.GetOrder(value=orderId)
annotation value = orderId
If parameter annotations can appear in arbitrary positions you can also use the pointcut execution(* *(..)) but this would not be very efficient because it would capture all method executions for each component in your application. So you should at least limit it to REST controlers and/or methods with request mappings like this:
#Around("execution(#org.springframework.web.bind.annotation.RequestMapping * (#org.springframework.web.bind.annotation.RestController *).*(..))")
A variant of this would be
#Around(
"execution(* (#org.springframework.web.bind.annotation.RestController *).*(..)) &&" +
"#annotation(org.springframework.web.bind.annotation.RequestMapping)"
)

Spring MVC RestController allow params with different names in methods

I am writing an API using Spring MVC and I am coming up with a problem allowing apps written in different languages to consume my API.
It turns out that the "Ruby users" like to have their params named in snake_case and our "Java users" like to have their param names in camel_case.
Is it possible to create my methods that allow param names to be named multiple ways, but mapped to the same method variable?
For instance... If I have a method that accepts a number of variables, of them there is mapped to a postal code. Could I write my method with a #RequestParam that accepts BOTH "postal_code" and "postalCode" and maps it to the same variable?
Neither JAX-RS #QueryParam nor Spring #RequestParam support your requirement i.e., mapping multiple request parameter names to the same variable.
I recommend not to do this as it will be very hard to support because of the confusion like which parameter is coming from which client.
But if you really wanted to handle this ((because you can't change the URL coming from 3rd parties, agreed long back), then the alternative is to make use of HandlerMethodArgumentResolver which helps in passing our own request argument (like #MyRequestParam) to the controller method like as shown in the below code:
Controller class:
#Controller
public class MyController {
#RequestMapping(value="/xyz")
public void train1(#MyRequestParam String postcode) {//custom method argument injected
//Add your code here
}
}
MyRequestParam :
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface MyRequestParam {
}
HandlerMethodArgumentResolver Impl class:
public class MyRequestParamWebArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
MyRequestParam myRequestParam =
parameter.getParameterAnnotation(MyRequestParam.class);
if(myRequestParam != null) {
HttpServletRequest request =
(HttpServletRequest) webRequest.getNativeRequest();
String myParamValueToBeSentToController = "";
//set the value from request.getParameter("postal_code")
//or request.getParameter("postalCode")
return myParamValueToBeSentToController;
}
return null;
}
#Override
public boolean supportsParameter(MethodParameter parameter) {
return (parameter.getParameterAnnotation(MyRequestParam.class) != null);
}
}
WebMvcConfigurerAdapter class:
#Configuration
class WebMvcContext extends WebMvcConfigurerAdapter {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new MyRequestParamWebArgumentResolver());
}
}
I think what you want to do is not allowed by Spring framework with the annotation RequestParam.
But if you can change the code or say to your third party to modify the calls i would suggest you 2 options
Option 1:
Use the #PathVariable property
#RequestMapping(value = "/postalcode/{postalCode}", method = RequestMethod.GET)
public ModelAndView yourMethod(#PathVariable("postalCode") String postalCode) {
//...your code
Here does not matter if the are calling your URL as:
http://domain/app/postalcode/E1-2ES
http://domain/app/postalcode/23580
Option 2:
Create 2 methods in your controller and use the same service
#RequestMapping(value = "/postalcode", method = RequestMethod.GET, params={"postalCode"})
public ModelAndView yourMethod(#RequestParam("postalCode") String postalCode) {
//...call the service
#RequestMapping(value = "/postalcode", method = RequestMethod.GET, params={"postal_code"})
public ModelAndView yourMethodClient2(#RequestParam("postal_code") String postalCode) {
//...call the service
If is possible, I would suggest you option 1 is much more scalable

My spring mvc session was changed implicitly after called a service method?

I'm using spring 3.2.5 via annotations and got some issue dealing with session.
My controller class is like this:
#Controller
public class WebController {
#Autowired
private IElementService elementService;
...
//in this method I set the "elementList" in session explicitly
#RequestMapping("/elementSearch.do")
public String elementSearch(
#RequestParam("keyword") String keyword,
HttpSession session){
List<Element> elementList= elementService.searchElement(keyword);
session.setAttribute("elementList", elementList);
return "searchResult";
}
//here I got my problem
#RequestMapping(value="/anotherMethod.do", produces="text/html; charset=utf-8")
#ResponseBody
public String anotherMethod(
...
//I called my service method here like
Element e = elementService.searchElement("something").get(0);
...
}
And I have a ElementServiceImpl class like this:
#Service
public class ElementServiceImpl implements IElementService {
#Autowired
private IBaseDAO baseDao;
#Override
public List<Metadata> searchElement(String keyword) {
List<Metadata> re = baseDao.searchElement(keyword);
return re;
}
}
And I have a BaseDAOImpl class implemented IBaseDAO and annonated with #Repository:
#Repository
public class BaseDAOImpl implements IBaseDAO {
...
}
Here is the problem, when I visit ".../anotherMethod.do", which will call the anotherMethod up there, my "elementList" in session was changed!
Then I looked into the anotherMethod() and found everytime
Element e = elementService.searchElement("something").get(0);
was called, my elementList was change to the new result returned by searchElement method(which returns a List).
But I didn't set session in that method, and I'm not using #SessionAttributes, so I don't understand how could my session attribute changed after calling a service method?
This problem is torturing me right now so any advise would be a great help, thanks!
update: I tried to print all my session attributes around that method call like this:
StringBuilder ss1 = new StringBuilder("-------------current session-------------\n");
session.setAttribute("test1", "test value 1");
log.info("sessionTest - key:test1 value:" + session.getAttribute("test"));
Enumeration<String> attrs1 = session.getAttributeNames();
while(attrs1.hasMoreElements()){
String key = attrs1.nextElement();
ss1.append(key).append(":").append(session.getAttribute(key)).append("\n");
}
log.info(ss1);
But I didn't see whether the "elementList" or the test value which I added just before print. And I do can get some value by
List<Element> elementList = (List<Element>) session.getAttribute("elementList");
and the elementList I get changed after calling service method, just like I posted before. Where my elementList stored? not in the session?
My goal is to show the elementList to the users in a table, and let them pick one of them, then I get the row number of the table and take it as a index of the elemntList, so I'll know which elemnt object the user picked. Are there any better way to do this so I can get rid of that problem?
Thanks again.

Resources