Spring #Autowired fails when ApplicationContext is inside another context - java

I'm trying to put together an SDK that uses Spring internally through a context it manages of its own. I want the jar that gets built to be usable regardless of whether or not Spring is in use on the application that wants to use the SDK.
I have something that works when it is running on its own. However if I attempt to use the SDK inside another Spring context (in my case a Spring Boot based application) I get a org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type exception.
Try as I might I cannot understand how to get this working, or indeed what I am doing wrong. The classes below show what I'm doing, the org.example.testapp.MySDKTest fails with the exception while the org.example.test.MySDKTest successfully passes. Sorry there is so much code but I can't reproduce the issue with a simplified case.
SDK source
package org.example.mysdk;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.example.mysdk.MyService;
import org.example.mysdk.MyServiceConfiguration;
public final class MySDK {
private static ApplicationContext applicationContext;
public static <T extends MyService> T getService(Class<? extends MyService> clazz, MyServiceConfiguration configuration) {
T tmp = (T) getApplicationContext().getBean(clazz);
tmp.setConfiguration(configuration);
return tmp;
}
private static ApplicationContext getApplicationContext() {
if (applicationContext == null) {
applicationContext = new AnnotationConfigApplicationContext(SpringContext.class);
}
return applicationContext;
}
}
.
package org.example.mysdk;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class MyService {
private MyServiceConfiguration configuration;
#Autowired
private MyAutowiredService myAutowiredService;
MyService() {
}
MyService(MyServiceConfiguration configuration) {
super();
this.configuration = configuration;
}
public MyServiceConfiguration getConfiguration() {
return configuration;
}
void setConfiguration(MyServiceConfiguration configuration) {
this.configuration = configuration;
}
String getSomething(String in) {
return "something + " + myAutowiredService.getThing(configuration.getValue()) + " and " + in;
}
}
.
package org.example.mysdk;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
#Service
#Scope("prototype")
public class MyServiceImpl1 extends MyService {
public MyServiceImpl1() {
}
public MyServiceImpl1(MyServiceConfiguration configuration) {
super(configuration);
}
public String method1() {
return this.getSomething("method1");
}
}
.
package org.example.mysdk;
public class MyServiceConfiguration {
private String value;
public void setValue(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
.
package org.example.mysdk;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
#Service
public class MyAutowiredService {
private String thing = "a value";
public String getThing(String in) {
return thing + " " + in;
}
#PostConstruct
void init() {
System.out.println("MyAutowiredService bean created");
}
}
.
package org.example.mysdk;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
#Configuration
#ComponentScan(basePackages = {
"org.example.mysdk"
})
public class SpringContext {
}
Tests
This first test fails with a org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type exception,
package org.example.testapp;
import static org.junit.Assert.*;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = App.class, loader = AnnotationConfigContextLoader.class)
public class MySDKTest {
#Autowired
MyServiceImpl1 service;
#Test
public void test() {
MyServiceConfiguration conf = service.getConfiguration();
assertEquals(conf.getValue(), "this is the instance configuration");
}
}
.
package org.example.testapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.example.mysdk.MySDK;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
#Configuration
#ComponentScan(basePackages = {
"org.example.testapp"
})
public class App {
#Bean
public MyServiceImpl1 myServiceImpl1() {
MyServiceConfiguration configuration = new MyServiceConfiguration();
configuration.setValue("this is the instance configuration");
return MySDK.getService(MyServiceImpl1.class, configuration);
}
}
and this test succeeds,
package org.example.test;
import static org.junit.Assert.*;
import org.example.mysdk.MySDK;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
import org.junit.Test;
public class MySDKTest {
#Test
public void test() {
MyServiceConfiguration configuration = new MyServiceConfiguration();
configuration.setValue("this is the instance configuration");
MyServiceImpl1 service = MySDK.getService(MyServiceImpl1.class, configuration);
assertEquals(service.getConfiguration().getValue(), "this is the instance configuration");
}
}
If I've gone about this the completely wrong way I'm happy to hear suggestions of how this should be done differently!

