I'm trying to configure a database client instance using Guice to create an AbstractModule, but i can't access application.conf using dependency injection, since the injector is not created yet.
Here is my code
#Singleton
public class DatastoreModule extends AbstractModule {
#Inject
private Config config;
#Override
public void configure() {
MongoClient mongo = new MongoClient(
config.getString("mongodb.host"),
config.getInt("mongodb.port")
);
Morphia morphia = new Morphia();
Datastore datastore = morphia.createDatastore(
mongo,
config.getString("mongodb.databaseName")
);
bind(Datastore.class).toInstance(datastore);
}
}
How can i access the configuration without using the deprecated Play.configuration API?
You can pass it in the constructor (in Scala). Here is the example from my project
class Guard(environment: Environment, configuration: Configuration) extends AbstractModule{
In Java it is the same:
public class DatastoreModule extends AbstractModule {
private final Environment environment;
private final Config config;
public DatastoreModule(Environment environment, Config config) {
this.environment = environment;
this.config = config;
}
...
}
More details: https://www.playframework.com/documentation/2.6.x/JavaDependencyInjection#Configurable-bindings
Just do not overuse it:
In most cases, if you need to access Config when you create a component, you should inject the Config object into the component itself or into the component’s Provider. Then you can read the Config when you create the component. You usually don’t need to read Config when you create the bindings for the component.
I very very rarely use it. It is always almost better to inject the configuration into component itself.
Related
I am trying to create something which will auto-create beans based on configurable properties (from application.yml and the like).
Since I can't just access the properties component like I normally would in the BeanFactoryPostProcessor, I'm kind of stumped how I can access them.
How can I access application properties in BeanFactoryPostProcessor?
If you want to access properties in a type-safe manner in a BeanFactoryPostProcessor you'll need to bind them from the Environment yourself using the Binder API. This is essentially what Boot itself does to support #ConfigurationProperties beans.
Your BeanFactoryPostProcessor would look something like this:
#Bean
public static BeanFactoryPostProcessor beanFactoryPostProcessor(
Environment environment) {
return new BeanFactoryPostProcessor() {
#Override
public void postProcessBeanFactory(
ConfigurableListableBeanFactory beanFactory) throws BeansException {
BindResult<ExampleProperties> result = Binder.get(environment)
.bind("com.example.prefix", ExampleProperties.class);
ExampleProperties properties = result.get();
// Use the properties to post-process the bean factory as needed
}
};
}
I didn't want to use the solution above that used an #Bean producer method since it's contrary to the recommended approach of annotating a class with #Component and picking up via component scanning. Fortunately it's straightforward to do that by implementing EnvironmentAware:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class ConditionalDependencyPostProcessor implements BeanFactoryPostProcessor, EnvironmentAware {
/** Logger instance. */
private final Logger logger = LoggerFactory.getLogger(ConditionalDependencyPostProcessor.class);
/** Spring environment. */
private Environment environment;
#Override
public void setEnvironment(final Environment env) {
environment = env;
}
...
private boolean hasRequiredProfiles(final DependencyInfo info) {
final Set<String> activeProfiles = new HashSet<>(Arrays.asList(environment.getActiveProfiles()));
for (String profile : info.requiredProfiles) {
if (!activeProfiles.contains(profile)) {
return false;
}
}
return true;
}
I should note what did NOT work: trying to autowire an Environment constructor argument. BeanFactoryPostProcessors require a no-argument constructor and don't support autowiring, which is itself a feature implemented by another post processor, AutowiredAnnotationBeanPostProcessor.
I'm currently trying to write an Integration test class which uses Spring Data Mongo repositories.
I use an embedded Mongo instance provided by the de.flapdoodle.embed.mongo dependency. Spring Data documentation specifies that we only have to put this dependency in the project and the EmbedMongoAutoConfiguration takes care of the rest.
For now, that's ok, and setting the port to 0 makes the auto configuration process to find a free port to launch the mongo instance on.
This feature is necessary for me to avoid collision with other tests (which are run on a Jenkins CI server along with other project of my company).
The problem comes now, I want to be able to inject some test data from some external file before each of my test method run. I found out that NoSQL Unit can do this with a simple method annotation and a JUnit #Rule.
Here is an example:
#Value("${local.mongo.port}")
private int mongoPort; // <- still 0 a the time the Rule below is created.
#Rule
public MongoDbRule managedMongoDb = new MongoDbRule(MongoDbConfigurationBuilder.mongoDb().databaseName("myAwesomeDb").port(mongoPort).build());
#Test
#UsingDataSet(locations = "testdata.json", loadStrategy = LoadStrategyEnum.CLEAN_INSERT)
public void testMyData() {
// ...
}
My problem is that, the #Rule needs the Mongo port in its builder to instantiate the underlying MongoClient, but at the time the #Rule is instantiated, the Spring context is not fully initialized and the EmbeddedMongoAutoConfiguration has not published the port yet.
So my question is, is there anyone who has ever used the Embedded Mongo feature with NoSQL Unit, and is there any way to, for example create the #Rule after the Spring context is initialized ?
I was wondering if finding the free port myself (in a static way), setting it to the #Rule and then tell the EmbeddedMongoAutoConfiguration to use it by overriding the IMongodConfig bean was a good idea ? or is there a "simpler" way ?
Note: I just saw that flapdoodle library provides a class and a static method to find a free server port and its used by Spring like this:
Network.getFreeServerPort(getHost()), Network.localhostIsIPv6()))
Thanks everyone in advance!
EDIT:
I tried the solution I talked just above, and it seems to work, though I still think it's a bit "verbose" and dirty.
private static final Logger log = LoggerFactory.getLogger(MyAwesomeIT.class);
private static int mongoPort;
static {
try {
mongoPort = Network.getFreeServerPort();
} catch (IOException e) {
log.error("Error while trying to find a free port for Mongo", e);
mongoPort = -1; // test should then not work
}
}
#Rule
public MongoDbRule managedMongoDb = new MongoDbRule(MongoDbConfigurationBuilder.mongoDb().databaseName("myAwesomeDb").port(mongoPort).build());
then in the associated configuration class :
#Configuration
#EnableAutoConfiguration
#EnableMongoRepositories
#EnableConfigurationProperties(MongoProperties.class)
static class ContextConfiguration {
#Autowired
private MongoProperties mongoProperties;
#PostConstruct
public void init() {
// Here, I override the port property
mongoProperties.setPort(mongoPort);
}
}
Refining the solution given by #user6599111, it is possible to obtain the port randomly chosen by Flapdoodle Embedded Mongo, simply injecting an object of type IMongodConfig.
Spring Boot builds automagically this object for you, as stated here.
Then, the configuration class will become the following.
#Configuration
#EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
public class MongoConfiguration {
#Autowired
private Environment environment;
#Autowired
private MongoProperties properties;
#Autowired(required = false)
private MongoClientOptions options;
#Autowired
private IMongodConfig mongoConfig;
#Bean
public MongoClient mongo() throws Exception {
properties.setPort(mongoConfig.net().getPort());
return properties.createMongoClient(this.options, this.environment);
}
}
I had the same problem and this was my solution
#Configuration
public class EmbeddedMongoConfig extends AbstractMongoConfiguration {
#Autowired
private Environment environment;
#Autowired
private MongoProperties properties;
#Autowired(required = false)
private MongoClientOptions options;
#Override
protected String getDatabaseName() {
return properties.getDatabase();
}
#Override
#Bean(destroyMethod = "close")
public Mongo mongo() throws Exception {
properties.setPort(Network.getFreeServerPort());
return properties.createMongoClient(this.options, this.environment);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#SpringApplicationConfiguration(classes = { AppRunner.class, EmbeddedMongoConfig.class })
public class BaseTest {
}
public class CategoryServiceTest extends BaseTest{
#Autowired
private CategoryService categoryService;
#Test
public void someTest(){
fail("Test not implemented");
}
}
I have tried this:
int mongoPort = SocketUtils.findAvailableTcpPort();
Source:
https://docs.spring.io/spring/docs/4.0.5.RELEASE/javadoc-api/org/springframework/util/SocketUtils.html
This worked for me.
We had embedded Mongo running for unit tests and where there were multiple applications building on Jenkins, some of them failed since the port was same for everyone. Manually changing the ports was also tried but since there were many applications and some of them used a common base class, it was failing.
I am not sure about the #Rule part but you can try this.
I am using spring framework for 2 different applications. Let's say both of the applications talk to one single MongoDB database. Following is how I configure MongoDB in both the applications:
#Configuration
#PropertySource("file:/etc/x/y/mongodb.properties")
public class MongoConfiguration {
#Autowired
private Environment env;
#Bean
public UserCredentials mongoCredentials() {
String mongoUserName = env.getProperty("mongodb.username");
String mongoPassword = env.getProperty("mongodb.password");
UserCredentials credentials = new UserCredentials(mongoUserName, mongoPassword);
return credentials;
}
#Bean
public MongoClient mongoClient() throws Exception {
String mongoUrl = env.getProperty("mongodb.url");
String mongoPort = env.getProperty("mongodb.port");
MongoClient mongo = new MongoClient(mongoUrl, Integer.valueOf(mongoPort));
return mongo;
}
#Bean(name="mongoTemplate")
public MongoTemplate mongoTemplate() throws Exception {
String mongoDatabaseName = env.getProperty("mongodb.databasename");
MongoTemplate mongoTemplate = new MongoTemplate(mongoClient(), mongoDatabaseName, mongoCredentials());
return mongoTemplate;
}
Now, this piece of code is duplicated in two different application configurations. How do I avoid doing this configuration at two different places?
Treat it the same as a util class that you don't want to duplicate: move you config file to a separate project and make both your applications include that projects.
If you need to add additional project-specific configuration, Spring provides the #Import annotation that allows you to import configuration from separate classes, so you can create two project specific configuration classes that both import the generic configuration from the shared lib and supply their own individual beans and property sources, e.g.:
#Configuration
#PropertySource("classpath:/com/appspecific/app.properties")
#Import(com.genericlib.BaseConfig.class)
public class AppConfig {
#Inject BaseConfig baseConfig;
#Bean
public MyBean myBean() {
// reference the base config context
return new MyBean(baseConfig.getSomething());
}
}
Use Spring Boot, and optionally include the #PropertySource to add to the environment. It will collect all the MongoDB information and configure a client and template for you.
I've a Configuration.java singleton class (with many properties loaded by a file) that I would like to inject in some classes in my Application.
So, I bind my injection in the ResourceConfig class with an AbstractBinder.
Now, I need to use this Configuration class in this ResourceConfig.
For example, in my Configuration class there is a property named "packages", that I have to use in ResourceConfig class in order to register package.
The issue is that the injection is not starting in the ResourceConfig class.
class Configuration {
//many properties
String packages = "";
}
class MyResourceConfig extends ResourceConfig {
#Inject
Configuration configuration;
MyResourceConfig() {
...
register(MyBinder.class); //with many injection
...
packages(configuration.packages);
}
}
So could you please advice me how to have this lifecycle ? (maybe I have to use another jersey class ?)
I realize it is a sort of a workaround, but you can achieve it by using static access to the CDI singleton object.
Something like:
#ApplicationPath("/rest")
public class RestApplication extends ResourceConfig {
public RestApplication() {
super();
super.packages(true, "com.package.name");
register(JacksonFeature.class);
register(JacksonObjectMapperProvider.class);
ModelConverters.getInstance().addConverter(
new CustomObjectMapperModelResolver(getObjectMapperProvider()));
// get the instance here ^
OpenApiResource openApiResource = new OpenApiResource();
register(openApiResource);
AcceptHeaderOpenApiResource ahoar = new AcceptHeaderOpenApiResource();
register(ahoar);
}
private static JacksonObjectMapperProvider getObjectMapperProvider() {
return CDI.current().select(JacksonObjectMapperProvider.class).get();
}
}
We are migrating some of our data services from Jersey 1.x using jersey-spring to Jersey 2.x using jersey-spring3.
We have a few test classes that inherit from JerseyTest. Some of these classes use custom applicationContext.xml files that are not specified in the web.xml file.
In Jersey 1.x the test classes that extended JerseyTest could call the super constructor with a WebappDescriptor.Builder to which a context parameter could be passed to set or override the application context path.
E.g.
public MyTestClassThatExtendsJerseyTest()
{
super(new WebAppDescriptor.Builder("com.helloworld")
.contextParam( "contextConfigLocation", "classpath:helloContext.xml")
.servletClass(SpringServlet.class)
.contextListenerClass(ContextLoaderListener.class)
.requestListenerClass(RequestContextListener.class).build());
}
How can the same be achieved with Jersey 2.x?
I have combed through the API docs, user guides and some of the sources but was unable to find an answer.
This didn't work for me as I was not using the .xml style configuration, I was using #Configuration annotations. So I had to directly provide the application context to the ResourceConfig class.
I defined the configure method in my JerseyTest like so:
#Override
protected Application configure() {
ResourceConfig rc = new ResourceConfig();
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
rc.property("contextConfig", ctx);
}
where SpringConfig.class is my class with the #Configuration annotation and
importing org.springframework.context.annotation.AnnotationConfigApplicationContext
Lets assume your Application looks like:
#ApplicationPath("/")
public class MyApplication extends ResourceConfig {
/**
* Register JAX-RS application components.
*/
public MyApplication () {
// Register RequestContextFilter from Spring integration module.
register(RequestContextFilter.class);
// Register JAX-RS root resource.
register(JerseySpringResource.class);
}
}
Your JAX-RS root resource like:
#Path("spring-hello")
public class JerseySpringResource {
#Autowired
private GreetingService greetingService;
#Inject
private DateTimeService timeService;
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getHello() {
return String.format("%s: %s", timeService.getDateTime(), greetingService.greet("World"));
}
}
And you have spring descriptor named helloContext.xml available directly from your class-path. Now you want to test your getHello resource method using Jersey Test Framework. You can write your test like:
public class JerseySpringResourceTest extends JerseyTest {
#Override
protected Application configure() {
// Enable logging.
enable(TestProperties.LOG_TRAFFIC);
enable(TestProperties.DUMP_ENTITY);
// Create an instance of MyApplication ...
return new MyApplication()
// ... and pass "contextConfigLocation" property to Spring integration.
.property("contextConfigLocation", "classpath:helloContext.xml");
}
#Test
public void testJerseyResource() {
// Make a better test method than simply outputting the result.
System.out.println(target("spring-hello").request().get(String.class));
}
}