How to get the instance of JpaRepository? [duplicate] - spring

This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 1 year ago.
I am new to Spring, this is maybe newbie question.
I create a Repository:
import org.springframework.data.jpa.repository.JpaRepository;
public interface TaskRepository extends JpaRepository<Task, String>{
}
The Repository work fine with my RestController
import org.springframework.web.bind.annotation.*;
#RestController
public class TaskController {
private final TaskRepository repository;
TaskController(TaskRepository repository) {
this.repository = repository;
}
// Aggregate root
// tag::get-aggregate-root[]
#GetMapping("/Tasks")
public List<Task> all() {
return repository.findAll();
}
// end::get-aggregate-root[]
#PostMapping("/Tasks")
public Task newTask(#RequestBody Task newTask) {
return repository.save(newTask);
}
// Single item
#GetMapping("/Tasks/{id}")
public Task one(#PathVariable String id) {
return repository.findById(id)
.orElseThrow(() -> new TaskNotFoundException(id));
}
#PutMapping("/Tasks/{id}")
public Task replaceTask(#RequestBody Task newTask, #PathVariable String id) {
return repository.findById(id)
.map(Task -> {
Task.setSparkAppId(newTask.getSparkAppId());
Task.setValue(newTask.getValue());
return repository.save(Task);
})
.orElseGet(() -> {
return repository.save(newTask);
});
}
#DeleteMapping("/Tasks/{id}")
public void deleteTask(#PathVariable String id) {
repository.deleteById(id);
}
}
However, I want to use that JpaRepository for my internal logic. I create a TaskHandler listener, that will update new Task in the database.
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.spark.launcher.SparkAppHandle;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.Optional;
import java.util.List;
public class TaskListener implements SparkAppHandle.Listener {
#Autowired
private TaskRepository repository;
private static final Logger logger = LoggerFactory.getLogger(TaskListener.class);
public Task task;
public TaskListener(Task task) {
this.task = task;
}
#Override
public void stateChanged(SparkAppHandle handle) {
String curAppId = handle.getAppId();
SparkAppHandle.State curState = handle.getState();
String curStateStr = curState.toString();
logger.info("app id " + curAppId + " state " + curStateStr);
Optional<Throwable> opErr = handle.getError();
opErr.ifPresent(throwable -> logger.error(throwable.getMessage()));
this.task.setSparkAppId(curAppId);
this.task.setState(curStateStr);
//replaceTask(task, task.getTaskId());
}
#Override
public void infoChanged(SparkAppHandle handle) {
String curAppId = handle.getAppId();
SparkAppHandle.State curState = handle.getState();
String curStateStr = curState.toString();
logger.info("app id " + curAppId + " state " + curStateStr);
// SparkTaskModel.getInstance().executeUpdateTask(this.taskId, curAppId, curStateStr);
Optional<Throwable> opErr = handle.getError();
if (opErr.isPresent()){
logger.error(opErr.get().getMessage());
}
this.task.setSparkAppId(curAppId);
this.task.setState(curStateStr);
//replaceTask(task, task.getTaskId());
}
// not work because repository is null
public Task replaceTask(Task newTask, String id) {
return repository.findById(id)
.map(Task -> {
Task.setSparkAppId(newTask.getSparkAppId());
Task.setValue(newTask.getValue());
return repository.save(Task);
})
.orElseGet(() -> {
return repository.save(newTask);
});
}
}
I cannot put the Repository into Contructor because I create the TaskListener object in my program, and I don't know what to put in place of Repository should it in TaskListener contructor.
Error: (xxx was my project name censored...)
java.lang.NullPointerException
at xxx.xxx.xxx.xxx.spark_launcher_for_meow.xxx.tasksparklauncher.TaskListener.replaceTask(TaskListener.java:70)
at xxx.xxx.xxx.xxx.spark_launcher_for_meow.xxx.tasksparklauncher.TaskListener.stateChanged(TaskListener.java:47)
at org.apache.spark.launcher.AbstractAppHandle.fireEvent(AbstractAppHandle.java:160)
at org.apache.spark.launcher.AbstractAppHandle.setState(AbstractAppHandle.java:137)
at org.apache.spark.launcher.AbstractAppHandle.setState(AbstractAppHandle.java:124)
at org.apache.spark.launcher.LauncherServer$ServerConnection.handle(LauncherServer.java:315)
at org.apache.spark.launcher.LauncherConnection.run(LauncherConnection.java:58)
at java.base/java.lang.Thread.run(Thread.java:829)
The error point to:
repository.findById(id)....
in function
replaceTask
Said that repository is null.

