Dependency injection into Logback Appenders with Spring Boot - spring-boot

I'm using Spring Boot 1.5.2 with Logback, which is configured using a logback-spring.xml. There, I define an appender of a custom type (subclass of RollingFileAppender) and would like to get a pair of beans injected.
Is this possible? I naively tried annotating the appender #Component etc. but as it is created by Logback/Joran, it of course doesn't work. Is there a trick I can apply?
If not possible, what would be the canonical way of achieving my goal (inserting beans from the application context into an appender)?

As mentioned also in the question, by default, Logback instantiates and manages the lifecycle of different logging components (appenders, etc) itself. It knows nothing of Spring. And, Logback typically configures itself way before Spring is started (as Spring also uses it for logging).
So, you cannot really use Spring to configure an instance of FileAppender (or some other rather fundamental appender) and then inject that into Logback.
However, in case your appender is not truly fundamental (or you are happy to ignore logging events during Spring Boot startup), you can follow the "simple" approach below. In case you would like to capture all events (including the ones during startup), keep on reading.
Simple approach (loses events during startup)
Create your appender as a Spring component:
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import org.springframework.context.SmartLifecycle;
import org.springframework.stereotype.Component;
#Component
public class LogbackCustomAppender extends UnsynchronizedAppenderBase<ILoggingEvent> implements SmartLifecycle {
#Override
protected void append(ILoggingEvent event) {
// TODO handle event here
}
#Override
public boolean isRunning() {
return isStarted();
}
}
As you can see, it is annotated with #Component so that Spring will pick it up during classpath scanning. Also, it implements SmartLifecycle so that Spring will call Logback Lifecycle interface methods (luckily, start() and stop() methods have exactly the same signature, so we only need to implement isRunning() which will delegate to Logback isStarted()).
Now, by the end of Spring application context startup, we can retrieve the fully initialized LogbackCustomAppender instance. But Logback is blissfully unaware of this appender, so we need to register it with Logback.
One way of doing this is within your Spring Boot Application class:
#SpringBootApplication
#ComponentScan(basePackages = {"net.my.app"})
public class CustomApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(CustomApplication.class, args);
context.start();
addCustomAppender(context, (LoggerContext) LoggerFactory.getILoggerFactory());
}
private static void addCustomAppender(ConfigurableApplicationContext context, LoggerContext loggerContext) {
LogbackErrorCollector customAppender = context.getBean(LogbackCustomAppender.class);
Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.addAppender(customAppender);
}
}
No need to change anything in your Logback configuration file.
More complicated approach (captures all events)
As mentioned above, you might be interested in not losing the events logged during Spring Boot startup.
For this, you could implement a placeholder appender (that would buffer startup events internally):
import ch.qos.logback.core.Appender;
import ch.qos.logback.core.UnsynchronizedAppenderBase;
import java.util.ArrayList;
public class BufferingAppenderWrapper<E> extends UnsynchronizedAppenderBase<E> {
private final ArrayList<E> eventBuffer = new ArrayList<>(1024);
private Appender<E> delegate;
#Override
protected void append(E event) {
synchronized (eventBuffer) {
if (delegate != null) {
delegate.doAppend(event);
}
else {
eventBuffer.add(event);
}
}
}
public void setDelegateAndReplayBuffer(Appender<E> delegate) {
synchronized (eventBuffer) {
this.delegate = delegate;
for (E event : this.eventBuffer) {
delegate.doAppend(event);
}
this.eventBuffer.clear();
}
}
}
We register that appender with Logback the usual way (e.g. logback.xml):
<appender name="DELEGATE" class="my.app.BufferingAppenderWrapper" />
<root level="INFO">
<appender-ref ref="DELEGATE" />
</root>
After Spring has started, look that appender up by name and register your Spring-configured appender with the placeholder (flushing the buffered events in the process):
#SpringBootApplication
#ComponentScan(basePackages = {"net.my.app"})
public class CustomApplication {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(CustomApplication.class, args);
context.start();
addCustomAppender(context, (LoggerContext) LoggerFactory.getILoggerFactory());
}
private static void addCustomAppender(ConfigurableApplicationContext context, LoggerContext loggerContext) {
LogbackErrorCollector customAppender = context.getBean(LogbackCustomAppender.class);
Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
BufferingAppenderWrapper<ILoggingEvent> delegate = (BufferingAppenderWrapper<ILoggingEvent>) rootLogger.getAppender("DELEGATE");
delegate.setDelegateAndReplayBuffer(customAppender);
}
}
LogbackCustomAppender stays the same.

