Background
I have an existing Grails application which I'm rewriting in Spring. My Grails application (simplified for example) has 2 plugins, UserProfilePlugin and UserOrderPlugin where each plugin has its own code + config + resources. Depending on a client's request, I can generate an application which is compiled/packaged to include:
Both UserProfilePlugin and UserOrderPlugin to display both User Profiles and User Orders
Only UserProfilePlugin to display only User Profiles
Only UserOrderPlugin to display only User Orders
Problem
I want to modularize my Spring application to have multiple code + config + resources units which can be included/excluded from the main application at compile/package time and can be reused across multiple applications. But I'm struggling to find Spring's equivalent of a Grails plugin. I have already gone through Spring's official documentation 1, 2, 3, 4, etc. but could not find anything useful. On the contrary, Spring application (as per the document) looks more like one monolithic unit with no reusable sub-units inside it.
Am I missing something obvious? Can someone please throw some light on this?
Spring does not have such a concept, but there are several ways to implement it.
The easiest way is probably to have a component scan in your application that would pick up all components from the optional package.
Let's say your "module" defines a top-level package "com.yourcompany.yourapp.module", then you would just have to make sure that all other parts of the same app are underneath "com.yourcompany.yourapp" and set your component scan to "com.yourcompany.yourapp".
If you are not a big fan of component scan, the cleanest way I can think of is the Java ServiceLoader SPI. You could define an interface that does the registration with Spring, e.g.
public interface SpringConfig{
void register(ApplicationContext ac);
}
(I'm sure you can improve on this design)
Anyway, in each module, you'd have a file called
/META-INF/services/com.yourcompany.yourapp.SpringConfig
that lists all implementations this module holds. In your central app, you could then have a component that initializes the modules:
#Component
public class ModuleInitializer implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
#Override
public void setApplicationContext(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#Override
public void afterPropertiesSet() throws Exception {
for (SpringConfig springConfig : ServiceLoader.load(SpringConfig.class)) {
springConfig.register(applicationContext);
}
}
}
In both versions, you enable / disable a module by adding it to / removing it from the classpath. So the easiest way to do that is to have one build artifact (e.g. Maven module) per application module
Related
I am trying to use Apache Camel File-Watch feature.
However I notice all example are used along with Spring.
Is there anyway to make use of this feature without Spring ?
Thank you for any potential input.
Best regards.
Apache Camel is a standalone integration framework to easily integrate different systems using well know and established EIP (Enterprise Integration Patterns).
It is not tied to Spring in any way at its core and has different deployment / integration models out of which is Spring / Spring Boot for the sake of easing its adoption and configuration for Spring framework users.
Out of the runtime contexts, you can use for example Camel Main component to run your application having a File Watch component setup to watch the /tmp/ directory change events:
The main application would look like the following:
public class FileWatchApplication {
private FileWatchApplication() {
}
public static void main(String[] args) throws Exception {
// use Camels Main class
Main main = new Main(FileWatchApplication.class);
// now keep the application running until the JVM is terminated (ctrl + c or sigterm)
main.run(args);
}
}
A simple RouteBuilder setting up your File Watch component would look like the following (note that it must be within the same (or child) package of the FileWatchApplication class):
public class FileWatchRouteBuilder extends RouteBuilder {
#Override
public void configure() throws Exception {
// snippet configuration from the official documentation
from("file-watch:///tmp/")
.log("File event: ${header.CamelFileEventType} occurred on file ${header.CamelFileName} at ${header.CamelFileLastModified}");
}
}
Camel is based on Spring. In fact, the Camel Context is an extension of the Spring Application Context. So if you have Camel, you also must have Spring.
I'm using Spring Batch with #EnableBatchProcessing(modular = true)
The problem is that in this mode you have to explicitly declare which beans to initialize (i.e which classes Spring needs to scan)
Here's an exmaple:
#Configuration
#EnableBatchProcessing(modular = true)
public class ModularJobsConfig {
#Autowired
private AutomaticJobRegistrar registrar;
#PostConstruct
public void initialize() {
registrar.addApplicationContextFactory(new GenericApplicationContextFactory(
SomeJobConfig.class,
SomeJobTasklet.class,
SomeClassToDefineTaskExecutor.class
SomeClassToRunTheJob.class));
}
}
I can imagine that by the time I'll have several jobs, this configuration class will be bloated. How can I automate this?
It is worth mentioning that each job has its own package (e.g com.example.jobs.<job_name>) + they are defined in different maven-modules but I think it's irrelevant.
Further Clarification
I have a core module which contains the configuration above. Every job is defined in a separate maven module and it's been registered as a maven dependency in core.
Mainly for preventing naming clash, I'm using #EnableBatchProcessing(modular = true) and I'm registering the jobs with AutomaticJobRegistrar as you can see in the example code above.
Ideally, I'd like Spring to scan the maven dependency and to do it for me (i.e. defining GenericApplicationContextFactory)
Currently, it's cumbersome to add manually each and every class (in the example above: SomeJobConfig.class, SomeJobTasklet.class etc)
As a counter example, if I didn't use modular=true I could let Spring Batch to load all beans on its own, but then I'd have to make sure methods names are unique across all the modules.
I've managed to avoid developing any xml files so far for my Spring Boot Maven project (apart from the pom) with them all being generated on compile and I was hoping to keep this way by defining my profiles within the run commands as specified here.
By simply using #ComponentScan my main class to enable the scanning of components and tagging my DAO as #Repository I have successfully managed to autowire my UserDAOmySQLImpl class (which inherits UserDAO).
#Autowired
private UserDAO userDAO;
Then looking forward to add a second DAO for when in Live where we use a db8 implementation I need the application to figure out which UserDAO implementation needs to be used. Profiles seemed to be the answer.
After some reading, it seemed that mostly I need to add in some sort of configuration classes and external xml to manage these profiles and components, though this seems messy and I am hoping unnecessary.
I have tagged by two implementations as so:
#Repository
#Profile("dev")
public class UserDAOmySQLImpl implements UserDAO {...
-
#Repository
#Profile("dev")
public class UserDAOdb8Impl implements UserDAO {...
And then set the active profile through the Maven goals as specified here in hope that this would be a nice clean solution so I could specify the active profile within the goal for our dev and live build scripts respectively.
My current goal for testing is:
spring-boot:run -Drun.profiles=dev
However, when building I receive an error that both beans are being picked up.
*No qualifying bean of type [com.***.studyplanner.integration.UserDAO] is defined: expected single matching bean but found 2: userDAOdb8Impl,userDAOmySQLImpl*
Questions
Is the profile set in the Maven Goal the one being checked against when using the #Profile tag?
Why is Spring picking up both implementations, when if the profile isn't being set properly surely neither implementation should be selected?
Any suggestions on a nice clean way to achieve what I'm looking for?
Bonus
I would really like to set the profile within the app, as it would be easy to simply check whether an environment file exists to decide which profile to use, though I'm struggling to replicate this (found within Spring Profiles Examples
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
//Enable a "live" profile
context.getEnvironment().setActiveProfiles("live");
context.register(AppConfig.class);
context.refresh();
((ConfigurableApplicationContext) context).close();
}
in to the unusual main class in the application I am working on which I am hesitant to play around with.
public class StudyPlannerApplication {
...
public static void main(String[] args) {
SpringApplication.run(StudyPlannerApplication.class, args);
}
...
}
Any help would much appreciated.
Cheers,
Scott.
Silly me, proof reading the question I noticed that a bad copy & paste job meant that both DAO implementations had the #profile set to "Dev". After changing the db8 DAO to #Profile("live") the above works fine.
So to choose your repository based on profile is actually quite easy when using maven and spring boot.
1) Ensure your main class has the #ComponentScan annotation
#ComponentScan
public class StudyPlannerApplication {
2) Tag your components with the #Profile tags according to which profile you would like them sectected for
#Repository
#Profile("dev")
public class UserDAOdb8Impl implements UserDAO {...
-
#Repository
#Profile("live")
public class UserDAOdb8Impl implements UserDAO {...
3)
Send your profile in with your maven goal
spring-boot:run -Drun.profiles=dev
Hopefully this will help others, as I haven't seen a full example of using the autowiring profiles as I have done elsewhere on the web
I'm working with the developer of PF4J(Plugin Framework for Java) to provide better plugin functionality for Wicket. There is already a pf4j-spring and a pf4j-wicket project to provide some basic integration. In order to allow the #SpringBean or #Inject annotations to have access to plugin beans in a child context we need to be able to lookup the ApplicationContext associated with a specific class.
So for example, say I have a MyService bean in a child(plugin) ApplicationContext and that plugin also provides a panel that needs that via a #SpringBean annotation. Spring doesn't allow the parent ApplicationContext to see beans in a child context and for good reason. So we would get an exception saying that bean could not be found since #SpringBean only looks up beans in the parent context. We have code that we have developed that look up the child context like so:
SpringPlugin plugin = (SpringPlugin)PluginManager.whichPlugin(MyService.class);
ApplicationContext pluginContext = plugin.getApplicationContext();
How could I modify or provide this functionality in a custom version of SpringComponentInjector? It uses a ISpringContextLocator but that context locator does not specify the class for which it needs the ApplicationContext.
Any ideas on how this could be achieved?
Thanks for your help!
I'm afraid current SpringComponentInjector is not prepared for such usage. You will have to create your own version.
The problem that I see is that you will have to have either as many IComponentInstantiationListeners as plugins there are. Or create a composite ICIL that delegates to SpringBeanLocators for each plugin. I think the composite would be better. Then you'll have to make sure that a Panel in pluginA cannot use a bean located by SpringBeanLocatorB.
If you manage to do it and you find something in wicket-spring that could be made more generic to help make your version simpler then please let us know and we will consider your suggestion(s)!
Take a look at sbp. It is built on top of pf4j to support Spring Boot, and also provides mechanism of sharing beans between main application and plugins. It looks like:
#Override
protected SpringBootstrap createSpringBootstrap() {
return new SharedDataSourceSpringBootstrap(this, MdSimplePluginStarter.class)
.addSharedBeanName("objectMapper")
.addSharedBeanName("cacheService");
}
Right now I'm exposing the service layer of my application using spring remoting's RMI/SOAP/JMS/Hessian/Burlap/HttpInvoker exporters. What I'd like is to allow the user to somehow define which of these remoting mechanisms they'd like enabled (rather than enabling all of them), then only create those exporter beans.
I was hoping that spring's application context xml's had support for putting in conditional blocks around portions of the xml. However, from what I've seen so far there's nothing in the standard spring distribution that allows you to do something like this.
Are there any other ways to achieve what I'm trying to do?
I am going to assume that you are looking to configure your application based on your environment, as in... for production I want to use this beans, in dev these other ...
As Ralph is saying, since Spring 3.1 you have profiles... But the key, is that you understand that you should put your environment based beans in different configuration files... so you could have something like dev-beans.xml, prod-beans.xml... Then in your main spring file, then you just invoke the appropriate one based on the environment that you are using... So profiles are only technique to do so... But you can also use other techniques, like have a system environmental variable, or pass a parameter in your build to decide which beans you want to use
You could realize this by using a Spring #Configuration bean, so you can construct your beans in java code. (see http://static.springsource.org/spring/docs/3.1.x/spring-framework-reference/html/beans.html#beans-java)
#Configuration
public class AppConfig {
#Bean
public MyService myService() {
if ( userSettingIshessian ) {
return new HessianExporter();
}else {
return new BurlapExporter();
}
}
}
Of course you need to get the user setting from somewhere, a system parameter would be easy, or config file, or something else.
Spring 3.1 has the concept of Profiles. My you can use them.