Spring AOP: exclude avoid final classes and enums from pointcut - spring

I am using try to implement Logging using Spring AOP. I have defined the
#Pointcut("execution(* com.mycom..*(..))")
private void framework() {}
#Around("framework()")
public Object aroundAdviceFramework(ProceedingJoinPoint jp) throws Throwable {
if (logger.isDebugEnabled())
logger.debug("DEBUG:: {} {} Enter", jp.getTarget().getClass().getName(), jp.getSignature().getName());
Object returnVal = jp.proceed(jp.getArgs());
if (logger.isDebugEnabled())
logger.debug("DEBUG:: {} {} Out", jp.getTarget().getClass().getName(), jp.getSignature().getName());
logger.info("INFO:: " + jp.getTarget().getClass().getName() + " " + jp.getSignature().getName() + " Finished:");
return returnVal;
}
There are lot of classes under mycom package and its subpackages. Some of the classes are enum and final class.
Because of this I am getting
nested exception is org.springframework.aop.framework.AopConfigException:
Could not generate CGLIB subclass of class [class com.mycom.util.BancsServiceProvider]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class com.mycom.util.BancsServiceProvider
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:529)
Is there a way to exclude all the final classes and enum classes from logging using some kind of regular expression.
Note:I have enum classes all over in different packages. It would be difficult to exclude them using complete class names.
Update 2014-11-17 (trying kriegaex's solution):
I tried using
#Pointcut("!within(is(FinalType))")
but I am getting following error
Pointcut is not well-formed: expecting ')' at character position 10
!within(is(FinalType))
I have added this maven dependency in the pom file
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.4</version>
</dependency>
I have also added this maven dependency
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
Now, everything is working like charm. Any ideas, whats happening here?

Currently you can exclude enums, aspects, interfaces, inner types, anonymous types via is() pointcut syntax which was introduced in AspectJ 1.6.9, see also my answer here.
What you cannot do at the moment is exclude final types via AspectJ syntax. But I think it would make sense, so I created a ticket for it.
How to exclude enums:
#Pointcut("execution(* com.mycom..*(..)) && !within(is(EnumType))")
Update: AspectJ 1.8.4 has been released, see also overview in the official download section. On Maven Central the download is not available yet, but it will be soon, I guess. When available, this link will be valid, currently it yields a 404 error.
So why is this release interesting? Because the ticket mentioned above has been resolved and there is a new pointcut primitive is(FinalType) available as of now, see 1.8.4 release notes.
So now the full solution you requested looks like this:
#Pointcut(
"execution(* com.mycom..*(..)) && " +
"!within(is(EnumType)) && " +
"!within(is(FinalType))"
)
I verified that it works like this.

Related

pjp.getArgs() is only capturing 1 argument

I am using ProceedingJoinPoint in my springboot application to capture arguments passed to log.info. I am using aspectjweaver-1.9.7. I have added the following dependencies in my maven pom.xml
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
I have created a library which is basically a wrapper around slf4j logger. So whenever log.info or log.debug is called, I have written an #Aspect class called LoggerAspect , in which I have a #Around Method which will do the required work.
Here is the #Around Method
#Around("call(* org.slf4j.Logger.info(..))")
public void injectLogConfigInfo(ProceedingJoinPoint pjp) throws Throwable {
System.out.println(pjp.getArgs().length);
Object[] args = logMod(pjp.getArgs());
pjp.proceed(args);
MDC.clear();
}
Now, when i am implementing the logger in my application , i am facing the following issue:
Example: log.info("This is test of info", SomeObject);
when i am passing this, pjp is only picking up the first String but it is ignoring the object. I had read that it returns all the arguments as an object[] array. The method logmod(pjp.getArgs()) does some processing on the object array which pjp returns. I am not that proficient in posting questions so do forgive me for missing out on details.
Due to restrictions at my org, i cannot post the whole code but i have included the required bits. Now I know that when debugging, it is beneficial to view the whole picture but I do not have that priviledge. So even if you do not have the exact answer, if you can share your experience whether you have faced this issue and what you did to resolve it?
Thanks

Argument passed to when() is not a mock! exception thrown with Spring Boot project which doesn't have #SpringBootApplication/main class

