Using multiple dispatcher servlets / web contexts with spring boot - java

I created a spring boot application with a parent context (services) and child context (spring-webmvc controllers):
#Configuration
public class MainApiApplication {
public static void main(String[] args) {
new SpringApplicationBuilder()
.parent(Services.class)
.child(ApiOne.class, MainApiApplication.class)
.run(args);
}
#Bean
public EmbeddedServletContainerFactory servletContainer() {
return new TomcatEmbeddedServletContainerFactory();
}
}
Now I want to add another client context (and DispatcherServlet) for my ApiTwo.class configuration. I think I have to do two things:
Move the servletContainer (thus the MainApiApplication.class configuration) out of the child context and
add a path mapping /one/ -> ApiOne.class and /two/ ApiTwo.class
What is the spring boot way to do it?

As #josh-ghiloni already said, you need to register a ServletRegistrationBean for every isolated web context you want to create.
You need to create an application context from a xml or java config class. You can use #Import and #ComponentScan annotation to add shared services to the parent context. Here is an example:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.context.support.XmlWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
//#ComponentScan({"..."})
//#Import({})
public class Starter {
public static void main(String[] args) throws Exception {
SpringApplication.run(Starter.class, args);
}
#Bean
public ServletRegistrationBean apiV1() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
XmlWebApplicationContext applicationContext = new XmlWebApplicationContext();
applicationContext.setConfigLocation("classpath:/META-INF/spring/webmvc-context.xml");
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/api/1/*");
servletRegistrationBean.setName("api-v1");
return servletRegistrationBean;
}
#Bean
public ServletRegistrationBean apiV2() {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
applicationContext.register(ResourceConfig.class);
dispatcherServlet.setApplicationContext(applicationContext);
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(dispatcherServlet, "/api/2/*");
servletRegistrationBean.setName("api-v2");
return servletRegistrationBean;
}
}

Create a ServletRegistrationBean that declares the servlet and its mappings. You will probably also want to exclude DispatcherServletAutoConfiguration from the autoconfigurations called, because it will register a DispatcherServlet at / and override yours
EDIT Despite my comment below saying you might not need this, unless you need your APIs running on separate ports (and it doesn't sound like you do), Dave Syer, one of the authors of Spring Boot, answered a very similar question here: Configure multiple servletcontainers/servlets with spring boot

Related

How to Inject Dependencies into a Servlet Filter with Spring Boot Filter Registration Bean?

I have a Servlet Filter within my Spring Boot (2.0.1) application that I'm registering with FilterRegistrationBean which I need it to be executed first (order of one) along the filter chain. The application is deployed to JBoss 7.2. This filter also has a dependency that is injected with #Autowired (see below):
package my.pkg.com
#SpringBootApplication
#ComponentScan(basePackages={"my.pkg.com"})
public class MyApp extends SpringBootServletInitializer {
public satic void main(String[] args) throws IOException {
SpringApplication.run(MyApp.class, args);
}
#Bean
#Order(1)
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> contextFilter = new FilterRegistrationBean<>();
contextFilter.setFilter(new MyFilter());
contextFilter.addUrlPattern("/api/*");
return contextFilter;
}
}
package my.pkg.com.filter
public class MyFilter extends Filter {
#Autowired
private MyService mySrv;
#Override
public void doFilter(…) {
mySrv.doSomething(); // mySrv is null
}
}
The problem is when the application is deployed and ran, when the Servlet request gets to MyFilter.doFilter(), mySrv is null which means MyFilter was never scanned for dependency injections.
I can verify through debugging MyService which is a #Repository in my.package.com.repository package does get initialized. It just never gets injected into MyFilter.
I can create a constructor for MyFilter to take MyService, then #Autowired MyService into MyApp and during filter registration, I can pass it to this constructor, which resolves the issue.
However, I want to know if there is anything I'm doing wrong that this dependency doesn't get injected into MyFilter with using the setup above alone.
If you create an object by yourself, using new, and this object is not returned by a #Bean-annotated method, then it's not a Spring bean, and Spring will thus not inject anything in it.
You can just add an #Bean-annotated method returning new MyFilter(), and call that method from myFilter() to get the bean, or add a MyFilter as argument to myFilter().
Example:
#Bean
#Order(1)
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> contextFilter = new FilterRegistrationBean<>();
contextFilter.setFilter(theActualFilter());
contextFilter.addUrlPattern("/api/*");
return contextFilter;
}
#Bean
public MyFilter theActualFilter() {
return new MyFilter(); // now this is a Spring bean
}
or
#Bean
#Order(1)
public FilterRegistrationBean<MyFilter> myFilter(MyFilter theActualFilter) {
FilterRegistrationBean<MyFilter> contextFilter = new FilterRegistrationBean<>();
contextFilter.setFilter(theActualFilter);
contextFilter.addUrlPattern("/api/*");
return contextFilter;
}
#Bean
public MyFilter theActualFilter() {
return new MyFilter(); // now this is a Spring bean
}
It's simple, add #Component annotation on your filter class and it will make #Autowired annotation working inside as Spring dependency injection will process your filter class and inject the service bean.

Injecting library class as dependencies in spring project

I have multiple library classes in my project which need to be injected into a service class. This is the error statement for IntegrationFactory class:
Consider defining a bean of type 'com.ignitionone.service.programmanager.integration.IntegrationFactory' in your configuration.
This error is coming on almost every injection where this library class is injected.
I have already added the Library package in #ComponentScan, but, as it is read-only file, I can not annotate the library class. I came to know from some answer here that Spring can not inject classes which it does not manage. This library is not built on spring.
I have tried to create a #Bean method which returns the IntegrationFactory(class in question) in the class where #Inject is used, but this too does not seem to work.
How can this be done, preferably without creating a stub/copy class?
This is EngagementServiceImpl class snippet:
#Inject
public EngagementServiceImpl(EngagementRepository engagementRepository,
#Lazy IntegrationFactory integrationFactory, TokenRepository tokenRepository,
EngagementPartnerRepository engagementPartnerRepository, MetricsService metricsService) {
this.engagementRepository = engagementRepository;
this.integrationFactory = integrationFactory;
this.tokenRepository = tokenRepository;
this.engagementPartnerRepository = engagementPartnerRepository;
this.metricsService = metricsService;
}
This is injection part:
#Autowired
private EngagementService engagementService;
This is ConfigClass:
#Configuration
public class ConfigClass {
#Bean
public IntegrationFactory getIntegrationFactory(){
Map<String, Object> globalConfig = new HashMap<>();
return new IntegrationFactory(globalConfig);
}
#Bean
#Primary
public EntityDataStore getEntityDataStore(){
EntityModel entityModel = Models.ENTITY;
return new EntityDataStore(this.dataSource(), entityModel );
}
#ConfigurationProperties(prefix = "datasource.postgres")
#Bean
#Primary
public DataSource dataSource() {
return DataSourceBuilder
.create()
.build();
}
}
You need to add your bean definitions in a configuration class.
#Configuration
public class ServiceConfig {
#Bean
public IntegrationFactory getIntegrationFactory(){
// return an IntegrationFactory instance
}
}
Then you have to make sure your #Configuration class gets detected by Spring, either by having it within your scanned path or by manually importing it via #Import from somewhere withing you scanned path. An example of #Import, considering you are using Spring Boot.
#Import(ServiceConfig.class)
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Hope this helps!
Your Bean IntegrationFactory can't be found, as it is not annotated with any Spring stereotype and therefore not recognized by the component scan.
As you have multiple options to provide an instance of your class to the application context, read the Spring documentation (which also includes samples) to find out which one fits you the most:
https://docs.spring.io/spring/docs/5.1.0.RELEASE/spring-framework-reference/core.html#beans-java-basic-concepts
One Option would be to create a factory which provides an instance of your class to the application context, like it is stated in the documentation:
#Configuration
public class AppConfig {
#Bean
public IntegrationFactory myIntegrationFactory() {
return new IntegrationFactory();
}
}
Do not forget to add the Configuration to the application context.

How can I register a secondary servlet with Spring Boot?

I have an extra servlet I need to register in my application. However with Spring Boot and its Java Config, I can't just add servlet mappings in a web.xml file.
How can I add additional servlets?
Also available is the ServletRegistrationBean
#Bean
public ServletRegistrationBean servletRegistrationBean(){
return new ServletRegistrationBean(new FooServlet(),"/someOtherUrl/*");
}
Which ended up being the path I took.
Just add a bean for the servlet. It'll get mapped to /{beanName}/.
#Bean
public Servlet foo() {
return new FooServlet();
}
You can register multiple different servlet with different ServletRegistrationBean like #Bean in Application class and you can register a servlet has multiple servlet mapping;
#Bean
public ServletRegistrationBean axisServletRegistrationBean() {
ServletRegistrationBean registration = new ServletRegistrationBean(new AxisServlet(), "/services/*");
registration.addUrlMappings("*.jws");
return registration;
}
#Bean
public ServletRegistrationBean adminServletRegistrationBean() {
return new ServletRegistrationBean(new AdminServlet(), "/servlet/AdminServlet");
}
We can also register the Servlet as follow way:
#Configuration
public class ConfigureWeb implements ServletContextInitializer, EmbeddedServletContainerCustomizer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerServlet(servletContext);
}
private void registerServlet(ServletContext servletContext) {
log.debug("register Servlet");
ServletRegistration.Dynamic serviceServlet = servletContext.addServlet("ServiceConnect", new ServiceServlet());
serviceServlet.addMapping("/api/ServiceConnect/*");
serviceServlet.setAsyncSupported(true);
serviceServlet.setLoadOnStartup(2);
}
}
If you're using embedded server, you can annotate with #WebServlet your servlet class:
#WebServlet(urlPatterns = "/example")
public class ExampleServlet extends HttpServlet
From #WebServlet:
Annotation used to declare a servlet.
This annotation is processed by the container at deployment time, and
the corresponding servlet made available at the specified URL
patterns.
And enable #ServletComponentScan on a base class:
#ServletComponentScan
#EntityScan(basePackageClasses = { ExampleApp.class, Jsr310JpaConverters.class })
#SpringBootApplication
public class ExampleApp
Please note that #ServletComponentScan will work only with embedded server:
Enables scanning for Servlet components (filters, servlets, and
listeners). Scanning is only performed when using an embedded web
server.
More info: The #ServletComponentScan Annotation in Spring Boot
This way worked for me, having a servlet called WS01455501EndpointFor89
#Bean
public ServletRegistrationBean<WS01455501EndpointFor89> servletRegistrationBeanAlt(ApplicationContext context) {
ServletRegistrationBean<WS01455501EndpointFor89> servletRegistrationBean = new ServletRegistrationBean<>(new WS01455501EndpointFor89(),
"/WS01455501Endpoint");
servletRegistrationBean.setLoadOnStartup(1);
return servletRegistrationBean;
}
Also available in the BeanDefinitionRegistryPostProcessor
package bj;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
#SpringBootApplication
class App implements BeanDefinitionRegistryPostProcessor {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
registry.registerBeanDefinition("myServlet", new RootBeanDefinition(ServletRegistrationBean.class,
() -> new ServletRegistrationBean<>(new HttpServlet() {
#Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.getWriter().write("hello world");
}
}, "/foo/*")));
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
}

Spring bean wiring in different contexts

For datasource layer I use the following Spring Configuration file:
#Configuration
#ComponentScan(basePackages = {"com.savdev.springmvcexample.repository", "com.savdev.springmvcexample.config"})
#EnableTransactionManagement
#EnableJpaRepositories(basePackages = {"com.savdev.springmvcexample.repository"})
public class InfrastructureContextConfiguration {
...
#Configuration
#Profile(value = "file_based")
#PropertySource("classpath:/db/config/file_based.properties")
public static class FileBasedConfiguration {
#Inject
private Environment environment;
#Bean
public DataSource dataSource() {
BasicDataSource dataSource = new org.apache.commons.dbcp.BasicDataSource();
dataSource.setDriverClassName(environment.getProperty("jdbc.driver"));
dataSource.setUrl(environment.getProperty("jdbc.url"));
dataSource.setUsername(environment.getProperty("jdbc.username"));
dataSource.setPassword(environment.getProperty("jdbc.password"));
return dataSource;
}
}
...
To run tests I load this configuration via #ContextConfiguration:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { InfrastructureContextConfiguration.class, HsqldbEmbeddableDbStarterContextConfiguration.class })
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = false)
#Transactional()
#ActiveProfiles(profiles = {"file_based", "test_data"} )
public abstract class AbstractJpaJavaTestBase {
...
And it works fine.
The same InfrastructureContextConfiguration class is used in web module when DispatcherServlet is created:
public class SpringMvcExampleWebApplicationInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerDispatcherServlet(servletContext);
}
private void registerDispatcherServlet(final ServletContext servletContext) {
WebApplicationContext dispatcherContext = createContext(WebMvcContextConfiguration.class, InfrastructureContextConfiguration.class);
DispatcherServlet dispatcherServlet = new DispatcherServlet(dispatcherContext);
dispatcherServlet.setContextInitializers( new SpringMvcExampleProfilesInitializer());
ServletRegistration.Dynamic dispatcher;
dispatcher = servletContext.addServlet("dispatcher", dispatcherServlet);
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
private WebApplicationContext createContext(final Class<?>... annotatedClasses) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(annotatedClasses);
return context;
}
}
But now, I'm getting NullPointerException in the following line of InfrastructureContextConfiguration:
dataSource.setDriverClassName(environment.getProperty("jdbc.driver"));
The environment is not wired. What can I do to resolve it?
What I found. The similar issue already have been met:
same1, some solutions
seems the problem is not connected, but the last answer is the best solution
total:
Actually, the field that is injected with #Inject cannot be null. It must throws exception. As a result if it is null then - the annotation has not been applied at all. As a result the main reason is the absence of its implementation in classpath.
So I added the following in my web.pom. and it resolved the problem:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
As alternative options I could use:
#Resource instead of #Inject, and environment has been set.
Passed the envirionment as argument into the constructor, instead of wiring it via annotation. But the best case, IMHO, is fix jar dependecy.

Why doesn't component scan pick up my #Configuration?

I have a config file like
package com.mypackage.referencedata.config;
#Configuration
#ComponentScan ("com.mypackage.referencedata.*")
public class ReferenceDataConfig {
In a spring xml if I have
<context:component-scan base-package="com.mypackage.referencedata.config.*" />
it does not get loaded.
If I use
<context:component-scan base-package="com.mypackage.referencedata.*" />
it works.
What gives? I'd expect the 1st to work as well.
<context:component-scan base-package="com.mypackage.referencedata.config.*" />
Will scan packages inside com.mypackage.referencedata.config as it is package.
com.mypackage.referencedata.config
Will work fine.
You don't need to scan the #Configuration class in component scan in SpringFramework. But you need to register it in the Application Initializer class of your web application that defines the configuration required as in web.xml file. You need to implement WebApplicationInitializer interface there and define onStartup method.
In that onStartup method you need to register your #Configuration class to the rootContext of your web application. Please take a look at the following code snippet.
1. The class that works as web.xml
public class ApplicationInitializer implements WebApplicationInitializer {
//Called first when the application starts loading.
public void onStartup(ServletContext servletContext)
throws ServletException {
System.out.println("Inside application initializer...");
//Registering the class that incorporates the annotated DispatcherServlet configuration of spring
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(DispatcherConfig.class);
//Adding the listener for the rootContext
servletContext.addListener(new ContextLoaderListener(rootContext));
//Registering the dispatcher servlet mappings.
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("dispatcher", new DispatcherServlet(rootContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
2. The #Configuration class of web application holds the beans and other setups.
#EnableWebMvc
#Configuration
#ComponentScan(basePackages={"com.abcprocure.servicerepo.controller", "com.abcprocure.servicerepo.model", "com.abcprocure.servicerepo.service"})
public class DispatcherConfig extends WebMvcConfigurerAdapter {
//Registers the url paths for resources to skip from spring. Eg. JS, CSS and images.
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// TODO Auto-generated method stub
registry.addResourceHandler("/js/**").addResourceLocations("/js/**");
registry.addResourceHandler("/html/**").addResourceLocations("/html/**");
}
//Defines the ViewResolver that Spring will use to render the views.
#Bean
public ViewResolver viewResolver() {
System.out.println("Inside View Resolver...");
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setViewClass(JstlView.class);
viewResolver.setPrefix("/WEB-INF/views/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
//Defines the DataSource to use in the application.
#Bean
public DataSource dataSource() {
System.out.println("Inside DataSource bean creation....");
BasicDataSource dataSource = new BasicDataSource();
dataSource.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
dataSource.setUrl("jdbc:sqlserver://192.168.100.131;databaseName=test");
dataSource.setUsername("egptender");
dataSource.setPassword("egp#123");
return dataSource;
}
//Defines the Hibernate's SessionFactory.
#Bean
public SessionFactory sessionFactory() {
LocalSessionFactoryBuilder builder = new LocalSessionFactoryBuilder(dataSource()).addAnnotatedClasses(Services.class, Operations.class, OperationParameters.class, ServiceModels.class, Businesslogic.class,TblFormMaster.class,TblFormBuilder.class);
builder.setProperty("hibernate.dialect", "org.hibernate.dialect.SQLServerDialect");
builder.setProperty("hibernate.show_sql", "true");
return builder.buildSessionFactory();
}
}
Hope this helps you. Cheers.
If you are using maven, check if you have correct dependencies

Categories