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");
}
}
}
Related
Spring 6, Quartz, and a SimpleTrigger based scheduled task.
#Component
#Slf4j
public class Greeting {
public void sayHello() {
log.debug("Hello at {}:", LocalDateTime.now());
}
}
Quartz config:
#Configuration
class QuartzConfig{
#Bean
MethodInvokingJobDetailFactoryBean greetingJobDetailFactoryBean() {
var jobFactory = new MethodInvokingJobDetailFactoryBean();
jobFactory.setTargetBeanName("greeting");
jobFactory.setTargetMethod("sayHello");
return jobFactory;
}
#Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
SimpleTriggerFactoryBean simpleTrigger = new SimpleTriggerFactoryBean();
simpleTrigger.setJobDetail(greetingJobDetailFactoryBean().getObject());
simpleTrigger.setStartDelay(1_000);
simpleTrigger.setRepeatInterval(5_000);
return simpleTrigger;
}
#Bean
public SchedulerFactoryBean schedulerFactoryBean() {
var factory = new SchedulerFactoryBean();
factory.setTriggers(
simpleTriggerFactoryBean().getObject(),
cronTriggerFactoryBean().getObject()
);
return factory;
}
And I tried to use awaitility to check the invocations.
#SpringJUnitConfig(value = {
QuartzConfig.class,
Greeting.class
})
public class GreetingTest {
#Autowired
Greeting greeting;
Greeting greetingSpy;
#BeforeEach
public void setUp() {
this.greetingSpy = spy(greeting);
}
#Test
public void whenWaitTenSecond_thenScheduledIsCalledAtLeastTenTimes() {
await()
.atMost(Duration.ofSeconds(10))
.untilAsserted(() -> verify(greetingSpy, atLeast(1)).sayHello());
}
}
Running the tests, it is failed.
org.awaitility.core.ConditionTimeoutException: Assertion condition defined as a com.example.demo.GreetingTest
Wanted but not invoked:
greeting.sayHello();
-> at com.example.demo.GreetingTest.lambda$whenWaitTenSecond_thenScheduledIsCalledAtLeastTenTimes$0(GreetingTest.java:36)
Actually, there were zero interactions with this mock.
within 10 seconds.
In the jobDetailFactorBean, I used jobFactory.setTargetBeanName("greeting"); to setup the target beans here, it should pass the Greeting bean directly.
Updated: resolved myself, check here.
You're creating a spy that in no way interacts with the actual code:
#BeforeEach
public void setUp() {
this.greetingSpy = spy(greeting);
}
This would have to be injected into the Spring context as a bean and used everywhere, where greeting is used. Spring actually provides such functionality: #SpyBean.
Instead of autowiring a greeting and wrapping it with a spy that does not interact with anything in the context, replace the #Autowired with #SpyBean annotation. Thanks to that a spy bean will be created and injected within the Spring context:
#SpyBean
Greeting greeting;
I created a commit in GitHub repository, where you can see the whole code - the test passes. I had to add the cronTriggerFactoryBean() method to the configuration as it is omitted in your question.
If you cannot use Spring Boot, you can create the spy within Spring context yourself using configuration:
static class Config {
#Bean
#Primary
Greeting greeting() {
return spy(new Greeting());
}
}
Thanks to that when you inject the bean, it will be possible to act on it with Mockito (remember to include the Config class in the #SpringJUnitConfig annotation).
I created another commit in the GitHub repository - the test passes. You can see the whole code there.
I use spring to inject DemoService has always been null, there is no problem with the filter inject of servlet, in the class of extends TurboFilter, how can I get the DemoService object?
https://stackoverflow.com/questions/30662641/inject-spring-bean-into-custom-logback-filter
I have tried the answer to this connection and did not solve the problem of inject.
public class ErrorLogTurboFilter extends TurboFilter {
#Autowired
private DemoService demoService;
#Override
public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) {
// todo
return FilterReply.NEUTRAL;
}
}
Problem: Logback starts up before the Spring context. Therefore you need to lazy initialize the Filter with the to be injected bean. Apart from that the Filter will not be called as a Spring bean, but as a Turbofilter, that does not know any injections and so on.
What you could try is define that Filter as a Spring bean in your context, that contains the DemoService. Inject the bean via a Setter for the service, but declare the field static, so you are able to access it from the logging context.
Now during the execution you need to check if the static field is already initialized, if so you can use it without a problem.
You are not trying the answer you are quoting, because your extended filter "ErrorLogTurboFilter" does not have a "#Named("errorLogTurboFilter")" which is the standard annotation to make your filter a spring bean.
see : What is javax.inject.Named annotation supposed to be used for?
#markusw According to your prompt, this is my solution,and thank you.
#Configuration
public class WebConfig {
#Bean
public DemoService demoService() {
return new DemoService();
}
}
public class ErrorLogTurboFilter extends TurboFilter {
private ApplicationContext ctx = new AnnotationConfigApplicationContext(WebConfig.class);
private DemoService demoService = ctx.getBean(DemoService.class);
#Override
public FilterReply decide(Marker marker, Logger logger, Level level, String s, Object[] objects, Throwable throwable) {
// todo
return FilterReply.NEUTRAL;
}
}
There have been several arguments around not using ApplicationContext.getBean() to get a bean reference, of which most are based on logic that it violates the principles of Inversion of control.
Is there a way to get reference to prototype scoped bean without calling context.getBean() ?
Consider to use Spring Boot!
Than you can do something like this...
Runner:
#SpringBootApplication
public class Runner{
public static void main(String[] args) {
SpringApplication.run(Runner.class, args);
}
}
Some Controller:
#Controller
public class MyController {
// Spring Boot injecting beans through #Autowired annotation
#Autowired
#Qualifier("CoolFeature") // Use Qualifier annotation to mark a class, if for example
// you have more than one concreate class with differant implementations of some interface.
private CoolFeature myFeature;
public void testFeature(){
myFeature.doStuff();
}
}
Some cool feature:
#Component("CoolFeature") // To identify with Qualifier
public class CoolFeature{
#Autowired
private SomeOtherBean utilityBean;
public void doStuff(){
// use utilityBean in some way
}
}
No XML files to handle.
We can still access context for manual configurations if needed.
Suggested reading:
Spring Boot Reference
Pro Spring Boot
This type of problem can be solved using method injection, which is described in more detail here: https://docs.spring.io/spring/docs/current/spring-framework-reference/html/beans.html#beans-factory-method-injection
This is the most common approach to create prototype bean:
abstract class MyService {
void doSome() {
OtherService otherService = getOtherService();
}
abstract OtherService getOtherService();
}
#Configuration
class Config {
#Bean
public MyService myService() {
return new MyService() {
OtherService getOtherService() {
return otherService();
}
}
}
#Bean
#Scope("prototype")
public OtherService otherService() {
return new OtherService();
}
}
Java 8 added a new feature by which we can provide method implementation in interfaces.
Is there any way in Spring 4 by which we can inject beans in the interface which can be used inside the method body?
Below is the sample code
public interface TestWiring{
#Autowired
public Service service;// this is not possible as it would be static.
//Is there any way I can inject any service bean which can be used inside testWiringMethod.
default void testWiringMethod(){
// Call method of service
service.testService();
}
}
This is a bit tricky but it works if you need the dependency inside the interface for whatever requirement.
The idea would be to declare a method that will force the implemented class to provide that dependency you want to autowire.
The bad side of this approach is that if you want to provide too many dependencies the code won't be pretty since you will need one getter for each dependency.
public interface TestWiring {
public Service getService();
default void testWiringMethod(){
getService().testService();
}
}
public class TestClass implements TestWiring {
#Autowire private Service service;
#Override
public Service getService() {
return service;
}
}
You can created Class utils of application context and use it everywhere even not bean class .
you can have code somethins this :
public class ApplicationContextUtil implements ApplicationContextAware {
private static ApplicationContext applicationContext;
#Override
public void setApplicationContext(ApplicationContext context) {
ApplicationContextUtil.applicationContext = context;
}
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
}
and add this to your spring configuration
<bean class="com.example.ApplicationContextUtil" id="applicationContextUtil"/>
now simple to use when you need :
ApplicationContextUtil.getApplicationContext().getBean(SampleBean.class)
this word in web and simple spring app.
I have a singleton that has a spring injected Dao (simplified below):
public class MyService<T> implements Service<T> {
private final Map<String, T> objects;
private static MyService instance;
MyDao myDao;
public void set MyDao(MyDao myDao) {
this. myDao = myDao;
}
private MyService() {
this.objects = Collections.synchronizedMap(new HashMap<String, T>());
// start a background thread that runs for ever
}
public static synchronized MyService getInstance() {
if(instance == null) {
instance = new MyService();
}
return instance;
}
public void doSomething() {
myDao.persist(objects);
}
}
My spring config will probably look like this:
<bean id="service" class="MyService" factory-method="getInstance"/>
But this will instantiate the MyService during startup.
Is there a programmatic way to do a dependency injection of MyDao into MyService, but not have spring manage the MyService?
Basically I want to be able to do this from my code:
MyService.getInstance().doSomething();
while having spring inject the MyDao for me.
Here is a solution, create a class with a static factory method:
public class MyService {
private static MyService instance;
private MyDao myDao;
public static MyService createInstance(final MyDao myDao) {
instance = new MyService(myDao);
return instance;
}
private MyService(final MyDao myDao) {
this.myDao = myDao;
}
public static synchronized MyService getInstance() {
return instance;
}
public void doSomething() {
// just do it!
myDao.justDoIt();
}
}
and use spring to initilize it:
<bean class="my.path.MyService" factory-method="createInstance" scope="singleton">
<constructor-arg ref="reference.to.myDao" />
</bean>
and now you should be able to do:
MyService.getInstance().doSomething();
without any problems.
If you want a singleton, why not just define that one class in the Spring configs, and it's automatically a singleton (by default).
To avoid initialising at start up, have you looked at Spring lazy initialisation ? Basically you need:
lazy-init="true"
in your bean definition.
As mentioned by others, you should let spring manage your singletons, but if you want to manage them yourself and just let spring inject dependencies, do this:
applicationContext.getAutowireCapableBeanFactory().autowireBean(yourService);
I believe the FactoryBean interface is a good alternative for you. It's a very good choice when you need to execute some initialization logic. For example to start an in memory database or some background processes in separate threads.
You can read more about it in the reference documentation.
An example that demonstrates how I instantiate a database and return a datasource everytime someone wants a bean from the FactoryBean implementation.
#PostConstruct
void init() {
embeddedDatabase = new EmbeddedDatabaseBuilder().addScript(schemaPath)
.addScript(dataPath).setType(embeddedDatabaseType).build();
}
public DataSource getObject() throws Exception {
return embeddedDatabase;
}
This enables loose coupling between the factory logic and the returned object. It's heavily used by the Spring framework internally.
If you want it to be initialized the first time you use it, then set lazy-initialization to true.
Another alternative if you want your code to interact with the Spring container is to create a factory that implements the ApplicationContextAware interface. Then you can do something like this:
myDao = context.getBean(MyDao.class);