My project is a simple spring boot application which doesn't have a main/#SpringBootApplication class. It is used as a dependency library for other modules. I am trying to write the unit tests for the classes present in this project like below and getting the below pasted error. Any quick help is much appreciated.
pom dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- exclude junit 4 -->
<exclusions>
<exclusion>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- junit 5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
As this project doesn't have main class, to get the spring application context using below configuration class.
#Configuration
public class TestServiceConfig {
#Bean
public TestService productService() {
return Mockito.mock(TestService.class);
}
#Bean
public MongoDriverService productMongo() {
return Mockito.mock(MongoDriverService.class);
}
}
Below is my test class which is throwing exception. Actual java class has a method called getPlanCode(which takes 6 arguments) and returns void. In this method mongo object is used for connecting the db so that I used #InjectMocks on service object.
public class ValidationServiceTest {
#Mock
MongoDriverService mongo;
#InjectMocks
TestService service;
#Test
#DisplayName("Test Get Plan Code positive")
public void getPlanCodeTest() {
doNothing().when(service).getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
service.getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
verify(service, times(1)).getPlanCode(anyString(), anyString(), any(Batch.class), any(BatchFile.class), any(Document.class), anyString());
}
}
Below is the exception
12:51:33.829 [main] DEBUG org.springframework.test.context.support.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext#45b4c3a9 testClass = DefaultMedicareBFTAccumsValidationServiceTest, testInstance = com.anthem.rxsmart.service.standalone.batchvalidation.DefaultMedicareBFTAccumsValidationServiceTest#14dda234, testMethod = getPlanCodeTest#DValidationServiceTest, testException = org.mockito.exceptions.misusing.NotAMockException:
Argument passed to when() is not a mock!
Example of correct stubbing:
service is not a mock since you are using #InjectMocks ( assume you are using #RunWith(MockitoRunner.class) or #ExtendWith but you are hiding that for whatever reasons).
What #InjectMocks does, is create of a new instance of TestService and literally inject mocks into it (mocked required dependencies). So service is a real thing, not a mock
IMO this test makes not sense as you are suppose to test your implementation of singular entity contract, not to test mocks...
Your test case and assertions are pointless as it is like "call method A and check if I just called method A" while you should check and validate eg return value of a call, or if some methods of mocks have been called eg if Mongo was queried with proper arguments. I just hope it is a really bad example, not real test scenario
Also test setup is wrong as you show us that you want to use #Configuration class with #Bean but then you are using #Mock in the test which will create brand new mocks for you. In other words - that config is not used at all
Posting this answer just for the developers who are in same understanding state.
#Test
#DisplayName("Test Get Plan Code positive")
public void getPlanCodeTest() {
service = new ValidationService(mongo);
Mockito.when(mongo.aggregateIterable("test", pipeline)).thenReturn(tierFilterDocs);
service.getPlanCode("", "", null, batchFile, null, "");
verify(mongo, times(1)).aggregateIterable("test", pipeline);
}
I have updated my test case so it solves the purpose now. Now no need of the Configuration file as I am mocking the object in test class itself.

Can't use the added function in Repository

I'm using Spring Data Neo4j 2.4.4 for my project. This is some in project:
User:
UserRepository:
I can still use the built-in functions in Repository such as save(), findAll(),... but when I add and use some functions, for example "existsByUsername", it has error:
11:53:26.562 [http-nio-8081-exec-1] WARN o.s.d.n.c.Neo4jPersistenceExceptionTranslator - Don't know how to translate exception of type class org.neo4j.driver.exceptions.NoSuchRecordException
Then, I try to add query for function, it is still
Could you help me to determine this error and give me a solution? Thank you!\
Updated:
I call API in Postman, I received this result while my DB has only 1 user:
{
"error": "Records with more than one value cannot be converted without a mapper.; nested exception is java.lang.IllegalArgumentException: Records with more than one value cannot be converted without a mapper."
}
As your exception states, no record is returned from Neo4j and thus it cannot be mapped to a Boolean.
Best would be to use Optional<User> and check with isPresent()
#Query("MATCH (n:User {username: $username}) RETURN n")
Optional<User> existsForUsername(String username);
That said, it is already handled by Spring Data without using a custom query :
boolean existsByUsername(String username);
Reference : https://docs.spring.io/spring-data/neo4j/docs/current/reference/html/#appendix.query.method.subject
There is a rather obscure set of references on github site for the NEO4J Spring Data: here
It seems that the existsBy property was omitted. It is being fixed, but whether that has made it to the Spring Data repository is another matter. You don;t say which version of the Spring-boot-starter-neo4j you are using, but you may care to use this one and see if it works;
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-neo4j -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-neo4j</artifactId>
<version>2.4.4</version>
</dependency>
In general, the other answers here are right. But, if you're using Spring Boot 2.4.4, OGM is no longer supported. It now uses a model called SDN (or at least it was when it was under development). You may just have a dependency issue. Here's part of my Gradle build file, these should be all the dependencies you need:
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
plugins {
application
id("org.springframework.boot") version "2.4.4"
id("io.spring.dependency-management") version "1.0.10.RELEASE"
}
java.sourceCompatibility = JavaVersion.VERSION_11
configurations {
compileOnly {
extendsFrom(configurations.annotationProcessor.get())
}
}
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springframework.boot:spring-boot-starter-data-neo4j")
implementation("org.springframework.boot:spring-boot-starter-security")
implementation("org.springframework.boot:spring-boot-starter-web")
}

Init WebApplicationContext in unit test