It isn't possible to do what you are trying to do. Logback is initialised before the application context is created so there's nothing to perform the dependency injection.
Perhaps you could ask another question describing what you'd like your appender to be able to do? There may be a solution that doesn't involve injecting Spring-managed beans into it.

Using logback-extensions you can create your appenders in a spring application context file or in a spring config factory.

Try defining a bean like this and calling the static getBean method on it, instead of using dependency injection:
#Component
public class BeanFinderGeneral implements ApplicationContextAware {
private static ApplicationContext applicationContext;
public static <T> T getBean(Class<T> clazz) {
return applicationContext.getBean(clazz);
}
#Override
public void setApplicationContext(ApplicationContext pApplicationContext) throws BeansException {
applicationContext = pApplicationContext;
}
}

In Spring boot you can write a configuration class and create a Bean of your logback class as below:
#Component
#Configuration
public class LogBackObjectBuilder {
#Bean
public RollingFileAppender myRollingFileAppender() {
return new YOUR-SUB-CLASS-OF-RollingFileAppender();
}
}
Just having this class scanned by spring will cause this Bean to be created and injected in the context.
I hope I understood your question right. You want your custom appender to be injected in the application context.

Related

Dependency injection in custom evaluator in logback in spring boot

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");
}
}
}

Receiving Objects from the IoC Container - Spring

have have these two apps which actually do the same (if I am correct)
#SpringBootApplication
public class DemoApplication {
#Autowired
HelloWorld helloWorld;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public CommandLineRunner run() {
helloWorld.setMessage("wow");
return (load) -> {
helloWorld.getMessage();
};
}
}
and
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new
ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
both uses
#Component
public class HelloWorld {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("Your Message : " + message);
}
}
The only difference at the helloWord obj is, that if I use the MainApp-class in my program, then the helloWorld class doesn't need the #Component annotation.
My Question:
If I am correct the SpringBoot annotation makes it unnecessary to define a ClassPathXMLApplicationContext. #Autowire does that for me.
I am now interested if I AutoWire lets say 100 objects at the beginning, all these objects are now in the IoC container correct?
If so: Is not possible to just hand out that container in a CTOR of another class and have access to all saved objects there like:
(HelloWorld) context.getBean("helloWorld"); or
(someRandomClass) context.getBean("someRandomClass")
public CTOR(IOCContainer container) {
this.container = container;
}
Instead of that implementation
public CTOR(HelloWorld helloWorld, SomeRandomClass someRandomClass) {
this.helloWorld = helloWorld;
this.someRandomClass = someRandomClass;
}
And if that is possible, how can I do that?
(There is no use case/task behind my question, i am just interested if that is possible)
The XML'ish way of configuration where you define your bean and wiring via
<bean ... etc. pp.
can be completely replaced by either using
#Component
public class MyClass ....
or by
#Bean
public MyClass myClass() {return new MyClass();}
definition in a configuration class. Both ways place the entity in the IoC container of Spring.
The #Autowire just informs the IoC container of Spring that you would like to have a bean fulfilling the contract of the entity marked with #Autowire injected into this place.
In order to get access to the container you just need to inject the ApplicationContext where you would like to have it.
There are two ways of creating beans in Spring. One is through XML config and the other is through annotation config. Annotation config is the preferred approach as it has lot of advantages over xml config.
Spring boot doesnt have any thing to do with annotation or xml config. Its just a easy way to boot spring application. #Component creates the object of the annotated bean in the application context. #Import or #ImportResource are the annotations used to load the configs from Annotations or through XML configs in Spring boot. With Spring boot u need not create ClassPathXMlCOntext or AnnotationContext objects, but its created internally by spring boot.
#Autowired is a way of getting the beans into any object by injecting rather than tight coupling to the code. Spring container(Application context) do this job of injecting. Just autowiring any class wont create the objects in Spring context. Its just an indication for the Spring context to set the object in the Application context here. You need to create them explicitly inside a xml config/ or annotations like #Component #Service others.
There is no need of hand out of container anywhere. U can just #Autowire ApplicationContext context; in any other spring bean object. With which you can call getBean(YourBean.class) to get that bean.

Print all the Spring beans that are loaded - Spring Boot

