AspectJ is not working on beans without interface and defined in the configuration class - kotlin

I created a demo application to reproduce it:
DemoService
open class DemoService {
fun test() {
println("test function is executed.")
}
}
DemoAspect
#Aspect
class DemoAspect {
#Around("execution(* com.example.demo.service.DemoService.test(..))")
fun testAspect(joinPoint: ProceedingJoinPoint) {
println("before test function.")
joinPoint.proceed()
println("after test function.")
}
}
AppConfig
#Configuration
#EnableAspectJAutoProxy
class AppConfig {
#Bean
fun demoService() = DemoService()
#Bean
fun demoAspect() = DemoAspect()
}
SpringDemoApplication
#SpringBootApplication
#Import(AppConfig::class)
class SpringDemoApplication
fun main(args: Array<String>) {
val context = runApplication<SpringDemoApplication>(*args)
val demoService = context.beanFactory.getBean(DemoService::class.java)
demoService.test()
}
Execution result:
test function is executed.
The aspect is not working which is not expected.
I tried following variations and they worked correctly:
Remove the beans in configuration services and register beans by annotations
DemoService
#Service
open class DemoService {
...
}
AppConfig
#Configuration
#EnableAspectJAutoProxy
class AppConfig {
#Bean
fun demoAspect() = DemoAspect()
}
Let DemoService implements an interface
DemoService
interface DemoService {
fun test()
}
open class DemoServiceImpl: DemoService {
override fun test() {
println("test function is executed.")
}
}
AppConfig
#Configuration
#EnableAspectJAutoProxy
class AppConfig {
#Bean
fun demoService() = DemoServiceImpl()
#Bean
fun demoAspect() = DemoAspect()
}
I want to understand why the AspectJ is not working on this combination:
The target bean is not implementing any interface.
The bean is registered in Configuration class.

Spring AOP, in contrast to native AspectJ, is based on dynamic proxies. In order to proxy a class, it must not be final. In Kotlin terms, it must be open. Moreover, in order to proxy a method, it must not be final either, i.e. you also need to open the method. See also this Baeldung tutorial.
So, please use open fun test(), then it works as expected.
Update for follow-up question:
why it works if I use #Service even though the function is final?
Because probably in your build you use the Kotlin all-open compiler plugin with Spring support enabled. If I were you, I would not use #Bean factory methods anyway, but simply #Component, #Service or similar Spring annotations directly on the implementation classes.
Of course, you could also use the generic all-open plugin in addition to the Spring version and then do this:
annotation class OpenMe()
#OpenMe
class DemoService {
fun test() {
println("test function is executed.")
}
}
Then, in Maven you would do something like:
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<configuration>
<args>
<arg>-Xjsr305=strict</arg>
</args>
<compilerPlugins>
<plugin>all-open</plugin>
<plugin>spring</plugin>
</compilerPlugins>
<pluginOptions>
<option>all-open:annotation=org.acme.OpenMe</option>
</pluginOptions>
</configuration>
<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-allopen</artifactId>
<version>${kotlin.version}</version>
</dependency>
</dependencies>
</plugin>
Then you would not need any open keywords on the service class or method anymore and could continue to use your #Bean factory method. But I think this is more complicated than using Spring annotations - convention over configuration.

Related

Dagger - Dependency inject into Activity

I have a main activity as below:
#AndroidEntryPoint
class MainActivity : BaseActivity() {
#Inject
lateinit var configuration: Configuration
...
}
And a module defined like:
#Module
#InstallIn(SingletonComponent::class)
class AppModules {
#Singleton
#Provides
fun bindDeviceConfig(): Configuration {
return Configuration()
}
}
When I try running this it gives an error:
lateinit property configuration has not been initialized
I've looked at the documentation and can't seem to get my head around why I'm getting this issue. Does anyone know why?

Spring Cloud Contract EXPLICIT and WEBTESTCLIENT testMode

