Spring: how AnnotationConfigWebApplicationContext could not overwrite later beans? - spring

I have a web application that use Sring IoC framework.
I use the Java configuration for Spring, and I only use #Configuration annoted module definition (no DI related tags elsewhere in the code).
The Spring registry is built on web application start-up thanks to (a bit modified version of) Spring context loader listener, and the contextConfigLocation param in web.xml configured to point to the #Configuration annotated class.
All that is good and I get a AnnotationConfigWebApplicationContext.
Now, I want to have plugins in my application, that will have their own #Configuration annotated configuration classes, and will use some of the main application services. BUT I don't want to have main application to be modified to load these new modules.
So, I thought that I could simply use the package searching of annotated class for that, but now, it seems that I can use two beans with the same type, even if they have different ids, and clearly AnnotationConfigWebApplicationContext doc states that:
Note: In case of multiple #Configuration classes, later #Bean definitions will override ones defined in earlier loaded files. This can be leveraged to deliberately override certain bean definitions via an extra Configuration class.
I don't want that, because modules should be able to contribute alternative version of services, not (alwways) override existing one - especcially if I want to have a "moduleDef" bean.
I tried to use differents approach on that, but the hierachy of Context and related services is just to big for me.
So, does anybody know how I could reach my goal ?
Thanks

You can have multiple beans of the same type, but You cannot have 2 or more beans with the same ID in a single Spring ApplicationContext - no matter if You use XML or JavaConfig.
The overriding mechanism matches the bean ID's, so all You need to do is to ensure unique ID, i.e.: coreModuleDef, someOtherModuleDef, anotherModuleDef. I don't think You need the ID of each module definition to be identical? What should be sufficient is the type to be the same, but not ID.
You can also turn off the overriding mechanism by setting allowBeanDefinitionOverriding to false on Your AnnotationConfigWebApplicationContext to get an exception if You accidentally override a bean:
public class MyDispatcherServlet extends DispatcherServlet {
#Override
protected void postProcessWebApplicationContext(
ConfigurableWebApplicationContext wac) {
((AnnotationConfigWebApplicationContext) wac)
.setAllowBeanDefinitionOverriding(false);
}
}
or:
public class MyContextLoaderListener extends ContextLoaderListener {
#Override
protected void customizeContext(
ServletContext servletContext,
ConfigurableWebApplicationContext applicationContext) {
((AnnotationConfigWebApplicationContext) wac)
.setAllowBeanDefinitionOverriding(false);
}
}

Related

Spring (Security) dependency injection

I asked a question more specific to my case about 2 hours ago, but I realised I'm not really addressing my problem at the root cause.
I have a Spring application that uses Spring Security. Throughout my application, (Controllers, service classes etc) I'm using dependency injection and it all works fine. However, I recently started configuring Spring Security, and I can't inject any dependencies inside the classes in my "security" package. Online I read somewhere: "Also when you use #Autowired in the class of which you created a new instance, the Spring context will not be known to it and thus most likely this will also fail" and I was wondering if this maybe had something to do with my issue. My spring configuration basically has one "starting-point", that is the following class:
#Component
#Service
public class AppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext sc) throws ServletException {
AnnotationConfigWebApplicationContext root = new AnnotationConfigWebApplicationContext();
root.register(SecSecurityConfig.class);
sc.addListener(new ContextLoaderListener(root));
sc.addFilter("securityFilter", new DelegatingFilterProxy("springSecurityFilterChain"))
.addMappingForUrlPatterns(null, false, "/*");
}
}
This code is run on startup. As you can see, it is registering the SecSecurityConfig.class which is where I configure Spring Security. Inside of that class and onwards (all classes it uses and all classes that those classes use) I can't inject any dependencies. I was wondering if anyone could tell me what the problem could be. Sorry if I'm unclear or incorrect - please tell me so, I find the concept of DI somewhat hard to grasp. My component-scan in XML is: <context:component-scan base-package="com.qars"/> which is the package that my security package is also in.
Also all my classes are annotated with #Component or #Service

Can I get another #Configuration only through #ComponentScan

