Spring dependency injection in a non-spring boot application - spring

I writing a library which doesn't have a main and isn't a Spring Boot application but I'd still like to use Spring Framework's dependency injection to load the dependencies of my classes. I'd like this library to be available to everyone, even developers who aren't going to be using Spring.
Is there a way for me to take advantage of Spring Framework's dependency injection even if the users of my library won't be using Spring?
Currently I'm importing my library into another one of my apps but my dependencies are turning out as null. Is there a way to get around this?
#Component
public class Client
{
#Autowired
private ClientService clientService; // this is null
public static Client createClientWithCredentials(String clientId, String clientSecret)
{
return new Client(clientId, clientSecret);
}
private Client(String clientId, String clientSecret)
{
// ...
}
}
#Service
public class ClientService {
// ...
}
Someone using my library would use instantiate it like this.
...
// Non-Spring project
Client client = Client.createClientWithCredentials("id", "secret");
...
Unfortunately using client will at some point throw a NullPointerException since clientService is null. I'm also testing my app this way and this is why I know that I have null dependencies.
How can I take advantage of Spring's Dependency Injection if my app is never run but only used as a library?
Is this possible or do I need to use the old way of every class constructing it's own dependencies?

Related

Using Gson instead of Jackson in Spring Webflux

We have many Spring MVC projects already, which all use gson instead of jackson for response body encode. Our bean classes are all written based on gson annotation. Now I am setting up a Spring Webflux restful server. It would save a lot of work if we can use the old bean classes from our Spring MVC projects.
I have tried spring.http.converters.preferred-json-mapper=gson property to no avail.
I have tried HttpMessageConverter bean, which is included in webflux packages, but that does not work as in the Spring MVC projects.
I googled a lot and the only thing helpful is to implement org.springframework.http.codec.HttpMessageEncoder class and set it to WebFluxConfigurer.configureHttpMessageCodecs() method:
#Configuration
public class WebConfiguration implements WebFluxConfigurer {
#Override
public void configureHttpMessageCodecs(ServerCodecConfigurer configurer) {
configurer.customCodecs().decoder(new GsonHttpMessageDecoder());
configurer.customCodecs().encoder(new GsonHttpMessageEncoder());
}
private static class GsonHttpMessageEncoder implements HttpMessageEncoder {
...
}
private static class GsonHttpMessageDecoder implements HttpMessageDecoder {
...
}
}
I haven't try this out yet, since it is a little complex. Is there some easy way to replace jackson with gson in Spring Webflux?
Any help is appreciated.
Spring Framework doesn't support GSON as a WebFlux Encoder / Decoder for now. Feel free to follow up on the dedicated issue.
Note that as far as I know, GSON doesn't support non-blocking parsing so even if the support is implemented in Framework, it won't be complete and should not cover streaming input use cases.

Injecting properties in Spring library

I am trying to develop a library in Spring that need property from my application. My application get properties from a Spring Cloud Config Server.
So in my library, I have #Configuration annoted class.
#Configuration
public class ServiceConfiguration {
#Value("${idwebservice.uri}")
private String identificationWsUri;
}
I would like this property to be injected through my app after fetching the config server. My application is a Spring Boot application so I didn't implement the fetch of the config server myself.
I am sure that this property is well injected in my app but if I run the app, I've got this error:
Could not resolve placeholder 'idwebservice.uri' in value "${idwebservice.uri}"
I don't know if it is possible so I chose a different approach. My String is now an autowired String
#Autowired
private String identificationWsUri;
and in my app
#SpringBootApplication
public class Application {
#Value("${idwebservice.uri"})
String identificationWsUri;
#Bean
public String identificationWsUri() {
return identificationWsUri;
}
}
I prefered to answer to my question because I saw a similar question and maybe it will help some people struggling with the same problem.

Spring Boot application runs fine via Maven but not via IDE Intellij IDEA