I want to use Spring Cloud Contract to produce my contracts and verify them. I want to use Spring WebFlux and Junit5. This is my Controller:
#RestController
#Slf4j
public class HelloWorldPortRESTAdapter implements HelloWorldPort {
#GetMapping(value = "/hello-world", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
#Override
public Mono<String> helloWorld() {
return Mono.just("Hello World!");
}
}
This is cloud contract maven plugin configuration:
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<basePackageForTests>com.example.feedproviderapi.contract</basePackageForTests>
<testFramework>JUNIT5</testFramework>
<testMode>EXPLICIT</testMode>
</configuration>
</plugin>
But i don't know how base test class should look like. I tried this:
#ExtendWith(SpringExtension.class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class BaseTestClass {
#LocalServerPort
private int port;
#BeforeEach
void setup(){
RestAssured.baseURI = "http://localhost:" + this.port;
}
}
When I run mvn clean install, it returns java.net.ConnectException: Connection refused (Connection refused)
Then I changed testMode property in maven plugin to WEBTESTCLIENT and updated BaseTestClass like this:
#ExtendWith(SpringExtension.class)
#SpringBootTest
public class BaseTestClass {
#Autowired
WebApplicationContext context;
#BeforeEach
void setup(){
RestAssuredWebTestClient.standaloneSetup(context);
}
}
And again when I run mvn clean install now it returns:
You haven't configured a WebTestClient instance. You can do this statically
RestAssuredWebTestClient.mockMvc(..)
RestAssuredWebTestClient.standaloneSetup(..);
RestAssuredWebTestClient.webAppContextSetup(..);
or using the DSL:
given().
mockMvc(..). ..
Btw I tried RestAssuredWebTestClient.standaloneSetup(new HelloWorldPortRESTAdapter()); as well in my BaseTestClass but the result is same.
So how should I implement BaseTestClass regarding EXPLICIT and WEBTESTCLIENT testModes?
I have struggled for 3 days, to makes RestAssuredWebTestClient works.
Thanks a llooottt for : https://www.baeldung.com/spring-5-webclient
That's how i could do that:
#WebFluxTest
public class AnimeControllerIntegrTest{
WebTestClient testClient;
#Test
public void get_RA() {
testClient = WebTestClient.bindToServer().baseUrl("http://localhost:8080/animes").build();
RestAssuredWebTestClient
.given()
.webTestClient(testClient)
.when()
.get()
.then()
.statusCode(OK.value())
.body("name" ,hasItem("paulo"))
;
}
}
Please check the spring cloud contract samples https://github.com/spring-cloud-samples/spring-cloud-contract-samples/blob/master/producer_webflux_webtestclient
And junit5
https://github.com/spring-cloud-samples/spring-cloud-contract-samples/tree/master/producer_with_junit5
The
plugin
<plugin>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-contract-maven-plugin</artifactId>
<version>${spring-cloud-contract.version}</version>
<extensions>true</extensions>
<configuration>
<packageWithBaseClasses>com.example</packageWithBaseClasses>
<testMode>WEBTESTCLIENT</testMode>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<failIfNoTests>true</failIfNoTests>
</configuration>
<dependencies>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-surefire-provider</artifactId>
<version>${junit-platform-surefire-provider.version}</version>
</dependency>
</dependencies>
</plugin>
And The base class for junit5
public abstract class BeerRestBase {
#BeforeEach
public void setup() {
// remove::start[]
RestAssuredWebTestClient.standaloneSetup(new ProducerController(personToCheck -> personToCheck.age >= 20));
// remove::end[]
}
}
Try passing an ApplicationContext instance instead of WebApplicationContext.

Arquillian with Mockito and CDI

Is it possible to create spy(mock) object in testing class?
Here is tested class.
#Stateless
#Slf4j
public class UserDao {
#Inject
private TestBean testBean;
public String mock() {
return testBean.mock();
}
public String notMock() {
return testBean.notMock();
}
}
TestBean code
#Stateless
#Slf4j
public class TestBean {
public String notMock() {
return "NOT MOCK";
}
public String mock() {
return "IMPLEMENTED MOCK";
}
}
Here's my test
#RunWith(Arquillian.class)
public class UserDataTest {
#Rule
public ExpectedException thrown = ExpectedException.none();
#Inject
private UserDao userDao;
#Deployment
protected static Archive createWar() {
File[] dependencies = Maven.configureResolver()
.withRemoteRepo("nexus-remote", "http://maven.wideup.net/nexus/content/groups/public/", "default")
.withRemoteRepo("nexus-release", "http://maven.wideup.net/nexus/content/repositories/releases/", "default")
.resolve(
"org.slf4j:slf4j-simple:1.7.7",
"eu.bitwalker:UserAgentUtils:1.15",
"org.mockito:mockito-all:1.10.8"
).withoutTransitivity().asFile();
return ShrinkWrap
.create(WebArchive.class, "pass.jpa.war")
.addAsWebInfResource("jbossas-ds.xml")
.addAsWebInfResource("jboss-deployment-structure.xml")
.addAsLibraries(
PassApiDeployments.createDefaultDeployment(),
PassUtilLibrary.createDefaultDeployment(),
PassJpaDeployments.createDefaultDeployment()
).addAsLibraries(dependencies);
}
#Test
public void testMock() {
assertEquals("MOCK", userDao.mock());
}
#Test
public void testNotMock() {
assertEquals("NOT MOCK", userDao.notMock());
}
}
I'd like to create a spy object on TestBean to change result on method test() of this bean.
So is it possible to create TestBean spy in UserDao.
I solve some problems through producer like this.
#Singleton
public class MockFactory {
#Produces
#ArquillianAlternative
public TestBean getTestBean() {
return when(mock(TestBean.class).mock()).thenReturn("MOCK").getMock();
}
}
But in this example I need create on Bean completely on my own. And if it is bean with additional dependencies and thus i will manage all dependencies.
As far as I know, its not possible to use a mocking framework in combination with arquillian ...
I haven't used it myself, but this Arquillian extension seems to be specifically designed to support Mockito Spy objects in an Arquillian test: https://github.com/topikachu/arquillian-extension-mockito/

Dagger: Inject named string in constructor

