Why is Spring ignoring #Transactional in spring aop? - spring

I want to add some log in my service,so I using Annotation and AOP.At first,I write the code like this:
#Aspect
#Component
public class LogAspect {
#Autowired
LogService logService;
#Transactional
Object log(ProceedingJoinPoint point) throws Throwable{
Object obj = null;
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature)signature;
Method method = methodSignature.getMethod();
LogAnnotation myAnno = method.getAnnotation(LogAnnotation.class);
String pageId=null;
JSONObject object=(JSONObject)point.getArgs()[0];
pageId=object.getString("pageId");
obj = point.proceed(); //do business job and affect the database
int i=1/0; //test Transactional
logService.insertLog(pageId,(LogVo)obj);
return obj;
}
#Around("#annotation(com.mycompany.annotation.LogAnnotation)")
public Object triggerSome(ProceedingJoinPoint pjp) throws Throwable {
return log( pjp);
}
}
In my example,I have add #Transactional and add / by zero exception,but when do business job code still affect the database.It seems nothingt to do with the word #Transactional.How to change my code?
I have try to change all my code in to a service,but still have not Transaction.
#Aspect
#Component
public class LogAspect {
#Autowired
LogService logService;
#Around("#annotation(com.mycompany.annotation.LogAnnotation)")
public Object triggerSome(ProceedingJoinPoint pjp) throws Throwable {
return logService.commonLog( pjp);
}
}
The common log service is :
#Transactional(rollbackFor=Throwable.class)
#Override
public Object commonLog(ProceedingJoinPoint point) throws Throwable{
Object obj = null;
Signature signature = point.getSignature();
MethodSignature methodSignature = (MethodSignature)signature;
Method method = methodSignature.getMethod();
LogAnnotation myAnno = method.getAnnotation(LogAnnotation.class);
String pageId=null;
JSONObject object=(JSONObject)point.getArgs()[0];
pageId=object.getString("pageId");
obj = point.proceed();
int i=1/0;
LogService.insertLog(pageId,(LogVo)obj);
return obj;
}

Related

Spring AOP matches invalid function

This is small sample from bigger project, trying to replicate issue that we see.
Implementation:
#Component
public class TestService {
public void test(String target, String key, Object message) {
System.out.println("Wrong method");
}
public void test(String target, Object message, SomeProcessor processor) {
System.out.println("Correct method");
}
}
Wrapper:
#Aspect
#Component
public class TestServiceAspect {
#Around(value = "execution(* com.example.TestService.test(..))" +
" && args(target, key, message)",
argNames = "pjp,target,key,message"))
public Object traceTest(ProceedingJoinPoint pjp,
String target, String key, Object message) throws Throwable {
System.out.println("Wrong wrapper");
return pjp.proceed();
}
#Around(value = "execution(* com.example.TestService.test(..))" +
" && args(target, message, processor)",
argNames = "pjp,target,message,processor")
public Object traceTest(ProceedingJoinPoint pjp,
String target, Object message, SomeProcessor processor) throws Throwable {
System.out.println("Correct wrapper");
return pjp.proceed();
}
}
Calling code:
#RestController
public class Tester {
private final TestService tst;
public Tester(TestService tst) {
this.tst = tst;
}
#RequestMapping(value = "/test", method = RequestMethod.POST)
public void testHandler() {
System.out.println("START");
tst.test("foo", (Object)"hello", new SomeProcessorImpl());
System.out.println("END");
}
}
We get following output:
START
Wrong wrapper
Correct wrapper
Correct method
END
Why is it OK for second pointcut ("Wrong wrapper") to match (is it correct at all)? I know why it happens, because they internally check if type can be coerced into expected type. Based on runtime information, framework will detect that passed object ("hello") can be cast into String and SomeProcessor can be converted to Object. But does it still make right thing to do?
Just make your pointcuts more precise, not just the bound parameter types. Binding parameters is only necessary, if you actually want to use them in the advice methods.
Here is an MCVE for native AspectJ, which should work the same way in Spring AOP. I was just lazy to set up a Spring project with #Component and #Service stuff.
package de.scrum_master.app;
public interface SomeProcessor {}
package de.scrum_master.app;
public class SomeProcessorImpl implements SomeProcessor {}
package de.scrum_master.app;
public class TestService {
public void test(String target, String key, Object message) {
System.out.println("Wrong method");
}
public void test(String target, Object message, SomeProcessor processor) {
System.out.println("Correct method");
}
}
package de.scrum_master.app;
public class Tester {
private final TestService tst;
public Tester(TestService tst) {
this.tst = tst;
}
public void testHandler() {
System.out.println("START");
tst.test("foo", (Object) "hello", new SomeProcessorImpl());
// tst.test("foo", "hello", (Object) new SomeProcessorImpl());
System.out.println("END");
}
public static void main(String[] args) {
new Tester(new TestService()).testHandler();
}
}
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.SomeProcessor;
#Aspect
public class TestServiceAspect {
#Around("execution(* de.scrum_master.app.TestService.test(String, String, Object)) && args(target, key, message)")
public Object traceTest(ProceedingJoinPoint pjp, String target, String key, Object message) throws Throwable {
System.out.println(pjp + " -> wrong wrapper");
return pjp.proceed();
}
#Around("execution(* de.scrum_master.app.TestService.test(String, Object, SomeProcessor)) && args(target, message, processor)")
public Object traceTest(ProceedingJoinPoint pjp, String target, Object message, SomeProcessor processor)
throws Throwable {
System.out.println(pjp + " -> correct wrapper");
return pjp.proceed();
}
}
Console output when running the driver application:
START
execution(void de.scrum_master.app.TestService.test(String, Object, SomeProcessor)) -> correct wrapper
Correct method
END

