Tomcat 8, Spring Boot, #Configurable LoadTimeWeaving without -javaagent? - spring-boot

I'm trying to setup a #Configurable domain object(not managed by the spring container).
I've got this working by adding the -javaagent:path/to/spring-instrument.jar as a JVM argument but it's not 100% clear to me whether or not this -javaagent MUST be in place. I'm running this on Tomcat 8. I may be misinterpreting the documentation but it seems I may be able to use a another mechanism to accomplish this, in particular this line:
Do not define TomcatInstrumentableClassLoader anymore on Tomcat 8.0 and higher. Instead, let Spring automatically use Tomcat’s new native InstrumentableClassLoader facility through the TomcatLoadTimeWeaver strategy.
Code Samples below:
#SpringBootApplication
#EnableLoadTimeWeaving
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean
public MyService myService(){
return new MyService();
}
}
#Configurable
public class MyDomainObject {
#Autowired
private MyService myService;
public MyService getMyService(){
return myService;
}
}
public class MyService {
private static final Logger log = LoggerFactory.getLogger(MyService.class);
public void test(){
log.info("test");
}
}
So is there a way to get these #Configrable objects woven without specifying the -javaagent? I'd be interested in learning if I can accomplish this when deploying as WAR to a Standalone Tomcat 8 server and/or using the embedded Tomcat 8 server when launching as a 'fat' jar.
As it stands deploying to Stand alone Tomcat 8 server doesn't throw an error but the getMyService() method above returns null. Launching as a fat jar throws the following error during startup:
Caused by: java.lang.IllegalStateException: ClassLoader [sun.misc.Launcher$AppClassLoader] does NOT provide an 'addTransformer(ClassFileTransformer)' method. Specify a custom LoadTimeWeaver or start your Java virtual machine with Spring's agent: -javaagent:org.springframework.instrument.jar
I suppose the real question is how do I Specify a custom LoadTimeWeaver in Tomcat 8? Nothing seems to be automatically happening as the documentation states but again I may be misinterpreting what that means exactly.

you can try this :
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() {
return new InstrumentationLoadTimeWeaver();
}
or
there is a new library that just solves to dynamically setup spring InstrumentationLoadTimeWeaver to enable support for aspects without having to start the JVM with an explicit java agent
<dependency>
<groupId>de.invesdwin</groupId>
<artifactId>invesdwin-instrument</artifactId>
<version>1.0.2</version>
</dependency>
#SpringBootApplication
#EnableLoadTimeWeaving
public class TestApplication{
public static void main(final String[] args) {
DynamicInstrumentationLoader.waitForInitialized(); //dynamically attach java agent to jvm if not already present
DynamicInstrumentationLoader.initLoadTimeWeavingContext(); //weave all classes before they are loaded as beans
SpringApplication.run(TestApplication.class, args); //start application, load some classes
}
}

what about creating your own annotation #MyConfigurable ? so you can do what ever you like when it's methods are called.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target(ElementType.CONSTRUCTOR)
#Retention(RetentionPolicy.RUNTIME)
public #interface MyConfigurable
{}

Related

How to use load time weaving without -javaagent?

I am trying to enable loadtimeweaving without javaagent jar files of aspectweaver and spring-instrument. This what I have implemented to achieve the same But it's not working.
#ComponentScan("com.myapplication")
#EnableAspectJAutoProxy
#EnableSpringConfigured
#EnableLoadTimeWeaving(aspectjWeaving = EnableLoadTimeWeaving.AspectJWeaving.AUTODETECT)
public class AopConfig implements LoadTimeWeavingConfigurer {
#Override
public LoadTimeWeaver getLoadTimeWeaver() {
return new ReflectiveLoadTimeWeaver();
}
/**
* Makes the aspect a Spring bean, eligible for receiving autowired components.
*/
#Bean
public InstrumentationLoadTimeWeaver loadTimeWeaver() throws Throwable {
InstrumentationLoadTimeWeaver loadTimeWeaver = new InstrumentationLoadTimeWeaver();
return loadTimeWeaver;
}
}
A workaround I found was to hot-attach InstrumentationSavingAgent from spring-instrument instead of starting the agent via -javaagent command line parameter. But for that you need an Instrumentation instance. I just used the tiny helper library byte-buddy-helper (works independently of ByteBuddy, don't worry) which can do just that. Make sure that in Java 9+ JVMs the Attach API is activated if for some reason this is not working.
So get rid of implements LoadTimeWeavingConfigurer and the two factory methods in your configuration class and just do it like this:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument</artifactId>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>1.10.14</version>
</dependency>
#SpringBootApplication
public class Application {
public static void main(String[] args) {
Instrumentation instrumentation = ByteBuddyAgent.install();
InstrumentationSavingAgent.premain("", instrumentation);
ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
// ...
}
}
Feel free to ask follow-up questions if there is anything you do not understand.
Update: One more thing I noticed is that this only works for me with aspectjWeaving = ENABLED, not with AUTODETECT. And for one sample Spring bean I noticed that #Component did not work, probably because of some bootstrapping issue between Spring vs AspectJ. Hence, I replaced it by an explicit #Bean configuration, then it worked. Something like this:
#Configuration
#ComponentScan("com.spring.aspect.dynamicflow")
#EnableLoadTimeWeaving(aspectjWeaving = ENABLED)
public class ApplicationConfig {
#Bean
public JobProcess jobProcess() {
return new JobProcessImpl();
}
}

Dependency injection into Logback Appenders with 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.

How to use CDI into JAX-RS client

I have searched a while on SO and official documentation but I cannot found a way to use directly CDI injection into a JAX-RS client.
I retrieve a client using the builder method and I want to register a WriterInterceptor (or any filter like component) which uses injection to retrieve another bean.
I want to use CDI injection and avoid registering each bean with HK2.
ClientBuilder.newBuilder()
.register(MyWriter.class)
.build();
And MyWriter with the injected class.
#Provider
public class MyWriter implements WriterInterceptor {
private final MyRepo repo;
#Inject
public MyWriter(MyRepo repo) {
this.repo = repo;
}
#Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
context.proceed();
}
}
public class MyRepo {
}
I am running in an embedded jetty with Jersey 2 and Weld SE.
Its possible to inject in java se application using wield .
#Singleton
public class Application {
private static Logger logger = LoggerFactory.getLogger(Application.class);
#inject
private SomeOtherBean injectedBean;
public void run() {
logger.debug("application initialized");
injectedBean.doSomething();
}
}
inside main initialize weild
import java.io.IOException;
import org.jboss.weld.environment.se.Weld;
import org.jboss.weld.environment.se.WeldContainer;
public class EntryPoint {
public static void main(String[] args) throws IOException {
Weld weld = new Weld();
WeldContainer container = weld.initialize();
Application application = container.instance().select(Application.class).get();
application.run();
weld.shutdown();
}
}
Have a look at below doc
https://docs.jboss.org/weld/reference/latest/en-US/html/environments.html#_java_se
also below tutorial
https://randling.wordpress.com/2011/08/15/cdi-in-java-se/
If I understand everything correctly, this has already been asked and answered. In a nutshell: you have to override the default behaviour of the H2K Binder, so it reaches for the Weld Bean Manager. You don't have to register every Bean with H2K later on.
Edit: to contain everything in the post, so you don't have to read the comments:
The linked answer is for the server-side, not the client.
With standard tools (Jersey Client-side injection providers and the Weld bridge), it seems to be a too big overhead/impossible to do
Apparently in the Dropwizard project they managed to do custom client-side injection.

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

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