I have a properties file and I would like to inject a property in a service.
I would like use the constructor method for DI like this:
#Inject
public ScanService(#Named("stocks.codes") String codes, IYahooService yahooService) {
this.yahooService = yahooService;
this.codes = codes;
}
I try to do a module like specified in this link => Dagger: Inject #Named strings?
#Provides
#Named("stocks.code")
public String providesStocksCode() {
return "test";
}
And for the provider method for my service:
#Provides
#Singleton
public IScanService provideScanService(String codes, IYahooService yahooService){
return new ScanService(codes, yahooService);
}
When I run the compilation I get this error:
[ERROR]
/Users/stocks/src/main/java/net/modules/TestModule.java:[22,7]
error: No injectable members on java.lang.String. Do you want to add
an injectable constructor? required by
provideScanService(java.lang.String,net.IYahooService)
for net.modules.TestModule
How can I inject my property correctly in the constructor ?
Thanks.
You have two different names: stocks.codes and stocks.code.
You will also have to annotate your provideScanService codes parameter:
#Provides
#Singleton
public IScanService provideScanService(#Named("stocks.codes") String codes, IYahooService yahooService){
return new ScanService(codes, yahooService);
}
Or do it like this:
#Provides
#Singleton
public IScanService provideScanService(ScanService scanService){
return scanService;
}
If you mean Dagger 2, I can help you.
First you have to declare dependencies in Component
#Singleton
#Component(modules = ApplicationModule.class)
public interface ApplicationComponent {
void inject(BaseActivity baseActivity);
#Named("cloud") UserDataSource userCloudSource();
#Named("disc") UserDataSource userDiscSource();
UserDataRepository userDataRepository();
}
then instantiate it in Module
#Module
public class ApplicationModule {
#Provides #Named("cloud")
UserDataSource provideCloudUserSource(UserCloudSource userSource) {
return userSource;
}
#Provides #Named("disc")
UserDataSource provideDiscUserSource(UserDiscSource userSource) {
return userSource;
}
#Provides
UserDataRepository provideUserRepository(UserDataRepository repository) {
return repository;
}
}
then inject it in constructor with #Named qualifiers
#Inject
public UserDataRepository(#Named("cloud") UserDataSource cloudSource,
#Named("disc") UserDataSource discSource) {
this.cloudDataSource= cloudSource;
this.discDataSource = discSource;
}

Google guice inject a instance created by Spring and method intercept

I use Gucie 3.0 to intercept any methods that have my defined annotation #LogRequired. However for my application, some beans are initialized by Spring with injected fields values. After calling giuce injector.injectMembers(this), the beans gets proxied by guice but all original fields values are gone. Looks like Guice re-constucts the beans and throw away all old values. Is this expected behavior or how can I solve this issue?
Create a class extends AbstractModule
public class InterceptorModule extends AbstractModule{ public void configure()
{ LogInterceptor tracing = new LogInterceptor(); requestInjection(tracing); bindInterceptor(Matchers.any(), Matchers.annotatedWith(LogRequired.class), tracing); }
}
Define the interceptor business logic
public class LogInterceptor implements MethodInterceptor { //business logic here }
Create LogService class
Public class LogService { Injector injector = Guice.createInjector(new InterceptorModule()); }
I have one of the bean example below with the getName method wants to be intercepted:
public class UserImplTwo implements IUser {
private String name;
#LogRequired
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
which is initialized by Spring context:
Finally I have a consumer to consume the bean:
public class Consumer
{
#Inject
private UserImplTwo instance;
public void setInstance(UserImplTwo instance)
{
this.instance = instance;
}
public void init()
{
// the value of name is printed out as 'hello world'
System.out.println( this.instance.getName());
LogService.injector.injectMembers(this);
// the value of name is printed out as null, should be 'hello world'
System.out.println( this.instance.getName());
}
}
Then use Spring to initialized the bean:
<bean id="consumer" class="com.demo.Consumer" init-method="init">
<property name="instance" ref="userTwo"></property>
</bean>
Please let me know if this the the right approach or if I did something wrong, because I have to use Spring to initialize some beans.
A "right approach" is probably to keep things simple and use Spring's DI if you use Spring Framework, and not try to mix and match with Guice :-)
Having said that there seems no technical reason why they can't be mixed and matched together to some degree.
I think you will have more success with another approach. One that I have used before is to make use of Spring MVC Java-based configuration. Here is the basic approach.
Create a class that extends WebMvcConfigurationSupport:
#Configuration
#Import(BeansConfig.class)
public class Config extends WebMvcConfigurationSupport {
}
Separate out your beans config (probably it can be merged with the above but I guess it's quite dull code and you normally don't want want to see it). And use it to create your beans with your Guice injector before providing them to Spring.
#Configuration
public class BeansConfig {
#Bean
public Consumer getConsumer() {
return SomeGuiceInjectorFactory.newInstance(Consumer.class);
}
}
Include this in your spring.xml (or bootstrap other ways if your servlet container is newer than mine was)
<context:annotation-config/>
<bean id="extendedWebMvcConfig" class="Config"/>
Constructor injection and most/all? other Guice goodness should work also with such scenario.
Also you won't need to configure your beans in xml.