I am using spring-boot 2.0.4; I have a bunch of services and they have a common configuration class marked with #Configuration.
I want to move this to a common dependency which will have this #Configuration, and based on the need, any micro-service can use #ComponentScan to activate this configuration from dependency.
I have done this for #Component classes, and it's working fine. I activate any particular component I need by adding it into #ComponentScan. How can I activate the configuration in a similar manner(based on need).
Here are the code examples:
Common Configuration:
package abc.department.common.configs.mongo
#Component
public class AbcMongo {
#Bean
public MongoTemplate mongoTemplate() {
// ... create MongoTemplate.
return createdMongoTemplate;
}
}
Here is a class which uses the above dependency:
#Configuration
#ComponentScan("abc.department.common.configs.mongo")
public class MyServiceConfigs {
}
Similarly, I want to do something like this:
package abc.department.common.configs.security.web
#Configuration
#EnableWebSecurity
public class AbcWebSecurity extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// ... do common configs;
}
}
and now, if a service would need web-security config, it could get like:
#Configuration
#ComponentScan({"abc.department.common.configs.mongo","abc.department.common.configs.security.web"})
public class MyServiceConfigs {
}
#Configuration is meant to specify the beans, for example:
#Configuration
public class MyMongoConfiguration {
#Bean
public MongoTemplate mongoTemplate() {
return new ...
}
#Bean
public MySampleBean mySampleBean(MongoTemplate tpl) {
return new MySampleBean(tpl);
}
}
But if so why do you need to work with #Component at all (at least for the beans you create)?
Configuration is a special bean used by Spring framework to load other beans and it can be viewed as a "substitution"/alternative technique to component scanning.
I believe that, if you have some infrastructure configuration that loads a bunch of "infrastructure beans" (shared jar if I get you right), then the services that use this jar should only say "Hey, I want to load this configuration" and not to scan inside the packaging structure of that jar. Why do I think so?
What if you decide to add new beans into a new package in the infra, should external services change their code and define an additional folder to scan? - Probably no.
What if you decide to move the infra to another package?
Now in Spring there are two simple ways to do this that come to mind:
Way 1: Use #Import Annotation
#Configuration // this is from "shared artifact"
class MyInfraConfiguration {
}
#Configuration // this is from an "applicative service" that uses the infra jar in dependencies
#Import(MyInfraConfiguration.class)
class ServiceAConfiguration {
}
Way 2: Use Spring Factories mechanism
The first way has a drawback: You need to know in a Service what infra configuration exactly is. If you see it as a drawback, consider using spring factories.
Spring factories allow registering the infra configuration in some file so that spring boot will load it in service one automatically, you won't even need to mention MyInfraConfiguration in the Service Configuration, just add a dependency to the infra jar and it will work.
In the infra component create:
META-INF/spring.factories
And add there:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.mycompany.myinfra.whatever.InfraConfiguration
That's it.
Now if you want to customize the loading of beans in the infra configuration, like, a creation of Mongo related templates only if some properties are available, you might want to use #Conditional. Now, although this is kind of out of scope for this question, I mention this because in conjunction with spring factories this can create a very flexible way to manage your configurations

Spring Framework 5.0.0.Final parent context not getting loaded