#Autowired won't work since TaskListener is not a bean managed by Spring, but a bean that you instantiate manually.
You can simply pass TaskRepository as 2nd constructor argument
public class TaskListener implements SparkAppHandle.Listener {
private final TaskRepository repository;
public final Task task;
public TaskListener(TaskRepository repository, Task task) {
this.repository = repository;
this.task = task;
}
// other stuff
}
And then somewhere in your code your that instantiates TaskListener you autowire TaskRepository and use it as constructor parameter
#Service // << this means that bean is managed by Spring
// and all autowirings will be automatically done by Spring
// therefore taskRepository will be NOT null
public class SomeTaskListenerService {
#Autowired
private TaskRepository taskRepository;
public void someMethod() {
Task task = ... // obtain task
TaskListener listener = new TaskListener(taskRepository, task);
// do some actions with the listener
}
}

You can use multiple constructors in your TaskListener class. But in this case, you have to add #Autowired for the constructor where spring injection is required.
So your TaskListener class, you can modify like this.
private final TaskRepository repository;
public Task task;
public TaskListener(Task task) {
this.task = task;
}
#Autowired
TaskListener(TaskRepository repository) {
this.repository = repository;
}

Related

Junit test for saving data with JPA

Am trying to make a junit test to save data with JPA. Below is my entity class
#Entity
#Table(name="book")
public class test {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="BOOK_REF_ID",nullable=false)
private int bookRefId;
#Column(name="BOOK_CODE",nullable=false)
private String bookCode;
#Column(name="BOOK_NAME",nullable=false)
private String bookDescription;
public int getBookRefId() {
return bookRefId;
}
public void setBookRefId(int bookRefId) {
this.bookRefId = bookRefId;
}
public String getBookCode() {
return bookCode;
}
public void setBookCode(String bookCode) {
this.bookCode = bookCode;
}
public String getBookDescription() {
return bookDescription;
}
public void setBookDescription(String bookDescription) {
this.bookDescription = bookDescription;
}
}
Service class is
public interface BookService()
{
public Book create(Book book);
}
Repository class is
public interface BookRepository extends
JpaRepository<Book,Integer>
{ }
Service Implementation class is
public BookServiceImpli implements BookService()
{
#Resource
BookRepository repository;
#Override
public Book create(Book book) {
// TODO Auto-generated method stub
return repository.save(book);
}
}
Now my test class is
#RunWith(SpringRunner.class)
#DataJpaTest
#SpringBootTest(classes= {JPAConfig.class})
#AutoConfigureTestDatabase(replace=Replace.NONE)
#TestPropertySource(
locations = "classpath:application.properties")
public class TestBook {
#Autowired
private BookService bookService ;
#Test
public void test() {
Book book = new Book();
book.setBookCode("abc");
book.setBookDescription("safd");
bookService.create(book);
}
Application properties contains password and database details and JPAConfig contain JPA configuration details such as entity scan database details. When am trying to run the test case am getting an error like
A component required a bean of type
'com.repository.sample.BookRepository' that could not be found.
I don't have main method in it.Am new to unit testing please anyone help me to solve the issue.

#RefreshScope stops #Scheduled task

I have a monitoring app wherein I am running a fixedRate task. This is pulling in a config parameter configured with Consul. I want to pull in updated configuration, so I added #RefreshScope. But as soon as I update the config value on Consul, the fixedRate task stops running.
#Service
#RefreshScope
public class MonitorService {
#Autowired
private AppConfig appConfig;
#PostConstruct
public void postConstRun() {
System.out.println(appConfig.getMonitorConfig());
}
#Scheduled(fixedRate = 1000)
public void scheduledMonitorScan() {
System.out.println("MonitorConfig:" + appConfig.getMonitorConfig());
}
}
AppConfig class just has a single String parameter:
#Configuration
#Getter
#Setter
public class AppConfig {
#Value("${monitor-config:default value}")
private String monitorConfig;
}
As soon as I update the value in consul, the scheduled task just stops running (display in sheduledMonitorScan method) stop showing up.
I'm successfully get & override the values from consul config server using RefreshScopeRefreshedEvent
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.cloud.context.scope.refresh.RefreshScopeRefreshedEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
#Component
#RefreshScope
public class AlertSchedulerCron implements ApplicationListener<RefreshScopeRefreshedEvent> {
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
#Value("${pollingtime}")
private String pollingtime;
/*
* #Value("${interval}") private String interval;
*/
#Scheduled(cron = "${pollingtime}")
//#Scheduled(fixedRateString = "${interval}" )
public void task() {
System.out.println(pollingtime);
System.out.println("Scheduler (cron expression) task with duration : " + sdf.format(new Date()));
}
#Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
// TODO Auto-generated method stub
}
}
Here's how we've solved this issue.
/**
* Listener of Spring's lifecycle to revive Scheduler beans, when spring's
* scope is refreshed.
* <p>
* Spring is able to restart beans, when we change their properties. Such a
* beans marked with RefreshScope annotation. To make it work, spring creates
* <b>lazy</b> proxies and push them instead of real object. The issue with
* scope refresh is that right after refresh in order for such a lazy proxy
* to be actually instantiated again someone has to call for any method of it.
* <p>
* It creates a tricky case with Schedulers, because there is no bean, which
* directly call anything on any Scheduler. Scheduler lifecycle is to start
* few threads upon instantiation and schedule tasks. No other bean needs
* anything from them.
* <p>
* To overcome this, we had to create artificial method on Schedulers and call
* them, when there is a scope refresh event. This actually instantiates.
*/
#RequiredArgsConstructor
public class RefreshScopeListener implements ApplicationListener<RefreshScopeRefreshedEvent> {
private final List<RefreshScheduler> refreshSchedulers;
#Override
public void onApplicationEvent(RefreshScopeRefreshedEvent event) {
refreshSchedulers.forEach(RefreshScheduler::materializeAfterRefresh);
}
}
So, we've defined an interface, which does nothing in particular, but allows us to call for a refreshed job.
public interface RefreshScheduler {
/**
* Used after refresh context for scheduler bean initialization
*/
default void materializeAfterRefresh() {
}
}
And here is actual job, whose parameter from.properties can be refreshed.
public class AJob implements RefreshScheduler {
#Scheduled(cron = "${from.properties}")
public void aTask() {
// do something useful
}
}
UPDATED:
Of course AJob bean must be marked with #RefreshScope in #Configuration
#Configuration
#EnableScheduling
public class SchedulingConfiguration {
#Bean
#RefreshScope
public AJob aJob() {
return new AJob();
}
}
I have done workaround for this kind of scenario by implementing SchedulingConfigurer interface.
Here I am dynamically updating "scheduler.interval" property from external property file and scheduler is working fine even after actuator refresh as I am not using #RefreshScope anymore.
Hope this might help you in your case also.
public class MySchedulerImpl implements SchedulingConfigurer {
#Autowired
private Environment env;
#Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(10);
}
#Override
public void configureTasks(final ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(this.taskExecutor());
taskRegistrar.addTriggerTask(() -> {
//put your code here that to be scheduled
}, triggerContext -> {
final Calendar nextExecutionTime = new GregorianCalendar();
final Date lastActualExecutionTime = triggerContext.lastActualExecutionTime();
if (lastActualExecutionTime == null) {
nextExecutionTime.setTime(new Date());
} else {
nextExecutionTime.setTime(lastActualExecutionTime);
nextExecutionTime.add(Calendar.MILLISECOND, env.getProperty("scheduler.interval", Integer.class));
}
return nextExecutionTime.getTime();
});
}
}
My solution consists of listening to EnvironmentChangeEvent
#Configuration
public class SchedulingSpringConfig implements ApplicationListener<EnvironmentChangeEvent>, SchedulingConfigurer {
private static final Logger LOGGER = LoggerFactory.getLogger(SchedulingSpringConfig.class);
private final DemoProperties demoProperties;
public SchedulingSpringConfig(DemoProperties demoProperties) {
this.demoProperties = demoProperties;
}
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
LOGGER.info("Configuring scheduled task with cron expression: {}", demoProperties.getCronExpression());
taskRegistrar.addTriggerTask(triggerTask());
taskRegistrar.setTaskScheduler(taskScheduler());
}
#Bean
public TriggerTask triggerTask() {
return new TriggerTask(this::work, cronTrigger());
}
private void work() {
LOGGER.info("Doing work!");
}
#Bean
#RefreshScope
public CronTrigger cronTrigger() {
return new CronTrigger(demoProperties.getCronExpression());
}
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
return new ThreadPoolTaskScheduler();
}
#Override
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (event.getKeys().contains("demo.config.cronExpression")) {
ScheduledTasksRefresher scheduledTasksRefresher = new ScheduledTasksRefresher(triggerTask());
scheduledTasksRefresher.afterPropertiesSet();
}
}
}
Then I use the ContextLifecycleScheduledTaskRegistrar to recreate the task.
public class ScheduledTasksRefresher extends ContextLifecycleScheduledTaskRegistrar {
private final TriggerTask triggerTask;
ScheduledTasksRefresher(TriggerTask triggerTask) {
this.triggerTask = triggerTask;
}
#Override
public void afterPropertiesSet() {
super.destroy();
super.addTriggerTask(triggerTask);
super.afterSingletonsInstantiated();
}
}
Properties definition:
#ConfigurationProperties(prefix = "demo.config", ignoreUnknownFields = false)
public class DemoProperties {
private String cronExpression;
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
}
Main definition:
#SpringBootApplication
#EnableConfigurationProperties(DemoProperties.class)
#EnableScheduling
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Based on previous answers I added the following interface and used it on #RefreshScope annotated beans:
public interface RefreshScopeScheduled {
#EventListener(RefreshScopeRefreshedEvent.class)
default void onApplicationEvent() { /*do nothing*/ }
}

