Spring DI with Constructor - java

I have the following code:
#Component
public class MainBean {
#Autowired
private MyTask myTask
#Autowired
private TaskScheduler taskScheduler
public void start() {
String str = "Print something to console";
//somehow call constructor and pass str argument??
taskScheduler.execute(myTask);
}
}
#Component
public class MyTask implements Runnable {
private String str;
#Autowired
public MyTask(String str) {
this.str = str;
}
#Override
public void run() {
System.out.println(str);
}
}
I want to call MyTask and pass the str argument to the constructor. How can I do this? I cant find any good examples anywhere.

If I understand correctly what you are trying to do, a good solution would be the following:
#Component
public class MainBean {
#Autowired
private MyTaskFactory myTaskFactory
#Autowired
private TaskScheduler taskScheduler
public void start() {
String str = "Print something to console";
taskScheduler.execute(myTaskFactory.getTask(str));
}
}
public class MyTaskFactory {
public MyTask getTask(String str) {
return new MyTask(str);
}
}
#Configuration
public class MyTaskFactoryConfig {
#Bean
public MyTaskFactory myTaskFactory() {
return new MyTaskFactory();
}
}
Note that MyTask will then be changed to:
public class MyTask implements Runnable {
private String str;
public MyTask(String str) {
this.str = str;
}
#Override
public void run() {
System.out.println(str);
}
}

As seen from annotations, you've configured MyTask to be Spring managed. Also, str property is Spring managed, it should be injected by Spring to MyTask.
So, whenever you need MyTask instance, you don't create it yourself.
You #Autowire wherever you need.
public class ClassThatNeedsMyTaskInstances{
#Autowired
MyTask myTask;
}
But beware that by default, MyTask will be a singleton, so you may want to change its scope to prototype.
But this may be a good case where you don't let Spring manage MyTasks lifecycle. Instead you manage it yourself by creating instances using new, or factory

The following examples are for injecting the string from and XML and a Java-based configuration:
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="MyTaskConstructor" class="java.lang.String">
<constructor-arg type="char[]" value="My value"></constructor-arg>
</bean>
<bean id="myTask" class="com.package.MyTask">
<constructor-arg ref="MyTaskConstructor"/>
</bean>
</beans>
Java-based
#Configuration
public class MyTaskConfig {
#Bean
public String getMyTaskConstructor() {
return "My value";
}
#Bean
public MyTask myTask() {
return new MyTask(getMyTaskConstructor());
}
}

Related

Dependency injection with #Qualifier

Suppose the following code:
#Service
public class SearchService {
#Autowired
DependencyService dependencyService;
}
#Service
public class DependencyService {
private final Util util;
DependencyService(Util util){
this.util = util;
execute();
}
public void execute(){
util.execte();
}
}
#Component
public class ConcreteUtil implements Util{
#Override
public void execte() {
System.out.println("I'm the first concrete Util");
}
}
#Component
public class SecondConcreteUtil implements Util{
#Override
public void execte() {
System.out.println("I'm the second concrete Util");
}
}
In Plain Java I can do something like this:
public class SearchService {
DependencyService first = new DependencyService(new ConcreteUtil());
DependencyService second = new DependencyService(new SecondConcreteUtil());
}
But in Spring, it's not resolved by the client. We instruct Spring which bean to take from inside DependencyService:
DependencyService(#Qualifier("concreteUtil")Util util){
this.util = util;
execute();
}
And not like that:
#Autowired
#Qualifier("concreteUtil")
DependencyService dependencyService;
Why? To me this approach sounds like the opposite of decoupling. What do I miss? And how can Plain Java's result be achieved?
Edit:
I want this behaviour
public class SomeSerice {
DependencyService firstConcrete = new DependencyService(new ConcreteUtil());
}
public class OtherService {
DependencyService SecondConcrete = new DependencyService(new SecondConcreteUtil());
}
So I can reuse the code
You can declare multiple beans of type Dependency service inside some configuration class, like
#Qualifier("ConcreteUtilDepService")
#Bean
public DependencyService concreteUtilDS(#Qualifier("ConcreteUtil")Util util){
return new DependencyService (util);
}

Create Runnable in Spring xml

I'd like to create a Runnable in my spring configuration for providing to a ScheduledExecutorService bean. This Runnable should call a function on an existing bean. I can't figure out how to do this in Spring. This is what the equivalent code looks like:
private Runnable updater = new Runnable() {
public void run() {
thing.update();
}
};
Spring XML can only define beans from concrete classes. Runnable is an interface, not a concrete class.
You can write a concrete class that does what you want:
public class ThingUpdatingRunnable implements Runnable {
private final Thing thing;
public ThingUpdatingRunnable(Thing thing) {
this.thing = thing;
}
#Override
public void run() {
thing.update();
}
}
... and define it as a bean in the normal way:
<bean id = "thingUpdater" class = "org.me.ThingUpdatingRunnable">
<constructor-argument ref="thing"/>
</bean>
... but you should probably get into configuring Spring through annotated Java - most Spring developers now prefer this to XML:
#Configuration
public class ThingUpdaterConfiguration {
#Bean
public Runnable thingUpdater(Thing thing) {
return new Runnable() {
public void run() {
thing.update();
}
}
}
}
(Or with Java 8:)
#Bean
public Runnable thingUpdater(Thing thing) {
return () -> thing.update();
}