I have a spring boot application which runs fine via Maven's mvn spring-boot:run command. However, when I try to run it through the IDE, which is Intellij IDEA 2017.2.1 in my case, it fails because it could not #Autowire a data source.
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.myApp.Application required a bean of type 'javax.sql.DataSource' that could not be found.
Action:
Consider defining a bean of type 'javax.sql.DataSource' in your configuration.
The original authors of this code base have the main class, which starts the application, accepting constructor arguments for the data source, an approach I am unfamiliar with as I am used to just doing it through the application.properties file and letting Spring Boot wire up it's own DataSource.
#EnableTransactionManagement
#SpringBootApplication
#EnableCaching
public class Application extends JpaBaseConfiguration {
protected Application(DataSource dataSource, JpaProperties properties,
ObjectProvider<JtaTransactionManager> jtaTransactionManagerProvider,
ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
super(dataSource, properties, jtaTransactionManagerProvider, transactionManagerCustomizers);
}
In IDEA, I've noticed that the datasource and the properties arguments to this constructor are underlined in red. For datasource the IDE is complaining that two beans exist and it doesn't know which to autowire between XADataSourceAutoConfiguration.class and DataSourceConfiguration.class. As for the other argument to the construction which is underlined in red, properties, it can't find any beans, the IDE complains that no bean of type JpaProperties is found. Here are some other methods which are overridden in the main application starter class,
#Override
protected AbstractJpaVendorAdapter createJpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
#Override
protected Map<String, Object> getVendorProperties() {
Map<String, Object> vendorProperties = new LinkedHashMap<>();
vendorProperties.putAll(getProperties().getHibernateProperties(getDataSource()));
return vendorProperties;
}
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
Unfortunately, because I am not familiar with this approach of using the constructor to configure/auto-configure the application in Spring Boot, I am unsure of a few things, but my exact question is why does the application run fine with Maven but not in Intellij IDEA? Moreover, since I don't have access to the original authors to this proprietary code base, I'd love to know why, if anyone can even give me a hint, they have configured the constructor as such as opposed to default autoconfiguration. I also have an integration test which I wrote that I am trying to run but this test, whether run through the IDE or via Maven's failsafe plugin also results in the same error with the DataSource not being #Autowired. So this is another question as to why this test won't run through Maven when the main application will. Here's my integration test,
#RunWith(SpringJUnit4ClassRunner.class)
#WebMvcTest(value = TransactionController.class, secure = false)
public class TransactionControllerIT {
#Autowired
MockMvc mockMvc;
#Test
public void shouldInitiateTransfer() {
String transferTransaction =
"some json string I can't show here on stack overflow";
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post("/begin-transfer")
.accept(MediaType.APPLICATION_JSON).content(transferTransaction)
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = null;
try {
result = mockMvc.perform(requestBuilder).andReturn();
} catch (Exception e) {
fail("Exception in integration test!");
}
MockHttpServletResponse response = result.getResponse();
assertEquals(HttpStatus.CREATED.value(), response.getStatus());
}
}
Thank you for reading my question.
You can easily run any Spring Boot app from IDEA doing the following:
In Maven panel, go to Plugins, unfold spring-boot and right-click on "spring-boot:run". Then click on "Create your-project..." as shown in the image.
This way you can just start the application in a comfortable way from IDEA from the main toolbar:
This way you are still using the maven way, but integrated in IDEA. I don't really know why are you having those problems. I also experience some problems when trying to execute the spring boot app directly.
Your test is failing because it's using a slice test, #WebMvcTest these tests (#DataJpaTest, JsonTest) only loads a small part of the overall application context rather than everything that the application does on startup or a (#SpringBootTest) would.
When using a slice test it will use any annotations, and require any beans, defined within the #SpringBootApplication class.
E.g. because you have autowired beans defined and and two additional annotations for any slice test caching and transaction management will be enabled and it will always require these dependencies passed.
I would not make your main application class extend a configuration class in this way, it's overly complex and smells like XY problem. You should externalize configurations (and Enable annotations) to their own #Configuration class and leave the #SpringBootApplication as vanilla a possible to avoid these sort of errors.

Spring Data Rest integration with Spring HATEOAS

From various documents and tuts, I've earned and learned following points so far:
Spring Data Rest (SDR) used for exposing our Spring Data Repository as REST service, so that one can use it for self exploring, without any need of creating JAXRS manually. It only works for Repository layer, and we cannot control its way of working in terms of modification or addition other than the configuration using RepositoryRestMvcConfiguration. It uses Spring HATEOAS internally somewhere.
Spring HATEOAS is made for creating links within Entities we return through Controller or REST endpoints. We got ResourceSupport to extend our entity or Resource wrapper class to wrap our Entity to create or add links. There are several Annotations and classes to use such as #EnableHyperediaSupport and EntityLinks.
There may be some points which I am yet to explore or get to know about, but I was just curious about How can we combine SDR into HATEOAS link building process ? Say for eg.
EntityBean bean = repository.findByName(name);
Resource<EntityBean> resource = new Resource<EntityBean>(bean);
//JaxRsLinkBuilder.linkTo(TestResource.class).withRel("entity") // this also works
//ControllerLinkBuilder.linkTo(TestResource.class).withRel("myRel") // this also works
// I am curious how ControllerLinkBuilder and JaxRSLinkBuilder both are working for JaxRS.
//Here TestResource is my REST service class. now in below line:
resource.add(JaxRsLinkBuilder.linkTo(MyRepository.class).withRel("sdf")); //not working
// MyRepository is SDR exposed repository, which I thought should work but not working.
return resource;
So, I just wanted to include my exposed REST repository into manual HATEOAS link building process.. is it possible to do so ?
You should able to use Spring-HATEOAS ResourceProcessor to build links.
Example:
#Component
public class MyBeanResourceProcessor implements ResourceProcessor<Resource<MyBean>> {
#Autowired
private EntityLinks entityLinks;
public Resource<MyBean> process(Resource<MyBean> resource) {
MyBean mybean = resource.getContent();
// Do your linking here using entity class
//EntityBean bean = repository.findByName(name);
//Resource<EntityBean> resource = new Resource<EntityBean>(bean);
// assuming you are linking to a single resource and bean.getId() method... check entitylinks for other methods
//resource.add(entityLinks.linkForSingleResource(bean.class,bean.getId()).withRel("sdf"));
return resource;
}
}

How to use spring to resolve dependencies of an object created manually?

I would like to know if it's possible to use Spring to resolve the dependencies of an object created manually in my program. Take a look at the following class:
public class TestClass {
private MyDependency md;
public TestClass() {
}
...
public void methodThaUsesMyDependency() {
...
md.someMethod();
...
}
}
This TestClass is not a spring bean, but needs MyDependency, that is a spring bean. Is there some way I can inject this dependency through Spring, even if I instantiate TestClass with a new operator inside my code?
Thanks
Edit: The method I'm describing in my original answer below is the general way to accomplish DI external of the container. For your specific need - testing - I agree with DJ's answer. It's much more appropriate to use Spring's test support, for example:
#Test
#ContextConfiguration(locations = { "classpath*:**/applicationContext.xml" })
public class MyTest extends AbstractTestNGSpringContextTests {
#Resource
private MyDependency md;
#Test
public void myTest() {
...
While the above example is a TestNG test, there is also Junit support explained in 8.3.7.2. Context management and caching.
General approach: Annotate your class with #Configurable and utilize AspectJ load-time or compile-time weaving. See 6.8.1 in the Spring documentation on AOP for more details.
You can then annotate your instance variables with #Resource or #Autowired. Though they accomplish the same goal of dependency injection, I recommend using #Resource since it's a Java standard rather than Spring-specific.
Lastly, remember to consider using the transient keyword (or #Transient for JPA) if you plan on serializing or persisting the objects in the future. Chances are you don't want to serialize references to your DI'd repository, service, or component beans.
See the autowire() method on the AutowireCapableBeanFactory class. If you use an ClasspathXmlApplicationContext, you can get the factory with getAutowireCapableBeanFactory()
To get the ApplicationContext, you would need to use a static singleton or other central repository, such as JNDI or a Servlet container. See DefaultLocatorFactory on how to get an instance of the ApplicationContext.
If what you need is for testing purposes, Spring has good support for the scenario that you described above.
Check out Spring Reference manual section on Testing

Resources