How can I get to know names of all the beans that are loaded as part of my spring boot app? I would like have some code in main method to print the details of beans that are loaded once the server is started up.
As shown in the getting started guide of spring-boot: https://spring.io/guides/gs/spring-boot/
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Bean
public CommandLineRunner commandLineRunner(ApplicationContext ctx) {
return args -> {
System.out.println("Let's inspect the beans provided by Spring Boot:");
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.sort(beanNames);
for (String beanName : beanNames) {
System.out.println(beanName);
}
};
}
}
As #Velu mentioned in the comments, this will not list manually registered beans.
In case you want to do so, you can use getSingletonNames(). But be careful. This method only returns already instantiated beans. If a bean isn't already instantiated, it will not be returned by getSingletonNames().
May I suggest using Actuator? it provides several endpoints including /beans which lists all beans in the application. You say "once the server is started" so this is an option for web applications.
To set up actuator
https://spring.io/guides/gs/actuator-service/
List of endpoints in actuator
http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html
Well, Although, this question is already answered, I still want to provide an answer which is a Java 8 variant :)
Arrays.asList(context.getBeanDefinitionNames()).stream().sorted().forEach(System.out::println);
Lets do Java 8 !!!
Actually I would recommend to create this class aside from modifying your #SpringBootApplication.
#Component
public class ContextTeller implements CommandLineRunner {
#Autowired
ApplicationContext applicationContext;
#Override
public void run(String... args) throws Exception {
System.out.println("-------------> just checking!");
System.out.println(Arrays.asList(applicationContext.getBeanDefinitionNames()));
}}
This way Spring Boot will load this class and execute just after loading context. Then you just can remove the file, and everything is clear.
applicationContext.getBeanDefinitionNames() does not show the beans which are registered without BeanDefinition instance.
For spring boot web applications, all the beans can be listed using the below endpoint.
#RestController
#RequestMapping("/list")
class ExportController {
#Autowired
private ApplicationContext applicationContext;
#GetMapping("/beans")
#ResponseStatus(value = HttpStatus.OK)
String[] registeredBeans() {
return printBeans();
}
private String[] printBeans() {
AutowireCapableBeanFactory autowireCapableBeanFactory = applicationContext.getAutowireCapableBeanFactory();
if (autowireCapableBeanFactory instanceof SingletonBeanRegistry) {
String[] singletonNames = ((SingletonBeanRegistry) autowireCapableBeanFactory).getSingletonNames();
for (String singleton : singletonNames) {
System.out.println(singleton);
}
return singletonNames;
}
return null;
}
}
[
"autoConfigurationReport",
"springApplicationArguments",
"springBootBanner",
"springBootLoggingSystem",
"environment",
"systemProperties",
"systemEnvironment",
"org.springframework.context.annotation.internalConfigurationAnnotationProcessor",
"org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory",
"org.springframework.boot.autoconfigure.condition.BeanTypeRegistry",
"org.springframework.context.annotation.ConfigurationClassPostProcessor.importRegistry",
"propertySourcesPlaceholderConfigurer",
"org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor.store",
"preserveErrorControllerTargetClassPostProcessor",
"org.springframework.context.annotation.internalAutowiredAnnotationProcessor",
"org.springframework.context.annotation.internalRequiredAnnotationProcessor",
"org.springframework.context.annotation.internalCommonAnnotationProcessor",
"org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor",
"org.springframework.scheduling.annotation.ProxyAsyncConfiguration",
"org.springframework.context.annotation.internalAsyncAnnotationProcessor",
"methodValidationPostProcessor",
"embeddedServletContainerCustomizerBeanPostProcessor",
"errorPageRegistrarBeanPostProcessor",
"messageSource",
"applicationEventMulticaster",
"org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration$EmbeddedTomcat",
"tomcatEmbeddedServletContainerFactory",
"org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration$TomcatWebSocketConfiguration",
"websocketContainerCustomizer",
"spring.http.encoding-org.springframework.boot.autoconfigure.web.HttpEncodingProperties",
"org.springframework.boot.autoconfigure.web.HttpEncodingAutoConfiguration",
"localeCharsetMappingsCustomizer",
"org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration",
"serverProperties",
"duplicateServerPropertiesDetector",
"spring.resources-org.springframework.boot.autoconfigure.web.ResourceProperties",
"org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$DefaultErrorViewResolverConfiguration",
"conventionErrorViewResolver",
"org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration",
"errorPageCustomizer",
"servletContext",
"contextParameters",
"contextAttributes",
"spring.mvc-org.springframework.boot.autoconfigure.web.WebMvcProperties",
"spring.http.multipart-org.springframework.boot.autoconfigure.web.MultipartProperties",
"org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration",
"multipartConfigElement",
"org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration$DispatcherServletRegistrationConfiguration",
"org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration$DispatcherServletConfiguration",
"dispatcherServlet",
"dispatcherServletRegistration",
"requestContextFilter",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration",
"hiddenHttpMethodFilter",
"httpPutFormContentFilter",
"characterEncodingFilter",
"org.springframework.context.event.internalEventListenerProcessor",
"org.springframework.context.event.internalEventListenerFactory",
"reportGeneratorApplication",
"exportController",
"exportService",
"org.springframework.boot.autoconfigure.AutoConfigurationPackages",
"org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$Jackson2ObjectMapperBuilderCustomizerConfiguration",
"spring.jackson-org.springframework.boot.autoconfigure.jackson.JacksonProperties",
"standardJacksonObjectMapperBuilderCustomizer",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperBuilderConfiguration",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration",
"jsonComponentModule",
"jacksonObjectMapperBuilder",
"org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration$JacksonObjectMapperConfiguration",
"jacksonObjectMapper",
"org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration",
"org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration",
"org.springframework.boot.autoconfigure.web.DispatcherServletAutoConfiguration",
"org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration",
"defaultValidator",
"org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration",
"error",
"beanNameViewResolver",
"errorAttributes",
"basicErrorController",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$EnableWebMvcConfiguration",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter",
"mvcContentNegotiationManager",
"org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration$StringHttpMessageConverterConfiguration",
"stringHttpMessageConverter",
"org.springframework.boot.autoconfigure.web.JacksonHttpMessageConvertersConfiguration$MappingJackson2HttpMessageConverterConfiguration",
"mappingJackson2HttpMessageConverter",
"org.springframework.boot.autoconfigure.web.HttpMessageConvertersAutoConfiguration",
"messageConverters",
"mvcConversionService",
"mvcValidator",
"requestMappingHandlerAdapter",
"mvcResourceUrlProvider",
"requestMappingHandlerMapping",
"mvcPathMatcher",
"mvcUrlPathHelper",
"viewControllerHandlerMapping",
"beanNameHandlerMapping",
"resourceHandlerMapping",
"defaultServletHandlerMapping",
"mvcUriComponentsContributor",
"httpRequestHandlerAdapter",
"simpleControllerHandlerAdapter",
"handlerExceptionResolver",
"mvcViewResolver",
"org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter$FaviconConfiguration",
"faviconRequestHandler",
"faviconHandlerMapping",
"defaultViewResolver",
"viewResolver",
"welcomePageHandlerMapping",
"org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration",
"objectNamingStrategy",
"mbeanServer",
"mbeanExporter",
"org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration",
"springApplicationAdminRegistrar",
"org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration",
"org.springframework.boot.autoconfigure.web.JacksonHttpMessageConvertersConfiguration",
"spring.info-org.springframework.boot.autoconfigure.info.ProjectInfoProperties",
"org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration",
"multipartResolver",
"org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration$RestTemplateConfiguration",
"restTemplateBuilder",
"org.springframework.boot.autoconfigure.web.WebClientAutoConfiguration",
"spring.devtools-org.springframework.boot.devtools.autoconfigure.DevToolsProperties",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$RestartConfiguration",
"fileSystemWatcherFactory",
"classPathRestartStrategy",
"classPathFileSystemWatcher",
"hateoasObjenesisCacheDisabler",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$LiveReloadConfiguration$LiveReloadServerConfiguration",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration$LiveReloadConfiguration",
"optionalLiveReloadServer",
"org.springframework.boot.devtools.autoconfigure.LocalDevToolsAutoConfiguration",
"lifecycleProcessor"
]
As you can see in the output, environment, systemProperties, systemEnvironment beans will not be shown using context.getBeanDefinitionNames() method.
#Component
public class ContextTeller implements CommandLineRunner {
#Autowired
public ApplicationContext applicationContext;
#Override
public void run(String... args) throws Exception {
System.out.println("<------------- Beans loaded --------------->");
Arrays.asList(applicationContext.getBeanDefinitionNames()).stream().forEach(System.out::println);
}
}
As mentioned by #Zergleb, using Actuator is appropriate too, however, based on the reference documentation this endpoint is no longer exposed via "web" by default. Thus you need to consider the following steps to access the endpoint
Add the below dependency to your pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Add these two propertiers to your application.properties file:
management.endpoints.web.exposure.include=beans
management.endpoint.beans.enabled=true
Access to your application context beans using /actuator/beans http endpoint
I did one small experiment for this requirement and found this solution. I have created SpringBoot while selecting the modules like WEB, Actuator, HAL and Devtools. I have configured the below property in application properties to load all the endpoints present in actuator.
management.endpoints.web.exposure.include=*
You can see actuator information in, http://localhost:8080/actuator. This will show all the application information along with actuator, status, info,etc.. In that, you can find the http://localhost:8080/actuator/beans which will load all the beans internally created by springboot application.
Once you are able to see all the beans information, I think it's not necessary to print again in main class.
Since, I have already configured rest-hal-browser dependency in my application, when I load the URL of http://localhost:8080, will load the UI to search for different endpoints. In the below image I am searching for actuator information.
Simple way to get all beans
ApplicationContext ctx =SpringApplication.run(DemoApplication.class, args);
String [] beans = ctx.getBeanDefinitionNames();
Arrays.sort(beans);
System.out.println("#############");
for(String s:beans) {
System.out.println(s + " of type " + ctx.getBean(s).getClass());
}