spring #value returns null

I'm using spring 3.2.2 and what to inject some string in my .properties file into a class. But always get null.
In my applicationContext.xml:
....
<context:component-scan base-package="com.mypackage">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/
</context:component-scan>
<context:property-placeholder location="classpath:/config/core/abc.properties"/>
....
in abc.properties:
path=testpath
in java code:
package com.mypackage
#Component
public class myclass implements Imyclass {
#Value("${path}")
private String path; //this always is null
...
public String getMyPath() {
return path+"myclass";
}
}
what I'm missing here?
[Edit]
the reason is in my code someone is calling new Imyclass().
Now there is another class impelement Imyclass:
package com.mypackage
#Component
public class myclass2 implements Imyclass {
#Value("${path}")
private String path; //this always is null
...
public String getMyPath() {
return path+"myclass2";
}
}
The function calling the Imyclass:
public class myClassContext {
public static String getPath(...){
return getImyclass(...).getMyPath();
}
private static Imyclass getImyclass(....){
Imyclass mc=null;
if (.....) {
mc=new myclass();
}
else{
mc=new myclass2();
}
return state;
}
}
What's the better way to manage the instance by Spring so the #value will inject correctly?
Do I have to inject both class into myClassContext as a property or use the #Configurable for both myclass and myclass2?

Spring Dependency Injection: How to Instantiate Class based on system property?

consider this example
I have interface
interface FSClient
String getMeData()
and classes implementing it
class MockFSClient implements FSClient {
public String getMeData() {
return “I am mock”;
}
}
class RestFSClient implements FSClient {
public String getMeData() {
final String data = // from web service
return data;
}
}
and a manager which looks like
class FSManager {
private FSClient fsclient;
#Autowired
public void FSManager(#Nonnull final FSClient fsClient) {
this.fsclient = fsClient;
}
}
I want to instantiate fsclient based on a system property
com.fs.mock=true
meaning if com.fs.mock=true, fsclient should be MockFSClient else fsClient should be RestFSClient
How can I do that?
Why do I need it?
so that I can decouple and do testing
Please help, I am new to Spring
Are you asking how you get the value of com.fs.mock because the answer is use the #Value annotation and a PropertyPlaceholderConfigurer bean.
If you are asking how do you create the actual object then as #Jakkra says use a factory that contains an if statement around the value of com.fs.mock. Its not the most elegant solution but it would work.
Example
public class ClientFactory {
#Value("${com.fs.mock}")
private boolean mockFlag;
public static returnClientInstance(){
if(mockFlag){
return new MockFSClient();
}
else{
return new RestFSClient();
}
}
}
Use profiles
...
<beans profile="dev">
<bean id="b1" class="MockFSClient" />
</beans>
<beans profile="uat,prod">
<bean id="b1" class="RestFSClient" />
</beans>
...
... -Dspring.profiles.active=dev ...
Probably the best approach is to use profiles as described by Evgeniy Dorofeev in which case you wouldn't need your custom System property. But here's another solution if you were to stick to your custom System property using Conditional Bean Configuration.
In this approach you would need two custom Conditions to encapsulate your conditions:
public class MockFSCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String fsMockProperty = context.getEnvironment().getProperty("com.fs.mock");
return fsMockProperty!=null && fsMockProperty.toLowerCase().equals("true");
}
}
public class RestFSCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String fsMockProperty = context.getEnvironment().getProperty("com.fs.mock");
return fsMockProperty==null || !fsMockProperty.toLowerCase().equals("true");
}
}
In which case your configuration class would look like this:
#Configuration
#ComponentScan(basePackages = {"your.beans.package"})
public class Config {
#Bean
public FSClient mockFSClient(){
return new MockFSClient();
}
#Bean
public FSClient restFSClient(){
return new RestFSClient();
}
#Bean(name="fsManager")
#Conditional(MockFSCondition.class)
public FSManager mockFsManager() {
return new FSManager(mockFSClient());
}
#Bean(name="fsManager")
#Conditional(RestFSCondition.class)
public FSManager restFsManager() {
return new FSManager(restFSClient());
}
}
Notice that we have created two beans with the same name fsManager each one of which is annotated with the two conditions above.
So now at runtime Spring would look for that custom System prop and given whether it's there/correct or not it will instantiate the correct version of FSManager constructor-injected with the right FSClient.