Recursion in objects with Spring AOP

I've added AOP to my little SpringBoot app to log inputs and outputs of the all methods:
#Aspect
#Component
#AllArgsConstructor
public class MyAspect{
private final ObjectMapper mapper;
#Pointcut("within(com.mypackage..*)")
protected void all() {}
#Before("all()")
public void before(final JoinPoint joinPoint) {
final Class<?> classData = joinPoint.getTarget().getClass();
final MethodSignature signature = (MethodSignature) joinPoint.getSignature();
final Map<Object, Object> parameters = this.getParameters(joinPoint);
try {
log.info(
"Class {} Method{} Parameters: {}",
classData.getSimpleName(),
signature.getName(),
this.mapper.writeValueAsString(parameters)
);
}
catch (final JsonProcessingException e) {
log.error("Error in JSON mapper", e);
}
}
#AfterReturning(value = "all()", returning = "returning")
public void after(final JoinPoint joinPoint, final Object returning) {
final Class<?> classData = joinPoint.getTarget().getClass();
final MethodSignature signature = (MethodSignature) joinPoint.getSignature();
try {
log.info(
"Class {} Method {} Returning {}",
classData.getSimpleName(),
signature.getName(),
this.mapper.writeValueAsString(returning)
);
}
catch (final JsonProcessingException e) {
log.error("Error in JSON mapper", e);
}
}
}
I have had a recursion problem in mapper.writeValueAsString(...). When I have an object with an attribute that inside has the same parent object (JPA relationship for example) it enters an infinite loop when it tries to deserialize this tree of objects. I have solved this problem by adding #JsonIgnoreProperties(value = {"entityParent"}) in the child entity, so that it does not deserialize the parent object again.
My current problem is that there are objects from Spring, from the framework, that I cannot edit to add this JSON property and they are giving me the same problem when deserializing.
My question is, how can I globally ignore this recursion for all objects?

Spring Security - Process custom annotation in GenericFilterBean

in my controller I have a custom annotation like:
#GetMapping("/apikey")
#Secured(apiKeys = { ApiKey.APP_1}) // <- Custom annotation
public ResponseEntity startApiKey() {
return ResponseEntity.status(HttpStatus.OK).body("ApiKey approved");
}
In my Spring Security Config I have added a Filter for checking the apikey and authentication:
public class ApiKeyAuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter {
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
return request.getHeader(ApiKeyHeadername.DEFAULTHEADERNAME.getHeadername());
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return "N/A";
}
#Override
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
super.setAuthenticationManager(authentication -> {
String principal = (String) authentication.getPrincipal();
if (!ApiKey.APP_1.getApiKey().equals(principal))
{
throw new BadCredentialsException("The API key was not found or not the expected value.");
}
authentication.setAuthenticated(true);
return authentication;
});
}
}
Before the custom annotation was proccessed within a AspectJ class:
#Component
#Aspect
#Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class SecurityAspect {
#Autowired
private IPrincipal principal;
#Autowired
private AuthorizationManager authorizationManager;
#Pointcut("#annotation(my.demo.application.security.aspect.Secured)")
public void methodAnnotatedWithSecured() {
}
#Around("methodAnnotatedWithSecured()")
public Object userAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
Secured securedAnnotation = method.getAnnotation(Secured.class);
Authorized securityInformation = new Authorized(securedAnnotation.apiKeys(), securedAnnotation.roles(),
securedAnnotation.usernames());
if (authorizationManager.authorizeUserPrincipal(principal,
securityInformation) == AuthorizationState.UNAUTHORIZED) {
throw DefaultNotAuthorizedExceptionFactory.createNotAuthorizedException();
}
return joinPoint.proceed();
}
}
How can I process the annotation informations in the AbstractPreAuthenticatedProcessingFilter, or how can i get the annotation by Reflection in this Filter. Or can I inject something to get it?
Thank you in advice