How to register Converter in Spring Data Rest application

I have Spring converter which uses Spring Data REST's component called EnumTranslator
#Component
public class TranslationStringToSpecificationStatusEnumConverter implements Converter<String, Specification.Status> {
private final EnumTranslator enumTranslator;
#Autowired
public TranslationStringToSpecificationStatusEnumConverter(EnumTranslator enumTranslator) {
this.enumTranslator = enumTranslator;
}
#Override
public Specification.Status convert(String source) {
return enumTranslator.fromText(Specification.Status.class, source);
}
}
Recommended way to register such converter is to subclass RepositoryRestConfigurerAdapter as follows:
#Configuration
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
private final TranslationStringToSpecificationStatusEnumConverter converter;
#Autowired
public RepositoryRestConfig(TranslationStringToSpecificationStatusEnumConverter converter) {
this.converter = converter;
}
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
}
When I run the Spring Boot application, it fails on the following:
***************************
APPLICATION FAILED TO START
***************************
Description:
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| translationStringToSpecificationStatusEnumConverter defined in file ...
↑ ↓
| org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration (field java.util.List org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration.configurers)
↑ ↓
| repositoryRestConfig defined in file ...
└─────┘
So there is circular bean dependency.
How can I register the converter above so that I don't introduce circular bean dependency?
To make it work:
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(String.class, Status.class, new StringToTranslatedEnumConverter<>(Status.class));
super.configureConversionService(conversionService);
}
First I created utility class that help me work with Spring beans in unmanaged objects:
#Component
public final class SpringUtils {
#Autowired private ApplicationContext ctx;
private static SpringUtils instance;
#PostConstruct
private void registerInstance() {
instance = this;
}
public static <T> T getBean(Class<T> clazz) {
return instance.ctx.getBean(clazz);
}
}
Then I created the converter:
public class StringToTranslatedEnumConverter<T extends Enum<T> & TranslatedEnum> implements Converter<String, T> {
private final ConcurrentMapCache cache;
private EnumTranslator enumTranslator;
private Class<T> type;
public StringToTranslatedEnumConverter(Class<T> type) {
this.type = type;
cache = new ConcurrentMapCache(type.getName());
}
#Override
public T convert(String from) {
if (enumTranslator == null) {
enumTranslator = SpringUtils.getBean(EnumTranslator.class);
}
Cache.ValueWrapper wrapper = cache.get(from);
if (wrapper != null) {
//noinspection unchecked
return (T) wrapper.get();
}
T translatedEnum = enumTranslator.fromText(type, from);
cache.put(from, translatedEnum);
return translatedEnum;
}
}
UPDATED
TranslatedEnum - it's interface-marker, used to mark enums which translation is only need.
public interface TranslatedEnum {
}
public enum Status implements TranslatedEnum {
CREATED, DELETED
}
The solution to this problem is Spring Core specific. In order to break circle bean dependency cycle, we have to delay setting converter in RepositoryRestConfig. It can be achieved with setter injection:
#Component
public class RepositoryRestConfig extends RepositoryRestConfigurerAdapter {
private TranslationStringToSpecificationStatusEnumConverter converter;
#Override
public void configureConversionService(ConfigurableConversionService conversionService) {
conversionService.addConverter(converter);
super.configureConversionService(conversionService);
}
#Autowired
public void setConverter(TranslationStringToSpecificationStatusEnumConverter converter) {
this.converter = converter;
}
}
You can find how to solve it in this commit by Greg Turnquist: https://github.com/pmihalcin/custom-converter-in-spring-data-rest/commit/779a6477d76dc77515b3e923079e5a6543242da2