Manually loading application context to write getBean() in spring boot application

In a spring application, we write like this to get a bean through manually loading spring application context.
ApplicationContext context = new ClassPathXmlApplicationContext("path/to/applicationContext.xml");
JobLauncher launcher=(JobLauncher)context.getBean("launcher");
How to do the similar thing in spring boot ?
Being a newbie...need help
#SpringBootApplication
public class Application {
public static void main(String[] args) throws Exception {
ApplicationContext app = SpringApplication.run(Application .class, args);//init the context
SomeClass myBean = app.getBean(SomeClass.class);//get the bean by type
}
#Bean // this method is the equivalent of the <bean/> tag in xml
public SomeClass getBean(){
return new SomeClass();
}
#Bean
public MyUtilClass myUtil(SomeClass sc){
MyUtilClass uc = new MyUtilClass();
uc.setSomeClassProp(sc);
return uc;
}
}
You can also your xml file to declare the beans instead of the java config, just use #ImportResource({"classpath*:applicationContext.xml"})
Edit: To answer the comment: Make the util class a spring bean(using #Component annotation and component scan or the same as SomeClass shown above) and then you can #Autowire the bean you like. Then when you want to use the Util class just get it from the context.

How to Initialize Jersey Application (ResourceConfig) With Spring?

I'm using Jersey 2 and Spring, and I'm trying to initialize my Jersey application (i.e. the class derived from ResourceConfig) with parameters from the Spring context.
Background: I have a single Jersey application that I build (i.e. a single WAR) and I deploy it across a server cluster with different Spring configurations on different servers to enable or disable different parts of the server, e.g. some of the servers have /search resources turned on, etc. This was really easy in Jersey 1.0: I just put,
<context:component-scan base-package="com.mycompany.resources.search"/>
in a Spring config to have Jersey scan that particular package and enable the JAX-RS resource providers in it.
Now in Jersey 2.0 the Spring <context:component-scan ... /> doesn't work, so resources have to be programmatically registered in a startup class derived from ResourceConfig:
public class MyApplication extends ResourceConfig {
public MyApplication() {
packages("com.mycompany.resources.search");
}
}
So far so good, but I need to conditionally scan that package, and I can't figure out how to get any Spring configuration into the MyApplication class. I thought that constructor injection might work:
public class MyApplication extends ResourceConfig {
#Autowired
public MyApplication(#Qualifier("my-config") MyConfiguration myConfiguration) {
if (myConfiguration.isEnabled()) {
packages("com.mycompany.resources.search");
}
}
}
However HK2 complains that it can't find a default constructor to use... so this indicates to me that DI is in play in the construction of this class, but that the DI isn't using Spring.
Similarly, using the the Spring bean lifecycle doesn't work:
public class MyApplication extends ResourceConfig implements InitializingBean {
#Autowired
private MyConfiguration myConfiguration;
public MyApplication() {
}
#Override
public void afterPropertiesSet() throws Exception {
if (myConfiguration.isEnabled()) {
packages("com.mycompany.resources.search");
}
}
}
(The afterPropertiesSet method isn't called.)
So now I'm stuck: is there any way to configure a Jersey ResourceConfig application object using Spring?
UPDATE:
I accepted #JohnR's answer below but I'll also include my eventual solution which I think is a bit cleaner. #JohnR's answer was to have the object initialized twice: first by Spring and then by Jersey/HK2. When Spring initializes the object you cache the dependencies in a static member, and then when Jersey/HK2 initializes it later you can retrieve the dependencies.
I ended up doing this:
public class MyApplication extends ResourceConfig {
public MyApplication() {
ApplicationContext rootCtx = ContextLoader.getCurrentWebApplicationContext();
MyConfiguration myConfiguration = rootCtx.getBean(MyConfiguration.class);
if (myConfiguration.isEnabled()) {
packages("com.mycompany.resources.whatever");
}
}
}
Rather than having the object initialized twice, we let Jersey/HK2 initialize it but then we retrieve the dependencies from Spring.
Both solutions are vulnerable to timing: they both assume that Spring is initialized before Jersey/HK2.
Expanding on my previous comment:
Trying to extend ResourceConfig is dangerous if you don't know what you're doing. Jersey becomes unpredictable, and if you try to subclass it into an Abstract class, Jersey crashes.
Instead, the JAX-RS specification provides us with a very useful interface called Feature: It allows you to register any classes you want as if you were configuring your own application. Furthermore, you don't need to use the awkward AbstractBinder, you just specify what contracts you register your classes with.
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import javax.ws.rs.container.DynamicFeature;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Feature;
import javax.ws.rs.core.FeatureContext;
// Don't use #Component here, we need to inject the Spring context manually.
public class MySpringFeature implements Feature {
#Context
private ServletContext servletContext;
private ApplicationContext applicationContext;
#Autowired
private MySecurityDAO mySecurityDAO;
#Autowired
private MySpringResponseFilter myResponseFilter;
#Override
public boolean configure(FeatureContext context) {
if(this.servletContext == null) {
return false; // ERROR!
}
this.applicationContext = WebApplicationContextUtils.getWebApplicationContext(servletContext);
if(this.applicationContext == null) {
return false; // ERROR!
}
// This is where the magic happens!
AutowireCapableBeanFactory bf = applicationContext.getAutowireCapableBeanFactory();
bf.autowireBean(this);
// From here you can get all the beans you need
// Now we take a Spring bean instance,
// and register it with its appropriate JAX-RS contract
context.register(myResponseFilter, ContainerResponseFilter.class);
// Or, we could do this instead:
SomeSecurityFilter mySecurityFilter = new SomeSecurityFilter();
mySecurityFilter.setSecurityDAO(mySecurityDAO);
context.register(mySegurityFilter, ContainerRequestFilter.class);
// Or even this:
SomeOtherSpringBean someOtherBean = applicationContext.getBean(SomeOtherSpringBean.class);
context.register(someOtherBean, SomeOtherJerseyContract.class);
// Success!
return true;
}
}
And in your ResourceConfig:
public class MyApplication extends ResourceConfig() {
public MyApplication() {
register(MySpringFeature.class);
}
}
Ta-da!
So now I'm stuck: is there any way to configure a Jersey
ResourceConfig application object using Spring?
I don't think you can configure Jersey to obtain your ResourceConfig from Spring as a Spring managed bean. It's a bit hackish, but you could do something like this. Note that you'll end up with two instance of your ResourceConfig: one managed by Spring and another by Jersey:
public class MyApplication extends ResourceConfig {
// static, available to all instances
private static MyConfiguration myConfiguration;
public MyApplication() {
// when Spring creates the first instance of MyApplication, myConfiguration
// will be null because the setter wasn't called yet
if (myConfiguration != null)
{
// second instance created by jersey... Spring will have autowired
// the first instance, and myConfiguration is static
if (myConfiguration.isEnabled())
packages("com.mycompany.resources.search");
}
}
#Autowired
public void setMyConfiguration(MyConfiguration config)
{
// instance level setter saves to a static variable to make it available for
// future instances (i.e. the one created by jersey)
MyApplication.myConfiguration = config;
}
}
Again, this is fairly hackish. You'll want to make sure Spring is initialized before Jersey and look closely at any threading issues that could occur during initialization.

Resources