I am moving to new Spring 3.x code based #Configuration style. I have done good, so far, but when I tried to add a #Profile("production") to select a JNDI data source instead of C3p0 connection pool #Profile("standalone"), I've got:
Error creating bean with name 'dataSource': Requested bean is currently in creation: Is there an unresolvable circular reference?
...
Of course, there is a lot of details (I included some below for reference), but my question is: would someone kindly post/point to a working example?
Thanks,
Details:
Same configuration, as XML, is working fine,
web.xml points to a root context configured in AppConfig.class and to a servlet context configured in WebConfig.class,
AppConfig has an #Import(SomeConfig.class) pulling a config from a service layer project - dependencies are wired by maven,
The SomeConfig class has the #Profile("standalone") on the C3p0 datasource,
The AppConfig class has the #Profile("production") on the JNDI datasource,
web.xml defines spring.profiles.default=production.
Edit: Solved
I moved the profile to the right place. Now they are in the same file and in the same project:
...
other bean definitions
/**
* Stand-alone mode of operation.
*/
#Configuration
#Profile("standalone")
static class StandaloneProfile {
#Autowired
private Environment env;
/**
* Data source.
*/
#Bean
public DataSource dataSource() {
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(env.getRequiredProperty("helianto.jdbc.driverClassName"));
ds.setJdbcUrl(env.getRequiredProperty("helianto.jdbc.url"));
ds.setUser(env.getRequiredProperty("helianto.jdbc.username"));
ds.setPassword(env.getRequiredProperty("helianto.jdbc.password"));
ds.setAcquireIncrement(5);
ds.setIdleConnectionTestPeriod(60);
ds.setMaxPoolSize(100);
ds.setMaxStatements(50);
ds.setMinPoolSize(10);
return ds;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
/**
* Production mode of operation.
*/
#Configuration
#Profile("production")
static class ProductionProfile {
#Autowired
private Environment env;
/**
* JNDI data source.
*/
#Bean
public DataSource dataSource() {
try {
JndiObjectFactoryBean jndiFactory = new JndiObjectFactoryBean();
jndiFactory.setJndiName("java:comp/env/jdbc/heliantoDB");
jndiFactory.afterPropertiesSet(); //edited: do not forget this!
return (DataSource) jndiFactory.getObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Related
So I have to deploy the same springboot app as multiple apps in the same tomcat server.
eg /app1 /app2 /app3.
They share most of the same configuration except for datasource configuration.
I've been searching for a way to externalise the datasource configuration based on the servlet-context or something like that.
Using springs externalised configuration, I am able to get it to load the same external data source file for all apps, but they need to be different. eg.
#PropertySource(value = "file:${HOME}/datasource-override.properties", ignoreResourceNotFound=false)
Using the embedded tomcat mode, ie via say .\gradlew bootRun I think I can achieve it.
I just need to use the following as the application.properties for that profile sets the server.context-path property. (as this is a single app) eg.
#PropertySource(value = "file:${HOME}/${server.context-path}/datasource-override.properties", ignoreResourceNotFound=false),
Searching around, I thought it might be something like (and combinations of) the following, but it didn't work. eg
#PropertySource(value = "file:${HOME}/${server.servlet.context-path}/datasource-override.properties", ignoreResourceNotFound=false)
All examples I've found so far deal with either the embedded tomcat, or a single externalised property file for a single app.
Ideally I would like it to find the file in either it's own directory
file:${HOME}/${server.servlet.context-path}/datasource.properties
So for the three apps it would be something like the following, where it detects from it's deployed context, what the location for it's property file is. eg:
file:${HOME}/app1/datasource.properties
file:${HOME}/app2/datasource.properties
file:${HOME}/app3/datasource.properties
Obviously if the app was deployed as /funky_chicken then it would have a matching funky_chicken/datasource.properties
Any thoughts ? I know I am probably close, and I've tried dumping all the environmental properties. (you are probably are going to tell me to get it from JNDI as it's the only one I haven't dumped looking for the context)
Now I know ${HOME} is not the best place to store config items, it really is just to make it easier to describe the issue.
Update:
Thanks to the suggestions to use profiles, is there a way to have three active profiles in the one tomcat server, /app1, /app2 and /app3 from the same WAR file?
Why you want to deploy in tomcat? Springboot app can work lonely. Hope below steps helpful to you.
add application.yml(application.properties is ok too) in /resources. In this file, you configure common setting here.
Then you add files named from application-app1.yml to application-app3.yml in /resources too. In these files, you configure different db setting.
launch your app: for example, I suppose app1 using port 10000, app2 using port 10001...
after maven well,
app1: java -jar target/[***SNAPSHOT].jar --server.port=10000 --spring.profiles.active=app1
app2: java -jar target/[***SNAPSHOT].jar --server.port=10001 --spring.profiles.active=app2
app3: java -jar target/[***SNAPSHOT].jar --server.port=10002 --spring.profiles.active=app3
You can solve problem with spring profiles and there is no need to use #PropertySource
for application 1 just activate profiles: spring.profiles.active=app1 - this assume that in classpath you have application-app1.properties file. Same for app2, app3..appN. And file application.properies will contains common properties for all of services
I had a similar requirement. My approach is to pass the config directory to tomcat as environment variable, lets assume you pass
spring.config.location=/home/user/config
Then under /home/user/config folder you should have files matching the contextPath for each application instance. In your case you should have
app1.properties
app2.properties
app3.properties
If you don't want to duplicate common parameters, you can put all common properties in a separate file and use "spring.config.import" to import it from each application specific configuration file. Note that importing another file is supported since Spring Boot 2.4 See section "Importing Additional Configuration"
For Spring Boot application to load the properties file according to the context path, you should override "createRootApplicationContext" to get the context path, and override "configure" to set it as the properties file name as below.
#SpringBootApplication
public class TestApp extends SpringBootServletInitializer {
private static Class<TestApp> applicationClass = TestApp.class;
private String contextPath;
public static void main(String[] args) {
try {
SpringApplication.run(applicationClass, args);
}
catch (Exception e) {
// Handle error
}
}
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(applicationClass).properties("spring.config.name:" + contextPath);
}
#Override
protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
contextPath = servletContext.getContextPath();
return super.createRootApplicationContext(servletContext);
}
}
You can try the RoutingDataSource approach, This lets you switch the datasource at runtime/realtime.
To implement this you have to pass some datasource identifier initially (You can set it in your auth token for rest based requests or in a session)
eg -
localhost:80/yourcontext/{app1}
localhost:80/yourcontext/{app2}
localhost:80/yourcontext/app3
Here app1, app2, app3 will be your datasource identifiers
App controller
#Controller
public class AppController extends SuperController{
#GetMapping("/{client}")
public String login(ModelMap map, #PathVariable("client") String client, HttpServletRequest request){
//Logic to set the path variable
return "loginPage";
}
}
Routing datasource Config
#Configuration
#EnableTransactionManagement
#ComponentScan({ "com.components_package" })
#PropertySource(value = { "classpath:database.properties" })
public class RoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
String tenant = DbContextHolder.getDbType();
LoginDetails LoginDetails = currentUser();
if(LoginDetails != null){
tenant = LoginDetails.getTenantId();
}
logger.debug("tenant >>>> "+tenant);
return tenant;
}
private LoginDetails currentUser() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication instanceof UserAuthentication) {
return ((UserAuthentication) authentication).getDetails();
}
//If not authenticated return anonymous user
return null;
}
}
Hibernate config
#PropertySource(value = { "classpath:database.properties" })
public class HibernateConfiguration {
#Autowired
private Environment environment;
#Bean
#Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dynamicDataSource());
em.setPackagesToScan("com.entities_package");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(hibernateProperties());
return em;
}
/** RoutingDataSource datasource initialized enabling the application
* to switch multiple databases at runtime
* #return
*/
private RoutingDataSource dynamicDataSource(){
RoutingDataSource routingDataSource = new RoutingDataSource();
/*Set default datasource*/
routingDataSource.setDefaultTargetDataSource(defaultDataSource());
/*Set all datasource map*/
routingDataSource.setTargetDataSources(fetchDatasourceMap());
routingDataSource.afterPropertiesSet();
return routingDataSource;
}
/** This is a default datasource
* #return
*/
private DataSource defaultDataSource() {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
DataSource dataSource = dsLookup.getDataSource("jdbc/defaultDs");
return dataSource;
}
/** This method sets all predefined client specific datasources in a map
* #return Map<Object, Object>
*/
private Map<Object, Object> fetchDatasourceMap(){
Map<Object, Object> dataSourcesMap = new HashMap<>();
//database.clients=app1,app2,app3
String client = environment.getRequiredProperty("database.clients");
String[] allClients = client.split(",");
if(allClients != null && allClients.length > 0){
for (Integer i = 0; i < allClients.length; i++) {
String clientKey = allClients[i].trim();
dataSourcesMap.put(clientKey, dataSource(clientKey));
}
}
return dataSourcesMap;
}
private DataSource dataSource(String clientKey) {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
String lookupKey = "jdbc/"+clientKey;
DataSource dataSource = dsLookup.getDataSource(lookupKey);
return dataSource;
}
private Properties hibernateProperties() {
Properties properties = new Properties();
properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect"));
properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql"));
// properties.put("hibernate.format_sql",
// environment.getRequiredProperty("hibernate.format_sql"));
return properties;
}
#Bean
JpaTransactionManager transactionManager() {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return transactionManager;
}
}
You have to define your datasource properties in context.xml for JNDI lookup.
Hope this helps
I would like to create instalation process for the first run of my Spring MVC app. Something like wordpress has (when you first run it, you need to specify DB connection and your first admin account... etc.
I have tried it with spring but the spring won't start because when DataSource Bean is not connected, it will simply fail to start. It always fail when transaction manager bean is beeing created:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'txManager' defined in SpringWebConfig: Invocation of init method failed; nested exception is java.lang.IllegalArgumentException: Property 'sessionFactory' is required|#]
Is there any way how to start the spring/hibernate app without transaction manager and then load it on-the-fly when user configures his db access details ? I know there is way to do it with application.properties but I want to create a simple install process just like in wordpress so it is most convenient for non-tech users.
EDIT 1: My current code:
#EnableWebMvc
#Configuration
#ComponentScan(basePackages = {"test"}) //package scan
public class SpringWebConfig implements WebMvcConfigurer {
#Bean(name = "dataSource")
public BasicDataSource dataSource() {
if (Config.getInstance().isInstalled()) {
BasicDataSource ds = new BasicDataSource();
ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
ds.setUrl("jdbc:mysql://" + Config.getInstance().getDburl() + "/" + Config.getInstance().getDbname());
ds.setUsername(Config.getInstance().getDbuser());
ds.setPassword(Config.getInstance().getDbpass());
this.ds = ds;
}
return ds;
}
#Bean
public HibernateTransactionManager txManager() {
if (sessionFactory() == null) {
return new HibernateTransactionManager();
}
else return new HibernateTransactionManager(sessionFactory());
}
#Bean
public SessionFactory sessionFactory() {
try {
LocalSessionFactoryBuilder builder = new LocalSessionFactoryBuilder(dataSource());
builder.scanPackages(dbEntity).addProperties(getHibernateProperties());
return builder.buildSessionFactory();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Adding #Lazy to all 3 #Beans solves the issue and hibernate is not requiered at the startup of Spring MVC (configured by class that implements WebMvcConfigurer), this allows to display install page for the user so he can define DB access details.
I do have a Spring Boot 5 application and I also have it running against one IBM MQ server.
Now we want it to connect to three or more MQ servers. My intention is now to just add XY connection infos to the environment and then I get XY MQConnectionFactory beans and al the other beans that are needed for processing.
At the moment this is what I have:
#Bean
#Qualifier(value="MQConnection")
public MQConnectionFactory getIbmConnectionFactory() throws JMSException {
MQConnectionFactory factory = new MQConnectionFactory();
// seeting all the parameters here
return factory;
}
But this is quite static. Is there an elegant way of doing this?
I stumbled about IntegrationFlow. Is this a possibly working solution?
Thanks for all your tipps!
KR
Solution
Based on Artem Bilan's response I built this class.
#Configuration
public class ConnectionWithIntegrationFlowMulti {
protected static final Logger LOG = Logger.create();
#Value("${mq.queue.jms.sources.queue.queue-manager}")
private String queueManager;
#Autowired
private ConnectionConfig connectionConfig;
#Autowired
private SSLSocketFactory sslSocketFactory;
#Bean
public MessageChannel queureader() {
return new DirectChannel();
}
#Autowired
private IntegrationFlowContext flowContext;
#PostConstruct
public void processBeanDefinitionRegistry() throws BeansException {
Assert.notEmpty(connectionConfig.getTab().getLocations(), "At least one CCDT file locations must be provided.");
for (String tabLocation : connectionConfig.getTab().getLocations()) {
try {
IntegrationFlowRegistration theFlow = this.flowContext.registration(createFlow(tabLocation)).register();
LOG.info("Registered bean flow for %s with id = %s", queueManager, theFlow.getId());
} catch (JMSException e) {
LOG.error(e);
}
}
}
public IntegrationFlow createFlow(String tabLocation) throws JMSException {
LOG.info("creating ibmInbound");
return IntegrationFlows.from(Jms.messageDrivenChannelAdapter(getConnection(tabLocation)).destination(createDestinationBean()))
.handle(m -> LOG.info("received payload: " + m.getPayload().toString()))
.get();
}
public MQConnectionFactory getConnection(String tabLocation) throws JMSException {
MQConnectionFactory factory = new MQConnectionFactory();
// doing stuff
return factory;
}
#Bean
public MQQueue createDestinationBean() {
LOG.info("creating destination bean");
MQQueue queue = new MQQueue();
try {
queue.setBaseQueueManagerName(queueManager);
queue.setBaseQueueName(queueName);
} catch (Exception e) {
LOG.error(e, "destination bean: Error for integration flow");
}
return queue;
}
}
With Spring Integration you can create IntegrationFlow instances dynamically at runtime. For that purpose there is an IntegrationFlowContext with its registration() API. The returned IntegrationFlowRegistrationBuilder as a callback like:
/**
* Add an object which will be registered as an {#link IntegrationFlow} dependant bean in the
* application context. Usually it is some support component, which needs an application context.
* For example dynamically created connection factories or header mappers for AMQP, JMS, TCP etc.
* #param bean an additional arbitrary bean to register into the application context.
* #return the current builder instance
*/
IntegrationFlowRegistrationBuilder addBean(Object bean);
So, your MQConnectionFactory instances can be populated alongside with the other flow, used as references in the particular JMS components and registered as beans, too.
See more info in docs: https://docs.spring.io/spring-integration/docs/5.2.3.RELEASE/reference/html/dsl.html#java-dsl-runtime-flows
If you are fine with creating them statically, you can create the beans as you are now (each having a unique qualifier), but you can access them all dynamically in your services / components by having an #Autowired List<MQConnectionFactory> field or #Autowired Map<String, MQConnectionFactory> field. Spring will automatically populate the fields with all of the beans of type MQConnectionFactory
In the the Map implementation, the String will be the qualifier value.
If you also want to create the beans dynamically based on some properties, etc, it gets a little more complicated. You will need to look into something along the lines of instantiating beans at runtime
I have a code base which is using for two different applications. some of my spring service classes has annotation #Transactional. On server start I would like to disable #Transactional based on some configuration.
The below is my configuration Class.
#Configuration
#EnableTransactionManagement
#PropertySource("classpath:application.properties")
public class WebAppConfig {
private static final String PROPERTY_NAME_DATABASE_DRIVER = "db.driver";
#Resource
private Environment env;
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getRequiredProperty(PROPERTY_NAME_DATABASE_DRIVER));
dataSource.setUrl(url);
dataSource.setUsername(userId);
dataSource.setPassword(password);
return dataSource;
}
#Bean
public PlatformTransactionManager txManager() {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
if(appName.equqls("ABC")) {
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_NEVER);
}else {
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
}
CustomDataSourceTransactionManager txM=new CustomDataSourceTransactionManager(def);
txM.setDataSource(dataSource());
return txM;
}
#Bean
public JdbcTemplate jdbcTemplate() {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
}
I am trying to ovveried methods in DataSourceTransactionManager to make the functionality. But still it is trying to commit/rollback the transaction at end of transaction. Since there is no database connection available it is throwing exception.
If I keep #Transactional(propagation=Propagation.NEVER), everything works perfectly, but I cannot modify it as another app is using the same code base and it is necessary in that case.
I would like to know if there is a to make transaction fully disable from configuration without modifying #Transactional annotation.
I'm not sure if it would work but you can try to implement custom TransactionInterceptor and override its method that wraps invocation into a transaction, by removing that transactional stuff. Something like this:
public class NoOpTransactionInterceptor extends TransactionInterceptor {
#Override
protected Object invokeWithinTransaction(
Method method,
Class<?> targetClass,
InvocationCallback invocation
) throws Throwable {
// Simply invoke the original unwrapped code
return invocation.proceedWithInvocation();
}
}
Then you declare a conditional bean in one of #Configuration classes
// assuming this property is stored in Spring application properties file
#ConditionalOnProperty(name = "turnOffTransactions", havingValue = "true"))
#Bean
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionInterceptor transactionInterceptor(
/* default bean would be injected here */
TransactionAttributeSource transactionAttributeSource
) {
TransactionInterceptor interceptor = new NoOpTransactionInterceptor();
interceptor.setTransactionAttributeSource(transactionAttributeSource);
return interceptor;
}
Probably you gonna need additional configurations, I can't verify that right now
I am getting the following error when trying to autowire two beans using
No qualifying bean of type [javax.jms.ConnectionFactory] is defined:
expected single matching bean but found 2: aConnectionFactory, bConnectionFactory
Description:
Parameter 1 of method jmsListenerContainerFactory in org.springframework.boot.autoconfigure.jms.JmsAnnotationDrivenConfiguration required a single bean, but 2 were found:
- aConnectionFactory: defined by method 'aConnectionFactory' in package.Application
- bConnectionFactory: defined by method 'bConnectionFactory' in package.Application
Action:
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
I have this annotation driven configuration:
#SpringBootApplication
#EnableIntegration
#IntegrationComponentScan
public class Application extends SpringBootServletInitializer implements
WebApplicationInitializer {
#Resource(name = "aConnectionFactory")
private ConnectionFactory aConnectionFactory;
#Resource(name = "bConnectionFactory")
private ConnectionFactory bConnectionFactory;
#Bean
public IntegrationFlow jmsInboundFlow() {
return IntegrationFlows
.from(
Jms.inboundAdapter(aConnectionFactory)
.destination(aQueue),
e -> e.poller( Pollers.fixedRate(100,
TimeUnit.MILLISECONDS).maxMessagesPerPoll(100))
).channel("entrypoint")
.get();
}
#Bean
public IntegrationFlow jmsInboundFlowB() {
return IntegrationFlows
.from(
Jms.inboundAdapter(bConnectionFactory)
.destination(bQueue),
e -> e.poller( Pollers.fixedRate(100,
TimeUnit.MILLISECONDS).maxMessagesPerPoll(100))
).channel("entrypoint")
.get();
}
#Bean(name = "aConnectionFactory")
#Profile({"weblogic"})
public ConnectionFactory aConnectionFactory() {
ConnectionFactory factory = null;
JndiTemplate jndi = new JndiTemplate();
try {
factory = (ConnectionFactory) jndi.lookup("jms/ConnectionFactory");
} catch (NamingException e) {
logger.error("NamingException for jms/ConnectionFactory", e);
}
return factory;
}
#Bean(name = "bConnectionFactory")
#Profile({"weblogic"})
public ConnectionFactory bConnectionFactory() {
ConnectionFactory factory = null;
JndiTemplate jndi = new JndiTemplate();
try {
factory = (ConnectionFactory) jndi.lookup("jms/ConnectionFactory");
} catch (NamingException e) {
logger.error("NamingException for jms/ConnectionFactory", e);
}
return factory;
}
}
Any ideas what's wrong in this code? This seems to be straight forward, but specifying the Qualifier doesn't work, I have also tried to use #Resource. What am I missing there?
Any help appreciated.
Nothing wrong with your code.
That is just JmsAnnotationDrivenConfiguration from Spring Boot which doesn't like your two ConnectionFactory beans, but requires only one.
Why just don't follow with that report recommendations and mark one of them with the #Primary?
Looks like you don't use Spring Boot JMS auto-configuration feature, so that would be just straightforward to disable JmsAnnotationDrivenConfiguration: http://docs.spring.io/spring-boot/docs/1.4.1.RELEASE/reference/htmlsingle/#using-boot-disabling-specific-auto-configuration
The problem consist
javax.jms.ConnectionFactory is singleton, you need one object that type!
Solutions for your problem:
If you need two object that create objects and extend ConnectionFactory
them change scope as needed.
try #Scope("singleton") or #Scope("prototype").
if you receive error, make a objects. then use a scope #Scope("singleton")
"Other Two" disfigure the other class that is already using and setting such an.