Spring Bean Factory Configuration passing input parameter

I'm trying to create a BeanFactory called TaskBeanFactory that I can Autowire into another prototype class that's running on a thread. I want a different instance of a bean returned by the Factory based on a taskName that i want to pass into it but when i start the application i get a null pointer exception because the taskName is null. I had a look at this article but i'm confused about how I should configure the Factory and then pass in the taskName.
The Factory:
import org.springframework.beans.factory.config.AbstractFactoryBean;
import org.springframework.stereotype.Component;
#Data
#Component
#NoArgsConstructor
public class TaskBeanFactory extends AbstractFactoryBean<GenericTask>{
private TaskNameEnum taskName;
public TaskBeanFactory(TaskNameEnum taskName) {
setSingleton(false);
}
#Override
public Class<?> getObjectType() {
return GenericTask.class;
}
#Override
protected GenericTask createInstance() throws Exception {
switch (taskName) {
case FILE_OPERATION:
return new FileTask();
case DATA_OPERATION:
return new DataTask();
default:
return new GenericTask();
}
}
}
The classes used by the Factory:
#Data
public class GenericTask {
private String idTask;
public void executeTask(Work work) {};
}
#Component
#Scope(value="prototype")
public class FileTask extends GenericTask {
#Override
public void executeTask(Work work) {
//some processing
}
}
#Component
#Scope(value="prototype")
public class DataTask extends GenericTask {
#Override
public void executeTask(Work work) {
//some processing
}
}
and the thread that's calling the Factory:
#Slf4j
#Data
#Scope("prototype")
#Component
public class WorkerThread implements Runnable {
#Autowired
private TaskBeanFactory taskBeanFactory;
#Autowired
private DataService dataService;
#Override
public void run() {
//iterate a Map of taskIds from the dataService
taskBeanFactory.setTaskName(TaskNameEnum.valueOf(taskEntry.getKey()));
GenericTask genericTask = taskBeanFactory.getObject();
//expecting genericTask to be of Type FileTask if called with one Key
//or of Type DataTask if called with another
}
}
}