Spring bean mocking and notice that called a method which annotated by #ExceptionHandler

I try to throw a specific exception when a method call via using doThrow. Then i expect to handle it by a method in its superclass which is annotated #ExceptionHandler.
1. Should i use Spy or Mock object for my controller class(it is a spring bean)
2. Should i use InjectMocks
3. Should i test within spring context because of ExceptionHandler class which is belonged to Spring
Here is my rough view of Controller class:
DefaultPageController extends SuperClass
{
#RequestMapping(method = RequestMethod.GET)
public String get(final Model model, final HttpServletRequest request, final HttpServletResponse response)
throws CMSItemNotFoundException
{....}
}
And ParentClass of my Controller(it is abstract)
public abstract class SuperClass
{...
#ExceptionHandler(InvalidCsrfTokenException.class)
public String handleInvalidCsrfTokenException(final InvalidCsrfTokenException exception, final HttpServletRequest request)
{
request.setAttribute("message", exception.getMessage());
LOG.error(exception.getMessage(), exception);
return FORWARD_PREFIX + "/404";
}
...
}
Finally my test class:
#IntegrationTest
//#RunWith(SpringJUnit4ClassRunner.class)
public class PageRedirectOnCSRFTest
{
#Mock
private Model model;
#Mock
private HttpServletRequest request;
#Mock
private HttpServletResponse response;
#Mock
private InvalidCsrfTokenException invalidCsrfTokenException;
#Mock
private MissingCsrfTokenException missingCsrfTokenException;
#InjectMocks
#Resource
private DefaultPageController controller;
#Before
public void setUp()
{
MockitoAnnotations.initMocks(this);
try
{
doThrow(invalidCsrfTokenException).when(controller).get(model, request, response);
}
catch (final Exception e)
{
}
}
//private final DefaultPageController controller = Mockito.spy(new DefaultPageController());
// private final InvalidCsrfTokenException invalidCsrfTokenException = new InvalidCsrfTokenException(
// Mockito.mock(CsrfToken.class), "1234");
// private final MissingCsrfTokenException missingCsrfTokenException = new MissingCsrfTokenException("1234");
#Test
public void testIfCalledHandleInvalidCsrfTokenException()
{
try
{
controller.get(model, request, response);
}
catch (final Exception e)
{
// YTODO Auto-generated catch block
Assert.assertTrue(e instanceof InvalidCsrfTokenException);
Mockito.verify(controller, Mockito.times(1)).handleInvalidCsrfTokenException(invalidCsrfTokenException, request);
}
}
}
Thx and brgs

Spring MVC Annotated Controller Interface with #PathVariable

