I am new at Spring AOP. I try to write advice for ResponseEntityExceptionHandler.handleException method to logging exception info. After hours of searching for solutions, I'm stuck.
This is my Apect component
#Log4j2
#Aspect
#Component
public class LogginAspect {
#Pointcut(value = "execution(* org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler.handleException(..)) && args(ex, request)")
public void callSpringExceptionHandler() {}
#Before("callSpringExceptionHandler()")
public void logBeforeError(JoinPoint joinPoint, Exception ex, WebRequest request) {
log.error(ex.getMessage(), ex);
}
}
I have tried different patterns of pointcut but with no luck.
My advice logBeforeError does not ever called at all. Please help me with my problem
From the Spring documentation :5.8. Proxying Mechanisms
If the target object to be proxied implements at least one interface,
a JDK dynamic proxy is used. All of the interfaces implemented by the
target type are proxied. If the target object does not implement any
interfaces, a CGLIB proxy is created.
and
With CGLIB, final methods cannot be advised, as they cannot be
overridden in runtime-generated subclasses.
ResponseEntityExceptionHandler is an abstract class which does not implement any interface and ResponseEntityExceptionHandler.handleException() is a final method. In short Spring AOP will not be able to advice that method execution.
You will be able to achieve advicing a final method using AspectJ though. Please go through the detailed answer from #kriegaex
Related
I'm trying to call a #Cacheable method from within the same class.
And it didn't work. Because of:
In proxy mode (the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object that calls another method of the target object) does not lead to actual caching at runtime even if the invoked method is marked with #Cacheable. Consider using the aspectj mode in this case. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, #PostConstruct).
It means, #Cachable(also #Transactional) works by proxy classes which is Spring AOP in. a internal call in the same class make call by 'this' instead of proxy classes.
To solve the problem, I should call a method by proxy or using AspectJ(another AOP).
So, I found 4 solutions.
What is your choice? and why others are not recommended?
Please, share your opinion!
using AspectJ (another AOP)
get the Bean from ApplicationContext and use it
#Service
public class UserService implements Service {
#Autowired
private ApplicationContext applicationContext;
private Service self;
#PostConstruct
private void init() {
self = applicationContext.getBean(UserService.class);
}
}
self-autowiring using #Resource //since Spring 4.3
#Component
#CacheConfig(cacheNames = "SphereClientFactoryCache")
public class CacheableSphereClientFactoryImpl implements SphereClientFactory {
/**
* 1. Self-autowired reference to proxified bean of this class.
*/
#Resource
private SphereClientFactory self;
#Override
#Cacheable(sync = true)
public SphereClient createSphereClient(#Nonnull TenantConfig tenantConfig) {
// 2. call cached method using self-bean
return self.createSphereClient(tenantConfig.getSphereClientConfig());
}
#Override
#Cacheable(sync = true)
public SphereClient createSphereClient(#Nonnull SphereClientConfig clientConfig) {
return CtpClientConfigurationUtils.createSphereClient(clientConfig);
}
}
make the Bean scope of the class as 'prototype' instead of 'singleton'
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class AService {
private final AService _aService;
#Autowired
public AService(AService aService) {
_aService = aService;
}
#Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = _aService.getEmployeeData(date);
...
}
}
I'm a newbie in spring :)
Actually, I choose the 4th solution, but I felt it isn't a good way. because I just need to call the caching method by proxy, and it make several beans to achieve it.
After reading articles, I think AspectJ is the best choice. It looks cool, Spring recommends it, and many people also recommend too.
But I don't understand how to AspectJ works (I will study) and I also don't know why others is not recommended.
references
Spring Cache #Cacheable - not working while calling from another method of the same bean
Spring cache #Cacheable method ignored when called from within the same class
https://spring.io/blog/2012/05/23/transactions-caching-and-aop-understanding-proxy-usage-in-spring
https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#cache
I have several controller functions separated by role, and instead of doing role validation in each controller method, I found that it seems to be able to get done by using Aspect, however something isn't right in my implementation as the code in Aspect never runs
Annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ForMerchantOnly {}
Aspect:
#Aspect
#Configuration
public class ForMerchantOnlyAspect {
private static final Logger logger = LogManager.getLogger(ForMerchantOnlyAspect.class);
#Before("#annotation(com.example.api.annotation.ForMerchantOnly) && args(request)")
public void before(HttpServletRequest request) throws ServiceException {
if (!(request instanceof HttpServletRequest)) {
throw new RuntimeException("request should be HttpServletRequesttype");
}
String domain = request.getServerName();
System.out.println("Aspect showing domain " + domain);
// -- other code
}
}
Controller
#ForMerchantOnly
#GetMapping("/list")
public ResponseEntity<ApiResp> list() {
System.out.println("Show something");
return ResponseEntity.ok().body();
}
I'm assuming when i call controller /list method via chrome browser, it would hit the code in ForMerchantOnlyAspect but it just went into the controller method directly. Am I missing something?
The Aspect was not working as it could not find a matching joinpoint . There are no controller methods that has annotation #ForMerchantOnly and has an argument of type HttpServletRequest
From the documentation :
args: Limits matching to join points (the execution of methods when
using Spring AOP) where the arguments are instances of the given
types.
Following aspect may be used for the requirement . Scoping designator within will set the scope to advice.
#Before("#annotation(com.example.api.annotation.ForMerchantOnly) && within(com.example.api..*)")
public void before() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
.getRequest();
System.out.println("Aspect showing domain " + request.getServerName());
}
Also note that an Aspect is better annotated with #Component and #Configuration be used for configurations.
You may also have a look at Method Security of Spring security framework , which lets to secure a method with annotations.
From the documentation
From version 2.0 onwards Spring Security has improved support
substantially for adding security to your service layer methods. It
provides support for JSR-250 annotation security as well as the
frameworkâs original #Secured annotation. From 3.0 you can also make
use of new expression-based annotations. You can apply security to a
single bean, using the intercept-methods element to decorate the bean
declaration, or you can secure multiple beans across the entire
service layer using the AspectJ style pointcuts.
I tried to use PointCut to perform some post action after ModelAndView.setViewName, but it seems that it never triggers:
#Aspect
#Component
public class TestAspect {
private Logger logger = LoggerFactory.getLogger(this.getClass());
#Pointcut("execution(* org.springframework.web.servlet.ModelAndView.*(..))")
public void testPointCut() {
}
#After("testPointCut()")
public void afterPointCut(JoinPoint joinPoint) {
logger.debug("afterPointCut");
}
}
If I change the execution part to some class of my own, this point cut works.
So what is the correct way to add PointCut to ModelAndView?
I am not a Spring user, but what I know about Spring AOP is that you can only apply it to Spring components. The class ModelAndView is not derived from any Spring core component class or annotated by anything making it such, it is a simple POJO. As such you cannot target it by Spring AOP pointcuts. You should rather target something within the reach of Spring AOP.
The alternative would be to unpack the big gun and use full AspectJ LTW (load-time weaving) which is not limited to Spring components.
I want to write common code which should be execute before every method,
Where can I place this code in spring.
Thanks in advance.
What you ask is not trivial but Aspect Oriented Programming (AoP) is one way to achieve that. This description assumes that you are somewhat familiar with the Proxy class, the InvocationHandler interface and the Interceptor pattern in general. As I said, not a totally trivial matter.
Define the logic that you what to be executed before every method, or some method or whatever. Usually it is some kind of Interceptor, this is an example:
public class TimeProfilerInterceptor implements MethodInterceptor {
#Getter
private final TimeStatistics statistics = new TimeStatistics();
#Override
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch watch = new StopWatch();
try {
watch.start();
Object returnValue = invocation.proceed();
return returnValue;
}
finally {
// etc...
}
}
}
Define a place where your logic is wired to your methods. In this example, the place is a Spring component that extends AbstractBeanFactoryAwareAdvisingPostProcessor and implements InitializingBean. The afterPropertiesSet method is called by Spring once the initialization of the bean is done. The method uses the Advice class from spring-aop to identify the pointcuts i.e. the methods that must be wrapped by the interceptor. In this case, it is an annotation based pointcut, meaning that it matches every method that has a certain custom annotation on it (TimeProfiled).
#Component
public class TimeProfiledAnnotationPostProcessor
extends AbstractBeanFactoryAwareAdvisingPostProcessor
implements InitializingBean {
#Autowired
TimeProfilerInterceptor timeProfilerInterceptor;
#Override
public void afterPropertiesSet() throws Exception {
this.setProxyTargetClass(true);
Advice advice = timeProfilerInterceptor;
Pointcut pointcut = new AnnotationMatchingPointcut(null, TimeProfiled.class);
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
}
Define your custom annotation to use where needed.
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface TimeProfiled {
}
Tell Spring to initiate the wrapping mechanism at startup via the following annotation upon a Spring Configuration or a SpringBootApplication:
#ComponentScan(basePackageClasses = TimeProfiledAnnotationPostProcessor.class)
#EnableAspectJAutoProxy(proxyTargetClass = true)
You can change the pointcut so that it matches other methods with other criteria, there is an entire syntax to do that, a world in itself, this is just a small example...
You should have a look at Spring AOP. With Spring AOP you can write Aspects which can be common code which is executed before/after a method. The following example is a simple Aspect:
#Aspect
public class EmployeeAspect {
#Before("execution(public String getName())")
public void getNameAdvice(){
System.out.println("Executing Advice on getName()");
}
#Before("execution(* your.package.name.*.get*())")
public void getAllAdvice(){
System.out.println("Service method getter called");
}
}
Within the #Before() annotation you can specify the exact method which is surrounded with the Aspect or you use the wildcard * to specify more methods. For this, you should be familiar with Pointcut expressions.
I try to use aspectj for checking whether the layers of the architecture are broken, e.g. the access from controller to repository without using a service between
Now this is marking every method call to a autowired annotated within de.fhb.controller.
but how can i then limit that to Repositories for example?
The package structure looks like this:
de.fhb.controller
de.fhb.service
de.fhb.entity and and so on.
my aspect looks like this
#Aspect
public class Layer {
#DeclareError("within(de.fhb.controller..*) && #annotation(org.springframework.beans.factory.annotation.Autowired) " )
private static final String typeWarning = "You try to access through many layers";
}
the repository:
#Repository
public interface BoxRepository extends JpaRepository<Box, Long>{
public List findByPrio(long prio);
}
and the controller that should be checked by aspects:
#RequestMapping("/box")
#Controller
public class BoxController {
#Autowired
private BoxRepository boxRepository; // negative example
#Autowired
private BoxService boxService;
#RequestMapping(value = "/")
public String list(Model model) {
List boxes = boxRepository.findAll(); // negativ example
model.addAttribute("boxes", boxes);
return "box/list";
}
for further look: repository at github
Your pointcut
within(de.fhb.controller..*) &&
execution(de.fhb.repository.BoxRepository *(..))
means in prose: Intercept any method in package de.fhb.controller or its sub-packages which has a return type of de.fhb.repository.BoxRepository, but in your code is no such method. So you will never see a compiler error.
;-)
Updated answer after question update:
You need to intercept calls to methods of types with a #Repository annotation:
within(de.fhb.controller..*) &&
call(* (#org.springframework.stereotype.Repository *).*(..))
But in order to do that you need to switch from Spring AOP to full-blown AspectJ with compile-time weaving, i.e. you need to use the AspectJ compiler Ajc to compile and weave your aspect. The reason is that Spring AOP does not support the call() pointcut, but this is what you need. An execution() pointcut will not help you here, but that is the only one supported by a proxy-based "AOP lite" framework like Spring AOP. You just need to weave this one aspect with AspectJ, the other aspects can still use Spring AOP. But then need to take care about AspectJ not picking up your other aspects.