Spring Qualifier and property placeholder

Does anyone know if I should be able to use property placeholder as an expression in a Qualifier? I can't seem to get this working.
I am using spring 3.0.4.
#Controller
public class MyController {
#Autowired
#Qualifier("${service.class}")
Service service;
}
#Service
#Qualifier("ServiceA")
ServiceA implements Service {
public void print() {
System.out.println("printing ServiceA.print()");
}
}
#Service
#Qualifier("ServiceB")
ServiceB implements Service {
public void print() {
System.out.println("printing ServiceB.print()");
}
}
XML:
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="file:/etc/config.properties"/>
</bean>
config.properties:
config.properties
service.class=serviceB
This works. You can leave off the service names if you just use the default spring bean name. serviceA vs ServiceA, etc.
#Controller
class MyController {
#Autowired(required=false)
#Qualifier("Service")
Service service;
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("app-ctx.xml", MyController.class);
for(String s:context.getBeanDefinitionNames()){
System.out.println(s);
for(String t:context.getAliases(s)){
System.out.println("\t" + t);
}
}
context.getBean(MyController.class).service.print();
}
}
public interface Service {
void print();
}
#Service(value="ServiceA")
public class ServiceA implements example.Service {
public void print() {
System.out.println("printing ServiceA.print()");
}
}
#Service(value="ServiceB")
public class ServiceB implements example.Service {
public void print() {
System.out.println("printing ServiceB.print()");
}
}
XML:
<beans>
<alias name="${service.class}" alias="Service"/>
<context:property-placeholder location="example/app.properties"/>
<context:component-scan base-package="example"/>
<beans>
Props:
service.class=ServiceB
This solution works without XML and with properties file.
Yours classes improved:
MyController.java:
#Controller
public class MyController {
#Autowired
public MyController(#Qualifier("MyServiceAlias") MyService myService) {
myService.print();
}
}
ServiceA.java:
#Service("serviceA")
public class ServiceA implements MyService {
#Override
public void print() {
System.out.println("printing ServiceA.print()");
}
}
ServiceB.java:
#Service("serviceB")
public class ServiceB implements MyService {
#Override
public void print() {
System.out.println("printing ServiceB.print()");
}
}
application.properties (here you can change which class will be loaded):
service.class=serviceA
And important configuration file AppConfig.java:
#Configuration
public class AppConfig {
#Autowired
private ApplicationContext context;
#Bean
public MyService MyServiceAlias(#Value("${service.class}") String qualifier) {
return (MyService) context.getBean(qualifier);
}
}
Additional explanations:
Use #Qualifier only for field which will be autowired. For services, to specify bean name, use #Service.
If you want standard bean name you don't need to use #Service with specyify name. For example, standard bean name for ServiceA is serviceA (not ServiceA - see big first letter), so #Service("serviceA") redundant (#Service is enough).
I based AppConfig on this answer: Spring Bean Alias in JavaConfig.
This solution is better than this Spring Qualifier and property placeholder, because you don't need XML.
Tested on Spring Boot 1.5.7.
I would venture to guess the answer is no, just based on the write ups in a few javadoc pages. For example, see the docs for #Value:
http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/beans/factory/annotation/Value.html
Notice they make special mention of using expressions in the annotation. For comparison, the docs for #Qualifier:
http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/beans/factory/annotation/Qualifier.html
Which make no mention of expressions. Obviously not a definitive answer (but spring is generally very good on documentation). Also, if expressions were supported in the #Qualifier annotation I would expect they work the same way as the #Value annotation (just based on spring being a very consistent framework).
Spring 3.1 has the new profile bean feature, which seems like it can accomplish something like what you're trying to do. Here's a write up for that:
http://blog.springsource.com/2011/02/14/spring-3-1-m1-introducing-profile/
As a workarround, you can set the desired Spring service implementation based on its name in your config.properties.
#Controller
public class MyController {
//add a String which will hold the name of the service to implement
#Value("${service.class}")
private String serviceToImplement;
Service service;
// now autowire spring service bean based on int name using setter
#Autowired
public void setService(ApplicationContext context) {
service = (Service) context.getBean(serviceToImplement);
}
}
#Service
#Qualifier("ServiceA")
ServiceA implements Service {
public void print() {
System.out.println("printing ServiceA.print()");
}
}
#Service
#Qualifier("ServiceB")
ServiceB implements Service {
public void print() {
System.out.println("printing ServiceB.print()");
}
}
config.properties
service.class=serviceB
Maybe give this a whirl:
#Controller
public class MyController {
private String serviceId;
#Value("${serviceId}")
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
#Autowired
#Qualifier(serviceId)
Service service;
}

Categories

Resources