I have inherited a springboot application. This application has a service similar to the following:
#Service
public class MyService {
String param1 = "";
String param2 = "";
public void doStuff() {
// do stuff assuming the parameters param1 and param 2 of this autowired service have already been initialized
}
}
, This service is autowired from another service similar to the following;
#Service
public MainService {
#Autowired MyService myService;
public performWork() {
this.myService.doStuff();
}
}
, and finally, the springboot app is similar to the following. The calling of the listen() method happens once the Kafka topic has a message (Kafka is only here relevant here because it kicks off the calling of the autowired services):
#SpringBootApplication
public class MyApplication {
#Autowired
MainService mainService;
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#KafkaListener(topics = "myTopic")
public void listen(String message) {
this.graphicService.performWork();
}
}
Here is my question: What is a proper way to have the parameters param1 and param2 already initialized on the MyService service before its doStuff() method is called?
I would instead NOT use a bean configuration file, but rather have it performed as part of the starting of the springboot app. Any advice is appreciated. Thanks
Per my understanding, you just want to execute initialization statements at your bean's initialization. You can use PostConstruct annotation for any work you need to do as soon as beans are created.
MyService class should look like the following
#Service
public class MyService {
String param1;
String param2;
#PostConstruct
public void postConstructRoutine() {
// initialize parameters
param1 = "some_value";
param2 = "some_other_value";
}
public void doStuff() {
// do stuff
}
}
The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.
You can find more info about the annotation at the documentation.
As a side note, it is always better to use constructor injection instead of Autowired. I would highly recommend it.
Related
I am writing a custom evaluator in which I want to autowire another bean. I am unable to do so as evaluator gets initialized by logger where as beans are initialized by spring context. Below is the sample of my code:
In logback-spring.xml:
<appender name="myAppender" class="ch.qos.logback.classic.net.SMTPAppender">
<evaluator class="com.package.CustomEvaluator">
<marker>FATAL</marker>
<interval>1000000</interval>
</evaluator>
</appender>
My custom evaluator:
#Slf4j
#Component
public class CustomEvaluator extends OnMarkerEvaluator {
#Autowired
private MyService myService;
#Override
public boolean evaluate(ILoggingEvent event) throws EvaluationException {
\\logic goes here
}
}
I am getting object of MyService always as null(which is expected). Is there any work around for this?
It don't think its possible because the Evaluator being an internal logback abstraction is not managed / initialized by spring, so obviously spring can't autowire anything into the evaluator.
In addition note, that logback gets initialized even before application context starts.
Of course you could provide some global holder class for the application context and set the context to it in the main method, and then get the reference to it in this evaluator, something like this:
public class ApplicationContextHolder {
private static ApplicationContext context;
// call it in main() method
public static void setContext(ApplicationContext ctx) {context = ctx;}
public static ApplicationContext getContext() {return context;}
}
class CustomEvaluator extends OnMarkerEvaluator {
public boolean evaluate(ILoggingEvent event) throws EvaluationException {
ApplicationContext ctx = ApplicationContextHolder.getContext();
if(ctx == null) {return false;} // not yet initialized / accessible
MyService myService = ctx.getBean(MyService.class);
}
}
But all-in-all I believe its a very ugly solution.
As a suggestion, I think you should consider refactoring of the logic so that the decision of whether to send an email based on logging event will be taken in the application (which is, I assume, spring boot driven so you have an access to the MyService)
Given the current implementation:
public foo() {
LOGGER.info("This should be sent by email");
}
I suggest a part of application:
#Component
public class MyLogic {
#Autowired MyService myService;
public void foo() {
if(myService.shouldSend()) {
LOGGER.info("This should be sent by email");
}
}
}
This is driving me nuts. I have the following files, it is a very simple setup.
public class MainApp {
public static void main(String[] args) {
//read the spring config java class
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("Config.class");
//System.out.println("Bean names: " + Arrays.toString(context.getBeanNamesForType(AccountDAO.class)));
//get the bean from spring container
AccountDAO accountDAO = context.getBean("accountDAO", AccountDAO.class);
//call the business method
accountDAO.addAccount();
//close the spring context
context.close();
}
}
Config.java:
#Configuration
#ComponentScan("com.aop")
#EnableAspectJAutoProxy
public class Config {
}
LoggingAspectDemo.java:
#Aspect
#Component
public class LoggingAspectDemo {
//this is where we add all our related advices for the logging
//let's start with an #Before advice
#Before("execution(public void addAccount())")
public void beforeAddAccountAdvice() {
System.out.println("\n=======>>>> Executing #Before advice on method addAccount() <<<<========");
}
}
AccountDAO.java
#Component
public class AccountDAO {
public void addAccount() {
System.out.println(getClass() + ": Doing my Db work: Adding an account");
}
}
Everytime I run the MainApp.java, I get:
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'accountDAO' available
at org.springframework.beans.factory.support.DefaultListableBeanFactory.getBeanDefinition(DefaultListableBeanFactory.java:687)
at org.springframework.beans.factory.support.AbstractBeanFactory.getMergedLocalBeanDefinition(AbstractBeanFactory.java:1207)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:284)
All the files are under "com.aop" package so #ComponentScan should be scanning all the components. It looks simple enough but I can't get my hands around the problem, can anyone help me where I am going wrong?
You're invoking the constructor of AnnotationConfigApplicationContext with "Config.class" as String argument, but this constructor is actually for invoking with base packages i.e. the argument must be a package name.
Since you want to use it with the Configuration class, use the constructor which accepts Class instance instead i.e.
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
We are facing a problem with Hystrix Command in a Spring Boot / Cloud microservice. We have a Spring Component containing a method annotated with #RabbitListener. When a new message arrives, the method delegates the invocation to NotificationService::processNotification().
The NotificationService is a bean annotated with #Service. The method processNotification() can request third party applications. We want to wrap the invocation of third party applications using #HystrixCommand to provide fault tolerance, but due to some reasons the Hystrix Command annotated method is not working.
If we invoke a Controller and the Controller delegates the invocation to a Service method, which in turns have a Hystrix Command , everything works perfectly. The only problem with Hystrix Command arises when the microservices consume a messages and it seems to be Hystrix Command doesn’t trigger the fallback method.
Here is the non-working code:
#Component
public class MessageProcessor {
#Autowired
private NotificationService notificationService;
#RabbitListener(queues = "abc.xyz-queue")
public void onNewNotification(String payload) {
this.notificationService.processNotification(payload);
}
}
#Service
public class NotificationService {
public void processNotification(String payload) {
...
this.notifyThirdPartyApp(notificationDTO);
...
}
#HystrixCommand(fallbackMethod = "notifyThirdPartyAppFallback")
public void notifyThirdPartyApp(NotificationDTO notificationDTO) {
//Do stuff here that could fail
}
public void notifyThirdPartyAppFallback(NotificationDTO notificationDTO) {
// Fallbacl impl goes here
}
}
#SpringBootApplication
#EnableCaching
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableRabbit
public class NotificationApplication {
public static void main(String[] args) {
SpringApplication.run(NotificationApplication.class, args);
}
}
I'm not sure about your problem without looking at the code.
As another approach you can take: instead of describing this calls with annotations in your service, just extend HystrixCommand and implement api calling logic in it (read more):
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
#Override
protected String run() {
// a real example would do work like a network call here
return "Hello " + name + "!";
}
}
I am working on a Spring application and for one of my scenarios, I had to write two implementations of a Service class. I would like to know how to autowire this in a service layer based on service id at runtime.
#Autowired
ProductPricing accessProduct;
public static void main(String args[])
{
long productId = serviceDao.getService(site.getSiteID()).getServiceId();
accessProduct.calculatePrice(pricingParam)
}
public class PricingManager1 implements ProductPricing
{
public void calculatePrice(Parmeter pricingParam) {}
}
public class PricingManager2 implements ProductPricing
{
public void calculatePrice(Parmeter pricingParam) {}
}
Now depending on the productId, either the method on PricingManager1 or PricingManager2 will be called. How to achieve the same dynamically? If I autowire the Service classes with qualifier, is there any way we can pass runtime productId as qulifier in main class?
You can achieve the expected output by using #Service and then getting the bean from spring context object in your main() as shown below:
Service Classes:
#Service("pricingManager1")
public class PricingManager1 implements ProductPricing
{
public void calculatePrice(Parmeter pricingParam) {}
}
#Service("pricingManager1")
public class PricingManager2 implements ProductPricing
{
public void calculatePrice(Parmeter pricingParam) {}
}
Main Class:
//No Autowiring
public static void main(String args[])
{
long productId = serviceDao.getService(site.getSiteID()).getServiceId();
//get the spring context object
//get the bean from context
if(productId == condition) {
beanContext.getBean("pricingManager1").calculatePrice(pricingParam)
} else {
beanContext.getBean("pricingManager2").calculatePrice(pricingParam)
}
}
If you are using Spring Boot, you can achieve this by using #ConditionalOnExpression annotation and SpEL along with #Bean.
#ConditionalOnExpression("#{this.productId == 999}")
If you are not using Spring Boot, use #Conditional from Spring 4, but this will need more effort to get the same result.
I have a resource endpoint that injects a #PathParam into constructor, i.e., different instance per #PathParam value. It all works fine in Jetty. But now I'm trying to write unit tests using Jersey Test Framework, and it seems that the test framework only supports one registered endpoint per type.
So if I do something like this:
#Path("/users")
public class MyResource {
public MyResource(#PathParam("userId") int userId) {
}
#Path("{userId}")
public String get() {
}
}
public class MyTest extends JerseyTestNg.ContainerPerClassTest {
#Override
protected Application configure() {
return new ResourceConfig()
.register(new MyResource(1))
.register(new MyResource(2));
}
#Test
public void test2() {
target("/users/1").request().get();
}
#Test
public void test2() {
target("/users/2").request().get();
}
}
I see that both test1 and test2 are invoking the instance of MyResource(1). Is this expected? Is there a solution to invoke the correct instance?
You should register the resource as a class. Jersey will create it for you. And handle all the injections.
"The example I posted is dumbed down. In reality, my resource constructor has another injected object that I need to mock. So how would I specify a mocked object parameter for the constructor?"
You can do something like
#Mock
private Service service;
#Override
public ResourceConfig configure() {
MockitoAnnotations.initMocks(this);
return new ResourceConfig()
.register(MyResource.class)
.register(new AbstractBinder() {
#Override
protected configure() {
bind(service).to(Service.class);
}
});
}
#Test
public void test() {
when(service.getSomething()).thenReturn("Something");
// test
}
Assuming you are already using the built in HK2 DI, and have an #Inject annotation on the constructor of your resource class, this should work. In the AbstractBinder we are making the mock object injectable. So now Jersey can inject it into your resource.
See Also:
Jersey - How to mock service