I am somewhat new to Spring Framework. I have a web application written with Spring (4.2.1). I'm trying to expose metrics using Micrometer library and will be scraping with Prometheus.
The relevant structure of the application is this:
- core-module (JAR)
- webservice-module (WAR)
I created a PrometheusService class which is a bean defined in core-module. Defined inside the bean is the PrometheusMeterRegistry and Counter:
#Service
public class PrometheusService {
private static PrometheusMeterRegistry registry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT);
private static Counter newAssetCounter = Counter
.builder("new_asset_counter")
.description("count of created assets")
.tags("region", "na")
.register(registry);
public PrometheusService() {
new JvmMemoryMetrics().bindTo(registry);
new DiskSpaceMetrics(new File("/")).bindTo(registry);
new ProcessorMetrics().bindTo(registry);
new UptimeMetrics().bindTo(registry);
}
public static PrometheusMeterRegistry getRegistry() {
return registry;
}
public Counter getNewAssetCounter() {
return this.newAssetCounter;
}
}
I created MetricsResource which is an HttpServlet that exposes the /metrics endpoint. When trying to #Autowire the PrometheusService bean, it was always null here. A quick search told me that HttpServlet isn't managed by Spring. If I wanted to #Autowire, I needed to add something like this:
SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(webApplicationContext);
Now, I was able to #Autowire the PrometheusService bean within the Servlet.
The Counter defined in the bean gets incremented within the core-module. The MetricsResource doGet method writes the metrics stored in the PrometheusMeterRegistry.
#WebServlet("/metrics")
public class MetricsResource extends HttpServlet {
private PrometheusService promService; // #Autowired
private PrometheusMeterRegistry registry;
#Override
public void init() throws ServletException {
super.init();
// SpringBeanAutowiringSupport.processInjectionBasedOnCurrentContext(webApplicationContext);
// promService = (PrometheusService) getServletContext().getAttribute("prometheusService");
// WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
// AutowireCapableBeanFactory ctx = context.getAutowireCapableBeanFactory();
// ctx.autowireBean(this);
}
#Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws IOException {
resp.setStatus(HttpServletResponse.SC_OK);
resp.setContentType(TextFormat.CONTENT_TYPE_004);
registry = promService.getRegistry();
Writer writer = resp.getWriter();
try {
registry.scrape(writer);
writer.flush();
} finally {
writer.close();
}
}
}
The problem though, is that the value of the Counter is always 0 at the /metrics endpoint.
No matter if it's #Autowired or if I'm manually trying to get the bean.
How could this be? My PrometheusService bean is a singleton. Even the PrometheusMeterRegistry and the Counter are marked static, so why am I getting a different object in my servlet? After some more searching, I found that Spring will create one singleton bean per container. So what I'm assuming is happening here is there are two containers or contexts. A main application context and a servlet context.
Some things I've tried:
Making PrometheusService implement ApplicationContextAware
Using a ServiceLocator class that implements ApplicationContextAware and returns beans
Adding context-params to web.xml
Using ServletContextAttributeExporter in app-context.xml
Using WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext())
I continue to get a new instance of the object. All I want to do is be able to create and expose custom metrics with Micrometer. Is my approach flawed? How can I access the correct bean from within my HttpServlet?
Spring dependency injection to other instance
http://senthadev.com/sharing-spring-container-between-modules-in-a-web-application.html
Spring dependency injection to other instance
ApplicationContext and ServletContext
Try processInjectionBasedOnServletContext(Object target, ServletContext servletContext).
Related
I have a Java Application running on a JBOSS.
This application gets data from several APIs and shows it on a webpage.
I want to cache the data so it doesn't need to call the APIs every time someone opens my webpage.
I want to cache 5 Lists or Strings for like 5 minutes. Is there any way to do it without a database?
You can use http sesion to persist information
Example
HttpSession misession= request.getSession(true);
Producto miproducto= new Producto(1,"telefono",300);
misession.setAttribute("producto",miproducto);
Link
HttpSession JBoss
Documenation
Jbos
If you are using springboot
You can use a singleton with application annotation
#Configuration
public class AppConfig {
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PrototypeBean prototypeBean() {
return new PrototypeBean();
}
#Bean
public SingletonBean singletonBean() {
return new SingletonBean();
}
}
public class SingletonBean {
// ..
#Autowired
private PrototypeBean prototypeBean;
public SingletonBean() {
logger.info("Singleton instance created");
}
public PrototypeBean getPrototypeBean() {
logger.info(String.valueOf(LocalTime.now()));
return prototypeBean;
}
}
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext context
= new AnnotationConfigApplicationContext(AppConfig.class);
SingletonBean firstSingleton = context.getBean(SingletonBean.class);
PrototypeBean firstPrototype = firstSingleton.getPrototypeBean();
// get singleton bean instance one more time
SingletonBean secondSingleton = context.getBean(SingletonBean.class);
PrototypeBean secondPrototype = secondSingleton.getPrototypeBean();
isTrue(firstPrototype.equals(secondPrototype), "The same instance should be returned");
}
if you are using primer faces, you cna use #ApplicationScoped
#ApplicationScoped
An application-scoped managed bean instance is tied to the lifetime of the web application itself. There’s only one instance of an application-scoped managed bean throughout your web application. It is not the same but similar to a Singleton EJB.
Singleton
I was trying to update the table row data from outside the controller (Inside some threads) and getting 'NullPointerException' always.
Thread code:
public class S3Thread implements Runnable {
#Autowired
private IAutomationService automationService;
#Override
public void run() {
Automation config = new Automation("user1","success");
automationService.updateAutomation(config);
}
}
NullPointer exception thrown on below line:
automationService.updateAutomation(config);
Note: I was able to create/update from the controller class.Only in Thread.
Well, this is the classical Why is my Spring #Autowired field null case. You create the S3Thread instance by yourself, and thus, no beans are injected into it.
Considering you're trying to just do something in a separate thread, you can consider using #Async:
#Async
public void updateAutomationConfiguration() {
Automation config = new Automation("user1", "success");
automationService.updateAutomation(config);
}
Notes:
You have to add the #EnableAsync annotation to any configuration class (eg. your main class) to make this work.
Spring uses proxying by default, which means that you can't add this updateAutomationConfiguration() class to your controller itself. Direct calls to methods within the same bean bypass the proxied logic. The solution is to put this method in a separate bean which can be autowired and invoked from within the controller. I've provided more detailed answers about alternative solutions in this answer.
Spring also has a getting started guide for creating asynchronous methods.
Alternatively, there are also some ways to execute asynchronous calls within controllers, for example by using CompletableFuture within a controller:
#PutMapping("/automation/configuration")
public CompletableFuture<String> updateAutomationConfiguration() {
return CompletableFuture.supplyAsync(() -> {
Automation config = new Automation("user1", "success");
return automationService.updateAutomation(config);
});
}
Related: How to create a non-blocking #RestController webservice in Spring?
Spring does not scan your runnable as it is not annotated with #Component.Try annotating it with #Component/#Service.
Don't forget to set scope required scope though!
There are 2 potential solutions to your problem:
Either you need to make S3Thread class a service by annotating it with #Service or #Component and autowiring it on the calling class, or you can alternatively use the constructor for initializing your automationService, e.g. private IAutomationService automationService = new AutomationService();
Since your thread class is not managed by spring you will not be able to inject the spring managed beans in the S3Thread class.
In order to do that you need to create a class or factory which should be hooked into the spring life cycle.
Once you have the hold of that class you can get the appropriate bean and pass the reference onto/or used in the S3Thread class directly. Something like this
#Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext ctx;
#Override
public void setApplicationContext(ApplicationContext appContext)
{
ctx = appContext;
}
public static ApplicationContext getApplicationContext() {
return ctx;
}
}
public class S3Thread implements Runnable {
#Override
public void run() {
Automation config = new Automation("user1","success");
IAutomationService automationService=
ApplicationContextUtils.getApplicationContext().getBean(IAutomationService .class);
automationService.updateAutomation(config);
}
}
i have a microservice application developed using spring boot. i also have a project under under the same application which contains some java classes (non spring classes). i am trying to use some of the beans which are available in the spring container in this non java class using the ApplicationContextAware approach. when i debug the code during the bootrun, i can see the setApplicationContext(ApplicationContext ctx) is getting call and the context is getting set.
when from inside my non spring java class when i tried to get the instance of the context using the public static getApplicationContext(), i am getting null for context.
below is the sample example i had used.
#Component
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ctx) {
context = ctx;
}
}
this is how i try to get the instance
ApplicationContext c = ApplicationContextProvider.getApplicationContext();
i am not able to figure it out what is missing here, as i am using spring boot, i dont think there is a need to configure any bean in xml.
This worked for me with your code. I could get the application start time after fetching the provider from the context.
#RestController
public class SampleController {
#Inject
ApplicationContextProvider provider;
#RequestMapping(value = "..", method = RequestMethod.GET)
public ResponseEntity<String> someMethod() throws IOException {
ApplicationContext c = provider.getApplicationContext();
System.out.println(c.getApplicationName());
c.getStartupDate()
}
}
I need to create a new EmployeeInfoCache instance (not a singleton) from info in the HttpServletRequest that is used to get info from an external app. I then want to give this object as a dependency to non-web-layer objects (where it will be set for all #Autowired references). EmployeeInfoCache itself has no web-layer dependencies (e.g. HttpServletRequest).
Can this be done? I thought about writing a spring interceptor which does the following but I don't know what to do to put an object in the spring context such that it will be used to resolve all #Autowired dependencies.
e.g.
public class MyInterceptor extends HandlerInterceptorAdapter
{
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception
{
- use info from HttpServletRequest to make calls to external app
- create EmployeeInfoCache object w/ this info
- add EmployeeInfoCache to spring application context where it will be used for resolution of #Autowired
}
}
And the remaining code:
// Assume don't have 'Component' or a similar annotation?
public class EmployeeInfoCache
{
...
}
// REST controller that calls the business logic method
#Controller
MyController
{
#Autowired
private MyBusinessObjectInterface myBusinessObject;
#RequestMapping(...)
public #ResponseBody MyResult myMethod(#RequestBody MyObject myObject, HttpServletRequest request, HttpServletResponse response)
{
myBusinessObject.doIt();
}
}
// Non-web-layer code that uses EmployeeInfoCache
#Service(...)
MyBusinessObject implements MyBusinessObjectInterface
{
// I want the EmployeeInfoCache instance created in MyInterceptor to be autowired here
#Autowired
private EmployeeInfoCache employeeInfoCache;
#Override
public void doIt()
{
employeeInfoCache.getName();
}
}
It sounds like you want to use a factory pattern. Have a Spring bean which is the factory method that returns the Cache.
http://kh-yiu.blogspot.in/2013/04/spring-implementing-factory-pattern.html
I would like to add web interface to my Java application, so that I can manipulate it's state using HTTP.
I have added to application context a Spring bean for some class that starts embedded Tomcat. This class of course has access to context that creates it. But I would like to store this context somehow in Tomcat class (org.apache.catalina.startup.Tomcat) so that later in can be retrieved in Servlets, so that I can do something like this:
public SomeClass extends extends HttpServlet {
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ApplicationContext appContext = getContextStoredEarlierInTomcatClass();
SomeBeanFromContext sbfc = appContext.getBean("sbfc", ApplicationContext.class);
sbfc.setSomeProperty(newValue);
}
}
Any idea how I could achieve it?
Thanks!
Classes including Servlets do not require an ApplicationContext to obtain references to String beans. This is done using dependency injection
#Controller
#RequestMapping("/mypage")
public class SomeClass {
#Autowired
private SomeBeanFromContext sbfc;
#RequestMapping(value = "/individualRequest", method = RequestMethod.GET)
public String doIndividualRequest(HttpServletRequest request) {
sbfc.setSomeProperty(newValue);
...
}
}
Spring MVC offers a complete method of injecting beans into target web controller classes using #Controller annotated classes.