I was trying with latest spring 5.0.0.Final with my EAR project which has a parent context defined in web.xml using context-param
with param names locatorFactorySelector and parentContextKey but spring could not able to load the parent context. When i checked the ContextLoaderListener source code it seems like there is no logic applied to pick parent context. Here my question is does spring 5 provide any default implementation of ContextLoader which caters loading of parent context or spring 5 dropped, if not what is the approach to support this, do i have to implement our own ?
The loading of the parent context based on locatorFactorySelector were handled at ContextLoader#loadParentContext(). But they changed it to return null in this commit.
As said by the javadoc , I think you can create a new ContextLoaderListener and override this method to return the parent context:
public class FooContextLoaderListener extends ContextLoaderListener{
#Override
protected ApplicationContext loadParentContext(ServletContext servletContext) {
//load and return the parent context ......
}
}
Then use this ContextLoaderListener to start up Spring :
<listener>
<listener-class>org.foo.bar.FooContextLoaderListener</listener-class>
</listener>
For me this below piece of code worked fine.
public class BeanFactoryContextLoaderListener extends ContextLoaderListener {
private static Logger log = Logger.getLogger(BeanFactoryContextLoaderListener.class);
#Override
protected ApplicationContext loadParentContext(ServletContext servletContext) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beanRefFactory.xml");
return ctx;
}
}
Obviously I added a listener too in web.xml.
My team recently bumped into the same problem. We wanted to start using Webflux and it requires Spring 5.
Here is what I did:
Manually reintroduce BeanFactoryLocator mechanism. Take following classes from Spring 4, put it into your code and fix packages:
AbstractUrlMethodNameResolver
AnnotationMethodHandlerAdapter
BeanFactoryLocator
BeanFactoryReference
BootstrapException
ContextSingletonBeanFactoryLocator
DefaultAnnotationHandlerMapping
HandlerMethodInvocationException
HandlerMethodInvoker
HandlerMethodResolver
InternalPathMethodNameResolver
MethodNameResolver
NoSuchRequestHandlingMethodException
ServletAnnotationMappingUtils
SingletonBeanFactoryLocator
SourceHttpMessageConverter
WebUtils
XmlAwareFormHttpMessageConverter
Following Subhranil's advice from this thread, use custom ContextLoaderListener which loads parent context same as in Spring 4. Then use it in web.xml.
In each WAR's spring-servlet.xml add DefaultAnnotationHandlerMapping so it scans for controllers. Accompanying beans like AnnotationMethodHandlerAdapter are also needed.
It worked for us.
If all you need is your context-param in any of your spring managed class you are looking for ServletContextAware.
Just implement that class and override its method to get the ServletContext object. Later you can also get the context-params using the ServletContext object.
Check out a very similar question.
Apparently, the mechanism for locating the parent context was removed with SPR-15154 (see also the corresponding Github issue spring-framework#19720).
One workaround is to extend org.springframework.web.context.ContextLoaderListener and re-implement the loadParentContext method is described in this stackoverflow answer.
There is probably a better way to solve parent context loading in Spring 5.x, which I still have to figure out.

Spring root and servlet context with Java config

I'm running a Spring application in a Servlet 3.0+ environment to programmatically configure the servlet context using all Java configuration. My question (with details below): how is a project structured to support component scanning for both root and web application contexts without duplicating component initialization?
As I understand it, there are two contexts in which to register Spring beans. First, the root context is where non-servlet-related components go. For example batch jobs, DAOs, etc. Second, the servlet context is where servlet-related components go such as controllers, filters, etc.
I've implemented a WebApplicationInitializer to register these two contexts just as the JavaDoc in WebApplicationInitializer specifies with a AppConfig.class and DispatcherConfig.class.
I want both to automatically find their respective components so I've added #ComponentScan to both (which is resulting in my Hibernate entities being initiated twice). Spring finds these components by scanning some specified base package. Does that mean I need to put all my DAO-related objects in a separate package from the controllers? If so, that'd be quite inconvenient as I generally like to package by functionality (as opposed to type).
Code snippets...
WebApplicationInitializer:
public class AppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext container) throws ServletException {
// Create the 'root' Spring application context
AnnotationConfigWebApplicationContext rootContext =
new AnnotationConfigWebApplicationContext();
rootContext.register(AppConfig.class);
// Manage the lifecycle of the root application context
container.addListener(new ContextLoaderListener(rootContext));
// Create the dispatcher servlet's Spring application context
AnnotationConfigWebApplicationContext dispatcherContext =
new AnnotationConfigWebApplicationContext();
dispatcherContext.register(WebAppConfig.class);
// Register and map the dispatcher servlet
ServletRegistration.Dynamic dispatcher =
container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/");
}
}
AppConfig:
#Configuration
#ComponentScan
public class AppConfig {
}
WebAppConfig:
#Configuration
#EnableWebMvc
#EnableSpringDataWebSupport
#ComponentScan(basePackageClasses = AppConfig.class)
public class WebAppConfig extends WebMvcConfigurerAdapter {
}
Just define what you want to scan for in each configuration. In general your root configuration should scan for everything but #Controllers and your web configuration should only detect #Controllers.
You can accomplish this by using the includeFilters and excludeFilters attributes of the #ComponentScan annotation. When using include filters, in this case, you also need to disable using the default filters by setting useDefaultFilters to false.
#Configuration
#ComponentScan(excludeFilters={#Filter(org.springframework.stereotype.Controller.class)})
public class AppConfig {}
And for your WebConfig
#Configuration
#EnableWebMvc
#EnableSpringDataWebSupport
#ComponentScan(basePackageClasses = AppConfig.class, useDefaultFilters=false, includeFilters={#Filter(org.springframework.stereotype.Controller.class)})
public class WebAppConfig extends WebMvcConfigurerAdapter {}
In addition, you need to import the #Filter annotation:
import static org.springframework.context.annotation.ComponentScan.Filter;
The short answer is:
Yes, you should define seperate component scans per context, thus modeling your project differently and extracting the DAO into a seperate name-space (package).
The longer answer is split into two parts, one regarding the servlet contexts and the other regarding modeling a project.
Regarding root and servlet contexts: You have defined correctly the different responsibilities of root context and servlet context. This is an understanding most developers tend to miss and it is very important.
Just to clarify a bit more on this subject, you can create one root context and several (0+) servlet contexts. All of the beans defined in your root context will be available for all servlet contexts but beans defined in each servlet context will be not be shared with other servlet contexts (different spring containers).
Now since you have multiple spring containers and you use component scan on the same packages then beans are created twice, once per container.
To avoid this you can do a few things:
Use one container and not two, meaning you can define only the root container or only the servlet container (I have seen many applications like this).
Seperate your beans into different places and provide each container its unique component scan. remember that all of the beans defined in the root container are available to use in the servlet container (you do not need to define them twice).
Lastly, a few words regarding project modeling. I personally like to write my project in layers, thus separating my code into controllers (application layer), business logic (bl layer) and DAO (database layer).
Modeling like this helps in many ways, including this root/servlet context issue you have encountered.
There are tons of information regarding layered architecture, here is a quick peed: wiki-Multilayered_architecture