You have to modify two files.
First App.java, it should scan for "org.example.mysdk" package to inject myAutowiredService in abstract class MyService, If not it has to be created in App.java. And the name of the MyServiceImpl1 bean must be different from myServiceImpl1 as it will conflict.
package org.example.testapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.example.mysdk.MySDK;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
#Configuration
#ComponentScan(basePackages = {
"org.example.testapp", "org.example.mysdk"
})
public class App {
#Bean
public MyServiceImpl1 myServiceImpl() {
MyServiceConfiguration configuration = new MyServiceConfiguration();
configuration.setValue("this is the instance configuration");
return MySDK.getService(MyServiceImpl1.class, configuration);
}
}
Then secondly in MySDKTest.java should inject myServiceImpl which was created in App.java
import static org.junit.Assert.*;
import org.example.mysdk.MyServiceConfiguration;
import org.example.mysdk.MyServiceImpl1;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = App.class, loader = AnnotationConfigContextLoader.class)
public class MySDKTest {
#Autowired
MyServiceImpl1 myServiceImpl;
#Test
public void createOxiAccountService() {
MyServiceConfiguration conf = myServiceImpl.getConfiguration();
assertEquals(conf.getValue(), "this is the instance configuration");
}
}

Related

Spring MVC can't find mappings

I am trying to get my OrderController to work, but MVC can't seem to find it. Does anyone know why this happens?
Initializer class. The getServletMapping method keeps notifying me about Not annotated method overrides method annotated with #NonNullApi
package configs;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class Initializer extends AbstractAnnotationConfigDispatcherServletInitializer {
#Override
protected Class<?>[] getRootConfigClasses() {
return new Class[0];
}
#Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{Config.class};
}
#Override
protected String[] getServletMappings() {
return new String[] { "/api/*" };
}
}
The config class.
package configs;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ClassPathResource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.jdbc.datasource.init.DatabasePopulatorUtils;
import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import javax.sql.DataSource;
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = {"model"})
#PropertySource("classpath:/application.properties")
public class Config {
#Bean
public JdbcTemplate getTemplate(DataSource ds) {
return new JdbcTemplate(ds);
}
#Bean
public DataSource dataSource(Environment env) {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("org.hsqldb.jdbcDriver");
ds.setUrl(env.getProperty("hsql.url"));
var populator = new ResourceDatabasePopulator(
new ClassPathResource("schema.sql"),
new ClassPathResource("data.sql")
);
DatabasePopulatorUtils.execute(populator, ds);
return ds;
}
}
Then the controller.
package controllers;
import model.Order;
import model.OrderDAO;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;
#RestController
public class OrderController {
private OrderDAO orderdao;
public OrderController(OrderDAO orderDAO) {
this.orderdao = orderDAO;
}
#PostMapping("orders")
#ResponseStatus(HttpStatus.CREATED)
public Order saveOrder(#RequestBody Order order) {
return orderdao.addOrder(order);
}
#GetMapping("orders/{id}")
public Order getOrderById(#PathVariable Long id) {
return orderdao.getOrderById(id);
}
#GetMapping("orders")
public List<Order> getOrders() {
return orderdao.getAllOrders();
}
#DeleteMapping("orders/{id}")
public void deleteOrderById(#PathVariable Long id) {
orderdao.deleteOrderById(id);
}
}
Everything seems fine, I can't find the issue.
Since the OrderController is in the controllers package, I forgot to add the package in the configs componentScan parameters.
Your controller registers the endpoints at /orders/*, but in getServletMappings you specify the /api/*. You should add /api prefix to your controller, like this
#RestController
#RequestMapping("api")
public class OrderController {
...

How to unit-test a class which has methods using database connection?

I am trying to run tests on a controller class and it has atleast a method which internally uses a DAO to retrieve information from the DB (MySQL).
The problem I have is that the dao method is giving null and I end up with NullPointerException error.
How do I tests a class which has methods that internally use a database connection?
Haven't been able to find any useful post/answer.
Project structure:
src
main
java
[package]
RegisterController.java
Config.java // configuration class
test
java
[package]
RegisterControllerTest.java
RegisterController.java
package com.webapp.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import com.webapp.Config;
import com.webapp.dao.AccountDao;
#Controller
public class RegisterController {
#Autowired
AccountDao accDao;
public String validateUsername(String uname){
List<String> errors = new ArrayList<String>();
// ... unrelated code
// NullPointerException thrown here
if(accDao.getAccountByUsername(uname) != null)
errors.add("err#taken");
return errors.toString();
}
}
RegisterControllerTest.java
import static org.junit.jupiter.api.Assertions.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import com.webapp.controller.RegisterController;
import com.webapp.Config;
#SpringBootTest
public class RegisterControllerTest {
#Mock
private Config config;
private RegisterController rc;
#BeforeEach
public void init() {
config = Mockito.mock(Config.class);
rc = new RegisterController();
}
#Test
public void testValidateUsername() {
assertEquals("[]", rc.validateUsername("Username123")); // N.P.E
}
}
Config.java
package com.webapp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import com.webapp.dao.AccountDao;
import com.webapp.dao.AccountDaoImpl;
import com.webapp.dao.Dao;
#Configuration
#ComponentScan(basePackages = { "com.webapp.controller", "com.webapp.dao", "com.webapp.test" })
public class Config {
private static class Database {
private static String host = "127.0.0.1";
private static String user = "root";
private static String pass = "root";
private static String dbname = "memedb";
private static int port = 3306;
public static String getUrl() {
return "jdbc:mysql://"+host+":"+port+"/"+dbname+"?serverTimezone=Europe/Stockholm";
}
}
#Bean
public DriverManagerDataSource getDataSource() {
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl(Database.getUrl());
ds.setUsername(Database.user);
ds.setPassword(Database.pass);
return ds;
}
#Bean
public AccountDao getAccDao() {
return new AccountDaoImpl(getDataSource());
}
}
Instead of mocking config, mock the Dao. You are getting NPE, because the mocked config is not setting #Autowired accDao... so your accDao == null:
#Controller
public class RegisterController {
AccountDao accDao;
public RegisterController(AccountDao accDao) {
this.accDao = accDao;
}
...
}
#BeforeEach
public void init() {
accDaoMock = Mockito.mock(AccountDao.class);
rc = new RegisterController(accDaoMock);
}
#Test
public void testValidateUsername() {
when(accDaoMock.getAccountByUsername("Username123")).thenReturn(null);
assertEquals("[]", rc.validateUsername("Username123"));
}
Why are you configuring the connection DB programmatically? I advise you configure the connection DB with autoconfiguration of Spring Boot.
The DAO object have to mock in an unit test.
Here is a good article about the Junit test for a Spring Boot App.

subclasses of abstract class object is null when using autowired

I have these classes. when I debug I see that the spring creates the service object in constructor and called the constructors in both classes but when I want to use the fields, they are null. What's the problem?! (type1Processor, type2Processor and type3Processor are null)
import com.vali.ReactiveSocketServer.service.ReceivedDataService;
import org.springframework.stereotype.Component;
public abstract class Processor {
public ReceivedDataService receivedDataService;
public Processor(ReceivedDataService receivedDataService) {
this.receivedDataService = receivedDataService;
}
public abstract void readStream(String stream);
}
and this is its subclass
import com.vali.ReactiveSocketServer.model.ReceivedData;
import com.vali.ReactiveSocketServer.service.ReceivedDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class Type1Processor extends Processor {
#Autowired
public Type1Processor(ReceivedDataService receivedDataService) {
super(receivedDataService);
}
#Override
public void readStream(String stream) {
System.out.println("readStream "+ getClass().getSimpleName() + "-" + stream);
receivedDataService.add(new ReceivedData(stream.getBytes()));
}
}
and this is its usage:
import com.vali.ReactiveSocketServer.processor.Processor;
import com.vali.ReactiveSocketServer.processor.Type1Processor;
import com.vali.ReactiveSocketServer.processor.Type2Processor;
import com.vali.ReactiveSocketServer.processor.Type3Processor;
import com.vali.ReactiveSocketServer.receivers.AppServer;
import com.vali.ReactiveSocketServer.socket.ClientHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.util.HashMap;
import java.util.Map;
#SpringBootApplication
public class ReactiveSocketServerApplication {
private AppServer appServer;
#Autowired
Type1Processor type1Processor;
#Autowired
Type2Processor type2Processor;
#Autowired
Type3Processor type3Processor;
public static void main(String[] args) {
SpringApplication.run(ReactiveSocketServerApplication.class, args);
ReactiveSocketServerApplication reactiveSocketServerApplication = new ReactiveSocketServerApplication();
reactiveSocketServerApplication.Start();
}
public void Start(){
appServer = AppServer.getInstance();
Map<Integer, Processor> processorMap = new HashMap<>();
processorMap.put(7001, type1Processor);
processorMap.put(7002, type2Processor);
processorMap.put(7003, type3Processor);
appServer.initialize(processorMap);
new ClientHandler(7001, 1000);
new ClientHandler(7002, 5000);
}
}
You are instantiating ReactiveSocketServerApplication yourself.
So spring can't inject the #Autowired annotated beans, because the instance was created outside of it's life cycle.
Remove this completly:
ReactiveSocketServerApplication reactiveSocketServerApplication = new ReactiveSocketServerApplication();
reactiveSocketServerApplication.Start();
And annotate your Start() with #PostConstruct:
#PostConstruct
public void Start() { ... }
You are missing #Component in the abstract class ReceivedDataService.
This mean when you create the subclases using :
#Autowired
public Type1Processor(ReceivedDataService receivedDataService) {
super(receivedDataService);
}
Can't inject ReceivedDataService and get the default value(null)

Spring AOP - Unable to execute Aspect

I am new to Spring AOP and annotations. I tried to write a simple program that uses Aspect. I am unable to figure out where I went wrong. Its doesn't print the what I have in my Aspect.
package com.business.main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#EnableAspectJAutoProxy
#Configuration
public class PrintMain {
public static void main(String[] args) {
// Do I always need to have this. Can't I just use #Autowired to get beans
ApplicationContext ctx = new AnnotationConfigApplicationContext(PrintMain.class);
CheckService ck = (CheckService)ctx.getBean("service");
ck.print();
}
#Bean(name="service")
public CheckService service(){
return new CheckService();
}
}
package com.business.main;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class SimpleAspect {
#Around("execution(* com.business.main.CheckService.*(..))")
public void applyAdvice(){
System.out.println("Aspect executed");
}
}
package com.business.main;
import org.springframework.stereotype.Component;
#Component
public class CheckService{
public void print(){
System.out.println("Executed service method");
}
}
Output: Executed service method
I expect to print what I have in my Aspect
I think your #Component isn't work!
Maybe, you need the #ComponentScan
#EnableAspectJAutoProxy
#ComponentScan
#Configuration
public class PrintMain {
public static void main(String[] args) {
// Do I always need to have this. Can't I just use #Autowired to get beans
ApplicationContext ctx = new AnnotationConfigApplicationContext(TNGPrintMain.class);
CheckService ck = (CheckService)ctx.getBean("service");
ck.print();
}
#Bean(name="service")
public CheckService service(){
return new CheckService();
}
}

Autowire in An Nonbean Class using AspectJ

AppConfig contains Java Configuration.
package com.wh;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableLoadTimeWeaving;
import org.springframework.context.annotation.EnableLoadTimeWeaving.AspectJWeaving;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
#Configuration
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving=AspectJWeaving.ENABLED)
public class AppConfig {
#Bean
#Lazy
public EchoService echoService(){
return new EchoService();
}
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
}
Service Class
package com.wh;
import org.springframework.stereotype.Service;
#Service
public class EchoService {
public void echo( String s ) {
System.out.println( s );
}
}
EchoDelegateService is the Non Bean class in which we have Autowired The required Bean.
We expect that the EchoService should get autowired.
Problem : EchoService not getting autowired. Gives an Null Pointer exception.
package com.wh;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
#Configurable( preConstruction = true, autowire = Autowire.BY_TYPE, dependencyCheck = false )
public class EchoDelegateService {
#Autowired
private EchoService echoService;
public void echo( String s ) {
echoService.echo( s );
}
}
Main Class where we are calling method of NonBean Class.
package com.wh;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class MainApp {
public static void main(String[] args) {
ApplicationContext ctx =
new AnnotationConfigApplicationContext(AppConfig.class);
new EchoDelegateService().echo("hihi, it works...");
}
}
Your question already includes the answer: "... in a non-bean class". This simply does not work. All the autowiring, aspect resolving and whatever is to that, only works for beans. Thus, you definitely need to construct your EchoDelegateService via the spring factory:
EchoDelegateService myService = ctx.getBean(EchoDelegateService.class);
myService.echo("this should really work now");

Categories

Resources