Is there any reason not to map Controllers as interfaces?
In all the examples and questions I see surrounding controllers, all are concrete classes. Is there a reason for this? I would like to separate the request mappings from the implementation. I hit a wall though when I tried to get a #PathVariable as a parameter in my concrete class.
My Controller interface looks like this:
#Controller
#RequestMapping("/services/goal/")
public interface GoalService {
#RequestMapping("options/")
#ResponseBody
Map<String, Long> getGoals();
#RequestMapping(value = "{id}/", method = RequestMethod.DELETE)
#ResponseBody
void removeGoal(#PathVariable String id);
}
And the implementing class:
#Component
public class GoalServiceImpl implements GoalService {
/* init code */
public Map<String, Long> getGoals() {
/* method code */
return map;
}
public void removeGoal(String id) {
Goal goal = goalDao.findByPrimaryKey(Long.parseLong(id));
goalDao.remove(goal);
}
}
The getGoals() method works great; the removeGoal(String id) throws an exception
ExceptionHandlerExceptionResolver - Resolving exception from handler [public void
todo.webapp.controllers.services.GoalServiceImpl.removeGoal(java.lang.String)]:
org.springframework.web.bind.MissingServletRequestParameterException: Required
String parameter 'id' is not present
If I add the #PathVariable annotation to the concrete class everything works as expected, but why should i have to re-declare this in the concrete class? Shouldn't it be handled by whatever has the #Controller annotation?
Apparently, when a request pattern is mapped to a method via the #RequestMapping annotation, it is mapped to to the concrete method implementation. So a request that matches the declaration will invoke GoalServiceImpl.removeGoal() directly rather than the method that originally declared the #RequestMapping ie GoalService.removeGoal().
Since an annotation on an interface, interface method, or interface method parameter does not carry over to the implementation there is no way for Spring MVC to recognize this as a #PathVariable unless the implementing class declares it explicitly. Without it, any AOP advice that targets #PathVariable parameters will not be executed.
The feature of defining all bindings on interface actually got implement recently in Spring 5.1.5.
Please see this issue: https://github.com/spring-projects/spring-framework/issues/15682 - it was a struggle :)
Now you can actually do:
#RequestMapping("/random")
public interface RandomDataController {
#RequestMapping(value = "/{type}", method = RequestMethod.GET)
#ResponseBody
RandomData getRandomData(
#PathVariable(value = "type") RandomDataType type, #RequestParam(value = "size", required = false, defaultValue = "10") int size);
}
#Controller
public class RandomDataImpl implements RandomDataController {
#Autowired
private RandomGenerator randomGenerator;
#Override
public RandomData getPathParamRandomData(RandomDataType type, int size) {
return randomGenerator.generateRandomData(type, size);
}
}
You can even use this library: https://github.com/ggeorgovassilis/spring-rest-invoker
To get a client-proxy based on that interface, similarly to how RestEasys client framework works in the JAX-RS land.
It works in newer version of Spring.
import org.springframework.web.bind.annotation.RequestMapping;
public interface TestApi {
#RequestMapping("/test")
public String test();
}
Implement the interface in the Controller
#RestController
#Slf4j
public class TestApiController implements TestApi {
#Override
public String test() {
log.info("In Test");
return "Value";
}
}
It can be used as:
Rest client
Recently I had the same problem. Following has worked for me:
public class GoalServiceImpl implements GoalService {
...
public void removeGoal(#PathVariableString id) {
}
}
i resolved this problem.
ON CLIENT SIDE:
I'm using this library https://github.com/ggeorgovassilis/spring-rest-invoker/. This library generate a proxy from interface to invoke spring rest service.
I extended this library:
I created an annotations and a factory client class:
Identify a Spring Rest Service
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface SpringRestService {
String baseUri();
}
This class generates a client rest from interfaces
public class RestFactory implements BeanFactoryPostProcessor,EmbeddedValueResolverAware {
StringValueResolver resolver;
#Override
public void setEmbeddedValueResolver(StringValueResolver resolver) {
this.resolver = resolver;
}
private String basePackage = "com";
public void setBasePackage(String basePackage) {
this.basePackage = basePackage;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
createBeanProxy(beanFactory,SpringRestService.class);
createBeanProxy(beanFactory,JaxrsRestService.class);
}
private void createBeanProxy(ConfigurableListableBeanFactory beanFactory,Class<? extends Annotation> annotation) {
List<Class<Object>> classes;
try {
classes = AnnotationUtils.findAnnotatedClasses(basePackage, annotation);
} catch (Exception e) {
throw new BeanInstantiationException(annotation, e.getMessage(), e);
}
BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
for (Class<Object> classType : classes) {
Annotation typeService = classType.getAnnotation(annotation);
GenericBeanDefinition beanDef = new GenericBeanDefinition();
beanDef.setBeanClass(getQueryServiceFactory(classType, typeService));
ConstructorArgumentValues cav = new ConstructorArgumentValues();
cav.addIndexedArgumentValue(0, classType);
cav.addIndexedArgumentValue(1, baseUri(classType,typeService));
beanDef.setConstructorArgumentValues(cav);
registry.registerBeanDefinition(classType.getName() + "Proxy", beanDef);
}
}
private String baseUri(Class<Object> c,Annotation typeService){
String baseUri = null;
if(typeService instanceof SpringRestService){
baseUri = ((SpringRestService)typeService).baseUri();
}else if(typeService instanceof JaxrsRestService){
baseUri = ((JaxrsRestService)typeService).baseUri();
}
if(baseUri!=null && !baseUri.isEmpty()){
return baseUri = resolver.resolveStringValue(baseUri);
}else{
throw new IllegalStateException("Impossibile individuare una baseUri per l'interface :"+c);
}
}
private static Class<? extends FactoryBean<?>> getQueryServiceFactory(Class<Object> c,Annotation typeService){
if(typeService instanceof SpringRestService){
return it.eng.rete2i.springjsonmapper.spring.SpringRestInvokerProxyFactoryBean.class;
}else if(typeService instanceof JaxrsRestService){
return it.eng.rete2i.springjsonmapper.jaxrs.JaxRsInvokerProxyFactoryBean.class;
}
throw new IllegalStateException("Impossibile individuare una classe per l'interface :"+c);
}
}
I configure my factory:
<bean class="it.eng.rete2i.springjsonmapper.factory.RestFactory">
<property name="basePackage" value="it.giancarlo.rest.services" />
</bean>
ON REST SERVICE SIGNATURE
this is an example interface:
package it.giancarlo.rest.services.spring;
import ...
#SpringRestService(baseUri="${bookservice.url}")
public interface BookService{
#Override
#RequestMapping("/volumes")
QueryResult findBooksByTitle(#RequestParam("q") String q);
#Override
#RequestMapping("/volumes/{id}")
Item findBookById(#PathVariable("id") String id);
}
ON REST SERVICE IMPLEMENTATION
Service implementation
#RestController
#RequestMapping("bookService")
public class BookServiceImpl implements BookService {
#Override
public QueryResult findBooksByTitle(String q) {
// TODO Auto-generated method stub
return null;
}
#Override
public Item findBookById(String id) {
// TODO Auto-generated method stub
return null;
}
}
To resolve annotation on parameters I create a custom RequestMappingHandlerMapping that looks all interfaces annotated with #SpringRestService
public class RestServiceRequestMappingHandlerMapping extends RequestMappingHandlerMapping{
public HandlerMethod testCreateHandlerMethod(Object handler, Method method){
return createHandlerMethod(handler, method);
}
#Override
protected HandlerMethod createHandlerMethod(Object handler, Method method) {
HandlerMethod handlerMethod;
if (handler instanceof String) {
String beanName = (String) handler;
handlerMethod = new RestServiceHandlerMethod(beanName,getApplicationContext().getAutowireCapableBeanFactory(), method);
}
else {
handlerMethod = new RestServiceHandlerMethod(handler, method);
}
return handlerMethod;
}
public static class RestServiceHandlerMethod extends HandlerMethod{
private Method interfaceMethod;
public RestServiceHandlerMethod(Object bean, Method method) {
super(bean,method);
changeType();
}
public RestServiceHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
super(bean,methodName,parameterTypes);
changeType();
}
public RestServiceHandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
super(beanName,beanFactory,method);
changeType();
}
private void changeType(){
for(Class<?> clazz : getMethod().getDeclaringClass().getInterfaces()){
if(clazz.isAnnotationPresent(SpringRestService.class)){
try{
interfaceMethod = clazz.getMethod(getMethod().getName(), getMethod().getParameterTypes());
break;
}catch(NoSuchMethodException e){
}
}
}
MethodParameter[] params = super.getMethodParameters();
for(int i=0;i<params.length;i++){
params[i] = new RestServiceMethodParameter(params[i]);
}
}
private class RestServiceMethodParameter extends MethodParameter{
private volatile Annotation[] parameterAnnotations;
public RestServiceMethodParameter(MethodParameter methodParameter){
super(methodParameter);
}
#Override
public Annotation[] getParameterAnnotations() {
if (this.parameterAnnotations == null){
if(RestServiceHandlerMethod.this.interfaceMethod!=null) {
Annotation[][] annotationArray = RestServiceHandlerMethod.this.interfaceMethod.getParameterAnnotations();
if (this.getParameterIndex() >= 0 && this.getParameterIndex() < annotationArray.length) {
this.parameterAnnotations = annotationArray[this.getParameterIndex()];
}
else {
this.parameterAnnotations = new Annotation[0];
}
}else{
this.parameterAnnotations = super.getParameterAnnotations();
}
}
return this.parameterAnnotations;
}
}
}
}
I created a configuration class
#Configuration
public class WebConfig extends WebMvcConfigurationSupport{
#Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RestServiceRequestMappingHandlerMapping handlerMapping = new RestServiceRequestMappingHandlerMapping();
handlerMapping.setOrder(0);
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
PathMatchConfigurer configurer = getPathMatchConfigurer();
if (configurer.isUseSuffixPatternMatch() != null) {
handlerMapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch());
}
if (configurer.isUseRegisteredSuffixPatternMatch() != null) {
handlerMapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch());
}
if (configurer.isUseTrailingSlashMatch() != null) {
handlerMapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
}
if (configurer.getPathMatcher() != null) {
handlerMapping.setPathMatcher(configurer.getPathMatcher());
}
if (configurer.getUrlPathHelper() != null) {
handlerMapping.setUrlPathHelper(configurer.getUrlPathHelper());
}
return handlerMapping;
}
}
and I configurated it
<bean class="....WebConfig" />

Resources