I have a utility method that I wanted to write a unit test for. Since this utility runs under a webapp, it is designed to get a Spring bean from the WebApplicationContext.(follow code is not under unit test)
action bean class
private IUnitBiz unitBiz;
public UnitBean()
{
unitBiz = CommonUtils.getBean(IUnitBiz.class);
}
in CommonUtils.class
public static ApplicationContext getWebApplicationContext() {
return FacesContextUtils.getWebApplicationContext(FacesContext.getCurrentInstance());
}
public static <T> T getBean(Class<T> beanType) {
return getWebApplicationContext().getBean(beanType);
}
------------------in unit test----------------
In unit test it is return null, how can i init WebApplicationContext or getBean for my unit test?
when i new action bean, getBean method is return null.
EasyMock could be a solution.
Example:
WebApplicationContext mockWebApplicationContext = EasyMock.createMock(WebApplicationContext.class);
MockServletContext mockServletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,
mockWebApplicationContext);
EasyMock.expect(mockWebApplicationContext.getServletContext()).andReturn(mockServletContext).anyTimes();
You could make your test case extends org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests.
By extending this class, you can access an instance of the org.springframework.context.ApplicationContext, which can be used and passed along your utility methods...
Actually, I don't know if you're using but you should try spring-test, it's the best way to use Spring in JUnit tests and it's framework independent (it can be used with Wicket, JSF, etc.).
You should start by including the dependency below in your Maven pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.0.0.RELEASE</version>
<scope>test</scope>
</dependency>

Using JSR-303 with custom validation

Dear Spring community,
What I am trying to implement is the following:
I would like to have a custom validator per controller (via #InitBinder)
I would like Spring to call validator.validate() (so not this way)
I would like to use JSR-303 #Valid annotation for that
The bean to be validated (RegistrationForm) does not have any per-field JSR-303 annotations
I don't want to include validation implementation (like Hibernate) into classpath; it will be useless as from above statement
I basically follow the steps mentioned here:
I add javax.validation.validation-api:validation-api as my dependency
I use <mvc:annotation-driven />
I mark my model with #Valid:
public String onRegistrationFormSubmitted(#ModelAttribute("registrationForm") #Valid RegistrationForm registrationForm, BindingResult result) ...
So what happens, is that validation API tries to locate any implementation and fails:
Caused by: javax.validation.ValidationException: Unable to find a default provider
at javax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:264)
at org.springframework.validation.beanvalidation.LocalValidatorFactoryBean.afterPropertiesSet(LocalValidatorFactoryBean.java:183)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1477)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1417)
The way out is to define a validator property for AnnotationDrivenBeanDefinitionParser:
<bean name="validator" class="org.company.module.RegistrationFormValidator" />
<mvc:annotation-driven validator="validator" />
but this approach means that the validator will be set to all controllers by ConfigurableWebBindingInitializer.initBinder().
I understand that I am trying to use the framework in a special way, but what the community will say, if there is a special meaning for validator property which tells that validator does not need to be resolved, e.g.
<mvc:annotation-driven validator="manual" />
with special treatment:
--- AnnotationDrivenBeanDefinitionParser.java.orig 2011-06-30 14:33:10.287577300 +0200
+++ AnnotationDrivenBeanDefinitionParser.java 2011-06-30 14:34:27.897449000 +0200
## -152,6 +152,10 ##
private RuntimeBeanReference getValidator(Element element, Object source, ParserContext parserContext) {
if (element.hasAttribute("validator")) {
+ if ("manual".equals(element.getAttribute("validator"))) {
+ return null;
+ }
+
return new RuntimeBeanReference(element.getAttribute("validator"));
}
else if (jsr303Present) {
Any feedback is welcomed.
P.S. Repost from Spring Forum.
This is also a repost of my answer/workaround on the above mentioned forum. Anyway I think it might help having it here as well.
The only workaround I found was to implement my own #Valid annotation, once Spring (at least in 3.1.1.RELEASE code base) only checks the method argument annotation's simple name (please look into the org.springframework.web.method.annotation.ModelAttributeMethodProcessor class below). This way, I don't need to add javax.validation.validation-api:validation-api to my project's dependencies and I stop getting the infamous javax.validation.ValidationException: Unable to find a default provider.
/**
* Validate the model attribute if applicable.
* <p>The default implementation checks for {#code #javax.validation.Valid}.
* #param binder the DataBinder to be used
* #param parameter the method parameter
*/
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) {
Annotation[] annotations = parameter.getParameterAnnotations();
for (Annotation annot : annotations) {
if (annot.annotationType().getSimpleName().startsWith("Valid")) {
Object hints = AnnotationUtils.getValue(annot);
binder.validate(hints instanceof Object[] ? (Object[]) hints : new Object[] {hints});
}
}
}
Why you don't want to include Hibernate Validator?
Ever JSR specification have some implementations, you can not work with the specification without any implementation (or provider).
Can you imagine working with JDBC without any JDBC driver? Working with JPA without a provider? Working with Servlets without any container?
It's just the same, Hibernate Validator is the reference implementation of JSR-303, I'm not aware of any other implementation, but if you don't like Hibernate Validator, just go for another implementation.
You wrote:
I add javax.validation.validation-api:validation-api as my dependency ...
Caused by: javax.validation.ValidationException: Unable to find a default provider
You will also need an implemenation of that api. For example Hibernate Validator, it is the default implmentation. (has nothing to do with the ORM Hibernate)
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.2.0.Final</version>
</dependency>

Resources