How can I instantiate an injected class (using Spring) before logback configuration

I am using spring to inject a class into my PropertyDefiner implementation which will be used to help set up some properties within the logback.xml file (through dynamic property loading).
I'd love to get this class loaded and instantiated before logback is configured. Any thoughts on how to do this?
If you're using annotations in Spring, it's convenient to do this by marking the class (i.e. the dependency) you'll be injecting as #Component and then using #Autowired in your PropertyDefiner implementation. This ensures that the first class will be instantiated first. http://static.springsource.org/spring/docs/3.0.0.M3/spring-framework-reference/html/ch04s12.html
Any other initialization you require could be achieved using instance initializer blocks http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html
I do not know if this can be done elegantly at present time (2012-07). However, support for injection has been requested in LOGBACK-719.
If your bean factory implements AutowireCapableBeanFactory, given the Spring Applicaton context, you could invoke autowireBean(Object existingBean) to autowire the bean. Here is a tentative implementation:
class Your.PropertyDefiner implements PropertyDefiner, LifeCycle {
#Autowired
#Qualifier("myKey")
String myKey;
public void start() {
ApplicationContext appContext = ... somehow get the spring app context
AutowireCapableBeanFactory factory = appContext.getAutowireCapableBeanFactory();
factory.autowireBean(this); // declare victory
}
}
The start() method will be invoked only if your PropertyDefiner implements the LifeCycle interface. Moreover, you need logback version 1.0.7 or later. Earlier versions do not invoke start().
My solution resulted in not implementing a PropertyDefiner. The original question became an issue of not having the application context from spring to set the dynamic properties. I'm not sure why, but code in a later listener (after the Spring listeners) would get called (invoking the LoggerFactory call) before the application context was available. I tried a number of things, until I starting looking at a different approach.
Instead of using dynamic properties I created a listener (called on server startup) which then programmatically sets up my appender with the properties I want (through the createAdminNotifyAppender).
#Override
public void contextInitialized(ServletContextEvent arg0)
{
//Set up the property reader to pull the correct properties
ServletContext context = arg0.getServletContext();
ApplicationContext appContext = WebApplicationContextUtils.getWebApplicationContext(context);
propReader = (AppConfigPropertiesReader)appContext.getBean("propertySourcesPlaceholder");
LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
createAdminNotifyAppender(lc, propReader);
}
The createAdminNotify method simply sets up an appender and adds it to the logging context. (if you're really interested, you can see that method's implementation on this thread).
Now I have a separate and modular listener that I can add to other apps that are using logback, but possibly with different properties. The properties are pulled from a database and can also vary by environment.

Resources