I'm having an issue with compilation ordering in a mixed Java/Groovy environment. We're using Gradle 2.1, JDK 7, and Groovy 2.3. The code compiles fine in STS (Spring Tool Suite), using the Gradle plugin and the same build.gradle files, but fails when the build is run on the command line. STS is configured to use the Groovy Eclipse plugin, which if I understand things correctly, uses its own compiler. So I think this problem stems from a compilation ordering problem when we use the Groovy compiler from Gradle's Groovy plugin. This is the Groovy class:
#Component
#ToString(includeNames = true, includePackage = false)
class ManagedCloseableHttpClientFactory implements ClientHttpRequestFactory {
#Delegate
HttpComponentsClientHttpRequestFactory factory
...
}
The ClientHttpRequestFactory is a Spring interface that is implemented by the Spring class HttpComponentsClientHttpRequestFactory. Somewhere else in the system, we have a Java class annotated with #Configuration, where the ManagedCloseableHttpClientFactory is injected using #Autowired. Like this:
#Configuration
public class FooConfiguration {
#Autowired
private ManagedCloseableHttpClientFactory httpClientFactory;
...
}
When the build is run from the command line, we get the following error message: /Users/xyz/source/prj/common/build/tmp/compileGroovy/groovy-java-stubs/common/web/client/ManagedCloseableHttpClientFactory.java:10: error: ManagedCloseableHttpClientFactory is not abstract and does not override abstract method createRequest(URI,HttpMethod) in ClientHttpRequestFactory. If we move the field marked with #Autowired to a Groovy class that is annotated with #Configuration, everything works, but not when it's declared inside a Java class. I'm guessing that this is a compilation ordering issue. In our Gradle files, we're using the groovy plugin, and have modified the source directories as follows:
project.sourceSets.main.java.srcDirs = []
project.sourceSets.test.java.srcDirs = []
project.sourceSets.main.groovy.srcDirs = ["src/main/java", "src/main/groovy"]
project.sourceSets.main.resources.srcDirs += ["config"]
project.sourceSets.test.groovy.srcDirs += ["src/test/java","src/test/groovy"]
What's the best approach here? Thanks.
The Groovy compiler's stub generator has some limitations. My best guess is that you can't have Java call a Groovy method materialized by #Delegate. I'd try to get rid of this particular Java->Groovy dependency or this particular usage of #Delegate (i.e. implement the delegation by hand).
If possible inject the interface instead of the concrete class. Since the injection happens on run time the class will be full created then, and in compile time compiler will recognize the interface as having all the required fields.
#Configuration
public class FooConfiguration {
#Autowired
private ClientHttpRequestFactory httpClientFactory;
...
}
Related
I have a Spring Boot + Kotlin + Gradle project. I'd like to create a small library for my use-cases. This library should use AOP to remove some cross cutting concerns I observed.
Therefore I started adding these two dependencies to my gradle build file.
build.gradle.kts
implementation("org.springframework:spring-aop:5.2.9.RELEASE")
implementation("org.springframework:spring-aspects:5.2.9.RELEASE")
I also added the freefair aspectj plugin due some suggestions from the interwebs.
The following aspect I created should be placed in src/main/aspectj according to this documentation: https://docs.freefair.io/gradle-plugins/5.2.1/reference/#_io_freefair_aspectj
This plugin adds AspectJ support to the project, by adding a aspectj directory to every source set.
Source contained in the src/main/aspectj directory will be compiled with ajc by the compileAspectj task.
plugins {
// ...
id("io.freefair.aspectj") version "5.2.1"
// ...
}
I then started to create my first aspect that matches on every method which is annotated with #Foozy
src/main/aspectj/FoozyAspect.kt < the 'special' source path
#Component
#Aspect
class FoozyAspect {
#Before("#annotation(com.client.annotation.Foozy)")
fun doStuff() {
LOG.info("Do Stuff")
}
companion object {
private val LOG = LoggerFactory.getLogger(FoozyAspect::class.java)
}
}
Then I created this annotation
src/main/kotlin/com.client.annotation/Foozy.kt
#Target(AnnotationTarget.FUNCTION)
annotation class Foozy
Now to test if everything works as expected I created a unit test
src/test/kotlin/FoozyAspectTest.kt
#SpringBootTest
#EnableAspectJAutoProxy
internal class FoozyAspectTest {
private val testCandidate: TestCandidate = TestCandidate()
#Test
fun `should work with aspect`() {
testCandidate.doStuff()
}
}
src/test/TestCandidate.kt
class TestCandidate {
#Foozy
fun doStuff(): String {
return "stuff"
}
}
Result
Executing the text in debug mode does not yield the awaited info log Do Stuff and also does not cease the thread at the breakpoint in the FoozyAspect.kt doStuff() method.
I have no idea what to configure here.
For good reason I kinda have the suspicion that I am mixing up different "ways" to get this to work or am just missing some final steps in preconfiguration/prerequisites.
The AspectJ compiler can't compile Kotlin source code. Your .kt file in src/main/aspectj will be completely ignored.
You have different options depending on what you really want to do:
Do you want your Aspect to be woven by ajc at compile-time, or do you just want to use "plain" Spring AOP?
The differences are explained here: https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-choosing
If you just want to use Spring AOP, you dont need a special gradle plugin. Just put your .kt file in src/main/kotlin and follow the Spring AOP docs.
If you want to weave your aspect at compile-time with ajc, you have two options:
Keep the io.freefair.aspectj plugin to compile an weave the aspect in one step: Implement your aspect as .java or .aj so it can be compiled by ajc
Switch to the io.freefair.aspectj.post-compile-weaving plugin in order to separate the compilation an weaving steps: In this case you can keep your Aspect implementation as Kotlin, but you have to put it in src/main/kotlin. Your Aspect will then be compiled by kotlinc and then woven by ajc.
This looks like the 347th duplicate of a classical Spring AOP question: If you read the manual, you will notice that Spring AOP only works for Spring components, e.g. declared via #Component or #Bean.
Your TestCandidate seems to be a POJO, so Spring does not know about it. Also if you make it a component, make sure you get an instance from the container and do not just create one via constructor call in your test.
I have entity class like this:
package tr.com.example.model.porting
...//omitting imports
#Configurable
#Data
#Entity
public class PortOut{
public void handleRequest(Long portingId) {
processRequest(portingId);
...
}
}
And here is my aspect code in different class annotated with #Aspect and #Component:
#Around("execution(* tr.com.example..PortOut.handleRequest(..))")
public void handlePortingProcessAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
joinPoint.proceed();
log.debug(log.debug("[POM]:{} is executed with parameters:{}", joinPoint.toShortString(), joinPoint.getArgs()))
}
Normally, my aspect code works fine for other Spring managed classes such as using #Service, #Component annotation. I am using https://github.com/subes/invesdwin-instrument as a weaving agent and also using Lombok.
I tried this link(along with other so posts). But since I am using Lombok, I cannot directy compile with aspectj compiler for weaving #Configurable classes, it doesn't work with Lombok as I found out.
https://groups.google.com/forum/#!topic/project-lombok/ZkLsTZVSgD4
Shortly, what I want is using aspectj for logging some methods's arguments, but I can't use it in any Hibernate entity class, other than that it works fine. How to get it working in entity classes?
Here you find the explanation why Lombok and AspectJ do not like each other.
What is said to work though, is to delombok the Lombok-annotated source code and then normally compile the generated source code with the AspectJ compiler (e.g. via AspectJ Maven plugin, which is what I use all the time in Maven projects). Assuming you do use Maven, also the delombok step can be done with Lombok Maven plugin. If you assign the right phases to the respective plugins, it should be possible to fully automate the build process.
Disclaimer: I have never used Lombok in my whole life, but know a thing or two about AspectJ and Maven.
It seems that the #PostConstruct method is not called when a bean is added to the context using a Kotlin BeanDefinitionDsl.
This happened to me in my own project but to create a simple way to reproduce it, here's what I did.
I forked the Spring example of using the Kotlin DSL https://github.com/sdeleuze/spring-kotlin-functional
I added a #PostConstruct to the UserHandler class. (More details below.)
I pushed the result here: https://github.com/benjishults/spring-kotlin-functional
So all you need to do is fork my repo and do a gradle run.
My questions are:
Shouldn't I expect that #PostConstruct to be called since I'm bringing the class in as a bean?
Am I missing a step?
Is this a Spring bug?
If you don't want to pull my repo, here are more details about what I did. I added this to the UserHandler class:
#PostConstruct
fun afterPropertiesSet() {
System.out.println("AFTER PROPERTIES SET CALLED")
}
along with the import and the Gradle dependency.
The UserHandler bean is pulled into the context using a call to the bean method within a beans DSL like so:
fun beans() = beans {
bean<UserHandler>()
// ...
}
and this is brought into the context with:
beans().initialize(context)
GenericApplicationContext instantiated in the Application class does not support out of the box #PostContruct. To make it works, you should use AnnotationConfigApplicationContext instead and remove the exclude for spring-aop in the Gradle build.
I'm using one mapper generated with MapStruct:
#Mapper
public interface CustomerMapper {
Customer mapBankCustomerToCustomer(BankCustomerData bankCustomer);
}
The default component model is spring (set in pom.xml)
<compilerArg>-Amapstruct.defaultComponentModel=spring</compilerArg>
I have a service in which I inject the customer mapper and works fine when I run the application
#Autowired
private CustomerMapper customerMapper;
But when I run unit tests that involves #SpringBootTest
#SpringBootTest
#AutoConfigureMockMvc
#RunWith(SpringRunner.class)
public class SomeControllerTest {
#Mock
private SomeDependency someDependency;
#InjectMocks
private SomeController someController;
#Test
public void shouldDoSomething() {
...
}
}
I get an org.springframework.beans.factory.UnsatisfiedDependencyException
Unsatisfied dependency expressed through field 'customerMapper'
I followed this answer and my problem was solved as quickly as I pasted proposed lines in my build.gradle file
As you are running your tests via the IDE there are 2 possibilities:
Eclipse or IntelliJ is picking up the Annotation Processors, you need to set them up correctly.
Eclipse or IntelliJ does not pick up the compiler options from the maven compiler
To rule out the possibilities do the following for each:
Make sure the IDE is configured to run APT. Have a look here how you can set it up. Run a build from the IDE and check if there are generated mapper classes
If there are they are most probably generated with the default component model. To solve this you have two options:
Use #Mapper(componentModel = "spring"). I personally prefer this option as you are IDE independent. You can also use a #MapperConfig that you can apply
Configure the IDE with the annotation options. For IntelliJ add the compiler argument in Settings -> Build, Execution, Deployment -> Compiler -> Annotation Processors, there is a section called Annotation Processor Options there add mapstruct.defaultComponentModel as option name and spring as value. I am not sure how to do it for Eclipse
I would like to know if it's possible to use Spring to resolve the dependencies of an object created manually in my program. Take a look at the following class:
public class TestClass {
private MyDependency md;
public TestClass() {
}
...
public void methodThaUsesMyDependency() {
...
md.someMethod();
...
}
}
This TestClass is not a spring bean, but needs MyDependency, that is a spring bean. Is there some way I can inject this dependency through Spring, even if I instantiate TestClass with a new operator inside my code?
Thanks
Edit: The method I'm describing in my original answer below is the general way to accomplish DI external of the container. For your specific need - testing - I agree with DJ's answer. It's much more appropriate to use Spring's test support, for example:
#Test
#ContextConfiguration(locations = { "classpath*:**/applicationContext.xml" })
public class MyTest extends AbstractTestNGSpringContextTests {
#Resource
private MyDependency md;
#Test
public void myTest() {
...
While the above example is a TestNG test, there is also Junit support explained in 8.3.7.2. Context management and caching.
General approach: Annotate your class with #Configurable and utilize AspectJ load-time or compile-time weaving. See 6.8.1 in the Spring documentation on AOP for more details.
You can then annotate your instance variables with #Resource or #Autowired. Though they accomplish the same goal of dependency injection, I recommend using #Resource since it's a Java standard rather than Spring-specific.
Lastly, remember to consider using the transient keyword (or #Transient for JPA) if you plan on serializing or persisting the objects in the future. Chances are you don't want to serialize references to your DI'd repository, service, or component beans.
See the autowire() method on the AutowireCapableBeanFactory class. If you use an ClasspathXmlApplicationContext, you can get the factory with getAutowireCapableBeanFactory()
To get the ApplicationContext, you would need to use a static singleton or other central repository, such as JNDI or a Servlet container. See DefaultLocatorFactory on how to get an instance of the ApplicationContext.
If what you need is for testing purposes, Spring has good support for the scenario that you described above.
Check out Spring Reference manual section on Testing