How to create a mocked (by jmockit) spring bean?

I am new to jmockit and would like to mock a bean inside my Java based Spring Application Configuration. I thought (better hoped) it would go like this:
#Configuration
public class MyApplicationConfig {
#Bean // this bean should be a mock
SomeService getSomeService() {
return new MockUp<SomeService>() {#Mock String someMethod() { return ""; }}.getMockInstance();
}
#Bean // some other bean that depends on the mocked service bean
MyApplication getMyApplication(SomeService someService) {
....
}
}
But unfortunatly this fails with "Invalid place to apply a mock-up".
I wonder if I can generate jmockit mocks inside Spring Configuration classes at all. I need the bean because it is referenced by other beans and the whole Spring Context initialization fails if I do not provide the mock as a Spring bean.
Thanks for any help.
Just use your regular Spring configuration. In a test class, declare the type to be mocked with #Capturing. It will mock whatever the implementation class that Spring used.
Edit: added full example code below.
import javax.inject.*;
public final class MyApplication {
private final String name;
#Inject private SomeService someService;
public MyApplication(String name) { this.name = name; }
public String doSomething() {
String something = someService.doSomething();
return name + ' ' + something;
}
}
public final class SomeService {
public String getName() { return null; }
public String doSomething() { throw new RuntimeException(); }
}
import org.springframework.context.annotation.*;
#Configuration
public class MyRealApplicationConfig {
#Bean
SomeService getSomeService() { return new SomeService(); }
#Bean
MyApplication getMyApplication(SomeService someService) {
String someName = someService.getName();
return new MyApplication(someName);
}
}
import javax.inject.*;
import org.junit.*;
import org.junit.runner.*;
import static org.junit.Assert.*;
import mockit.*;
import org.springframework.test.context.*;
import org.springframework.test.context.junit4.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = MyRealApplicationConfig.class)
public final class MyApplicationSpringTest {
#Inject MyApplication myApplication;
#Mocked SomeService mockService;
#BeforeClass // runs before Spring configuration
public static void setUpMocksForSpringConfiguration() {
new MockUp<SomeService>() {
#Mock String getName() { return "one"; }
};
}
#Test
public void doSomethingUsingMockedService() {
new Expectations() {{ mockService.doSomething(); result = "two"; }};
String result = myApplication.doSomething();
assertEquals("one two", result);
}
}
import org.junit.*;
import static org.junit.Assert.*;
import mockit.*;
// A simpler version of the test; no Spring.
public final class MyApplicationTest {
#Tested MyApplication myApplication;
#Injectable String name = "one";
#Injectable SomeService mockService;
#Test
public void doSomethingUsingMockedService() {
new Expectations() {{ mockService.doSomething(); result = "two"; }};
String result = myApplication.doSomething();
assertEquals("one two", result);
}
}
Spring-ReInject is designed to replace beans with mocks.

Resources