I have a legacy application which uses Spring for bean management (AnnotationConfigWebApplicationContext) and CDI (inject, brought by jersey-spring dependency) for DI.
I have the following situation:
#Service
#RequestScoped
#Scope(value = "request")
public class InjectedClass {
private SomeEnum someAttribute;
public void getSomeAttribute() {
return someAttribute;
}
}
#Service
public class MiddleLayer {
#Inject
public MiddleLayer(InjectedClass injectedClass) {
}
private void middleLayerMethod() {
if (someAttribute == SomeEnum.Y) {
// do something specific
}
}
}
// controller
public class SomeController {
#Inject
// In this flow injectedClass instance is initialised with SomeAttribute = Y by ContainerRequestFilter
public SomeController(MiddleLayer middleLayer) {
}
public void someMethod () {
MethodResult result = middleLayer.middleLayerMethod();
// do some additional things with result
}
}
#Component
public class PeriodicActivity {
#Inject
// I need this MiddleLayer to be injected with injectedClass instance where SomeAttribute = X, since it doesn't go via request filter
public PeriodicActivity(MiddleLayer middleLayer) {
}
public void method () {
MethodResult result = middleLayer.middleLayerMethod();
// do some other things with result
}
}
Without DI what I need to happen would look like this:
public class PeriodicActivity() {
InjectedClass injectedClassObjA = new InjectedClass();
injectedClassObjA.setSomeAttribute(X);
MiddleLayer middleLayer = new MiddleLayer(injectedClassObjA);
middleLayer.middleLayerMethod();
}
I am looking to do something similar with dependencies.
After some reading I am starting to wonder whether it is possible.
Related
I try to implement chain of responsibility with quarkus 2.10.0.
I have class IssueChangeChain and no one IssueChangeChainLink doesn't inject in field links.
#ApplicationScoped
public class IssueChangeChain {
#Inject
#All
List<IssueChangeChainLink> links;
public void processIssueChange(JiraChangeDTO change) {
logger.info("Try to process " + change + " through " + links);
if(change == null)
return;
links.forEach(link ->{
var changeItem = link.getChangeItem(change);
if (changeItem != null) {
link.processChangeItem(changeItem);
}
});
}
void setLinks(List<IssueChangeChainLink> links) {
this.links = links;
}
}
And three classes implemented IssueChangeChainLink
#ApplicationScoped
public class IssueCreationChainLink implements IssueChangeChainLink<AddJiraIssueDTO> {
#Override
public AddJiraIssueDTO getChangeItem(JiraChangeDTO change) {
...
}
#Override
public void processChangeItem(AddJiraIssueDTO changeItem) {
...
}
private static final Logger log = LoggerFactory.getLogger(IssueCreationChainLink.class);
}
#ApplicationScoped
public class SprintChangeChainLink implements IssueChangeChainLink<IssueAddSprintDTO> {
#Override
public IssueAddSprintDTO getChangeItem(JiraChangeDTO change) {
...
}
#Override
public void processChangeItem(IssueAddSprintDTO changeItem) {
...
}
private static final Logger log = LoggerFactory.getLogger(SprintChangeChainLink.class);
}
What should I do to inject List of beans?
#Inject
List<?> instances;
does not work in Quarkus as this is not supported in CDI. In CDI it is looking for a bean which is a List.
What you should do is inject
#Inject
#All
Instance<IssueChangeChainLink> instances;
Instance from CDI implements Iterable, which you can then use .stream() or .forEach() from to iterate over beans.
I have following spring bean with Prototype scope. In the AppRunner class, I want a new bean to injected by spring within the for loop (if loop count is 2, then i want only 2 new beans to be injected).
But spring injects a new bean every time the setter methods of the SimpleBean is called.
SimpleBean.java
#Component
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode =
ScopedProxyMode.TARGET_CLASS)
public class SimpleBean {
private String id;
private Long value;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Long getValue() {
return value;
}
public void setValue(Long value) {
this.value = value;
}
}
AppRunner.java
#Component
public class AppRunner {
#Autowired
SimpleBean simpleBean;
public void execute(List<Output> results){
List<SimpleBean> finalResults = new ArrayList<SimpleBean>();
for(Output o : results){
simpleBean.setId(o.getAppId());
simpleBean.setValue(o.getAppVal());
finalResults.add(simpleBean);
}
}
}
Output.java
public class Output {
private String appId;
private Long appVal;
public String getAppId() {
return appId;
}
public void setAppId(String appId) {
this.appId = appId;
}
public Long getAppVal() {
return appVal;
}
public void setAppVal(Long appVal) {
this.appVal = appVal;
}
}
Unfortunately prototype scope doesn't work like this. When your AppRunner bean is instantiated by the container it is asking for its dependencies. Then a new instance of SimpleBean is created. This instance stays as dependency. Prototype scope starts working when you will have multiple beans with dependency on SimpleBean. Like:
#Component
class BeanOne {
#Autowired
SimpleBean bean; //will have its own instance
}
#Component
class BeanTwo {
#Autowired
SimpleBean bean; //another instance
}
There is one rather straightforward update which can lead to your desired behaviour. You can remove autowired dependency and ask for a new dependency in your loop from context. It would look like this.
#Component
public class AppRunner {
#Autowired
ApplicationContext context;
public void execute(List<Output> results){
List<SimpleBean> finalResults = new ArrayList<SimpleBean>();
for(Output o : results) {
SimpleBean simpleBean = context.getBean(SimpleBean.class);
simpleBean.setId(o.getAppId());
simpleBean.setValue(o.getAppVal());
finalResults.add(simpleBean);
}
}
}
Other option could be technique called Method injection. It is described in the relevant documentation for prototype scope. You can take a look here 7.5.3 Singleton beans with prototype-bean dependencies
CDI has the feature of Specialization, and I'm looking for that in the Spring world.
Details.
In CDI, the #Specializes annotation allows one to change the behaviour of a bean just by overriding it. This is completely transparent to users of that bean, e.g. if we'd have
public class OneBean {
public String whoAmI() { return "OneBean"; }
}
#Specializes
public class AnotherBean extends OneBean {
#Override
public String whoAmI() { return "AnotherBean"; }
}
we could
public class SomewhereElse {
#Inject
OneBean oneBean; // we know nothing of AnotherBean here!
public void guessWhosThere() {
return oneBean.whoAmI(); // yet it returns "AnotherBean"
}
}
This gets really useful as soon as OneBean is actually used with and without AnotherBean. For example, if OneBean is in one.jar and AnotherBean is in another.jar, we can change the bean's behaviour just by reconfiguring the classpath.
Question. Does something like Specialization also exist in Spring?
I could only find the #Primary annotation, which however has a different semantics: #Primary does not replace one bean, but only marks one of multiple alternatives as the primary one. Especially, as I understood, I could not build a deep inheritance hierarchy as it's possible with #Specializes.
Short answer
In Spring 4, this is not possible. Period. Still, in 2016, nothing like this is possible with Spring's obsolete dependency injection model.
Seems like there is no similar annotation in spring, but you can achive it via #Qualifier.
Beans:
#Resource("oneBean")
public class OneBean {
public String whoAmI() { return "OneBean"; }
}
#Resource("anotherBean")
public class AnotherBean extends OneBean {
#Override
public String whoAmI() { return "AnotherBean"; }
}
SomewhereElse:
public class SomewhereElse {
#Autowired
#Qualifier("anotherBean")
OneBean oneBean;
public void guessWhosThere() {
return oneBean.whoAmI(); // returns "AnotherBean"
}
}
Edited.
Also you can develop your own annotation and use it in BeanPostProcessor, look at spring docs here
OR even better to use CustomAutowireConfigurer, see here
With Spring boot, you could probably get a similar result by leveraging its auto-configure mechanism, e.g. with a bean condition such as #ConditionalOnMissingBean:
public class OneBean {
public String whoAmI() { return "OneBean"; }
}
#Configuration
public class OneConfiguration {
#Bean
#ConditionalOnMissingBean
public OneBean getBean() { return new OneBean(); }
}
#Component
public class AnotherBean extends OneBean {
#Override
public String whoAmI() { return "AnotherBean"; }
}
However, you would have to make sure that all configurations are built accordingly if you don't know for sure which ones will be specialized:
public class OneBean {
public String whoAmI() { return "OneBean"; }
}
public class AnotherBean extends OneBean {
#Override
public String whoAmI() { return "AnotherBean"; }
}
public class YetAnotherBean extends AnotherBean {
#Override
public String whoAmI() { return "YetAnotherBean"; }
}
#Configuration
public class OneConfiguration {
#Bean
#ConditionalOnMissingBean
public OneBean getBean() { return new OneBean(); }
}
#Configuration
public class AnotherConfiguration {
#Bean
#ConditionalOnMissingBean
public AnotherBean getBean() { return new AnotherBean(); }
}
#Configuration
public class YetAnotherConfiguration {
#Bean
#ConditionalOnMissingBean
public YetAnotherBean getBean() { return new YetAnotherBean(); }
}
// and so on...
It seems that the setter on my bean is not working.
This is my Spring java configuration, SpringConfig.java:
#Configuration
#ComponentScan("com.xxxx.xxxxx")
public class SpringConfig {
#Bean(name="VCWebserviceClient")
public VCWebserviceClient VCWebserviceClient() {
VCWebserviceClient vCWebserviceClient = new VCWebserviceClient();
vCWebserviceClient.setSoapServerUrl("http://localhost:8080/webservice/soap/schedule");
return vCWebserviceClient;
}
The VCWebserviceClient.java:
#Component
public class VCWebserviceClient implements VCRemoteInterface {
private String soapServerUrl;
public String getSoapServerUrl() {
return soapServerUrl;
}
public void setSoapServerUrl(String soapServerUrl) {
this.soapServerUrl = soapServerUrl;
}
// Implemented methods...
}
My app.java:
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
VCWebserviceClient obj = (VCWebserviceClient) context.getBean("VCWebserviceClient");
System.out.println("String: "+obj.getSoapServerUrl()); // returns NULL
Why is obj.getSoapServerUrl() returning NULL?
This example shows how it should work.
The instance returned by VCWebserviceClient is not the one actually used by your application. It is a way for Spring to know what class to instanciate.
Any way, for you issue, use the #Value (http://docs.spring.io/spring/docs/3.0.x/reference/expressions.html)
#Component
public class VCWebserviceClient implements VCRemoteInterface {
// spring resolves the property and inject the result
#Value("'http://localhost:8080/webservice/soap/schedule'")
private String soapServerUrl;
// spring automatically finds the implementation and injects it
#Autowired
private MyBusinessBean myBean;
public String getSoapServerUrl() {
return soapServerUrl;
}
public void setSoapServerUrl(String soapServerUrl) {
this.soapServerUrl = soapServerUrl;
}
// Implemented methods...
}
While overrding a Javaconfig Bean by extending the original #Configuration class, I would like to add a #DependsOn for the new Bean definition.
However, this depends-on seems not to be taken in account. here is a TestCase reproducing my issues:
public class SpringTest {
#Test
public void testDependsOnTakenInAccount() {
AnnotationConfigApplicationContext ctx2 = new AnnotationConfigApplicationContext(AConfig.class, CConfig.class);
Assert.assertEquals("overriden", ctx2.getBean("bean"));
}
#Configuration
public static class AConfig {
#Bean
public Object bean() {
return "not overriden";
}
}
#Configuration
public static class CConfig extends AConfig {
protected boolean isInitialized = false;
#Bean
public Void doInit() {
isInitialized = true;
return null;
}
#Bean
#DependsOn("doInit")
public Object bean() {
if (!isInitialized) {
throw new RuntimeException("Not initialized");
}
return "overriden";
}
}
}
Is this an expected behavior? If yes, how can I add dependency while overriding a bean?
For me seems like a bug.
When overriding a #Bean factory method in a Configuration class, the parent BeanDefinition wins and get registered on the BeanFactory overriding the child one.
So you cannot configure the bean with annotaions (because it will be overriden).
The following Test result on
expected:<[doInit]> but was:<[otherBean]>
#RunWith(JUnit4ClassRunner.class)
public class DependOnTest {
#Test
public void testBeanDefinitionOverriding() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(Config.class);
BeanDefinition bd = ctx.getBeanDefinition("bean");
Assert.assertEquals("doInit", bd.getDependsOn()[0]);
}
#Configuration
public static class ParentConfig {
#Bean
#DependsOn("otherBean")
public String bean() {
return "not overriden";
}
#Bean
public String otherBean() {
return "otherBean";
}
}
#Configuration
public static class Config extends ParentConfig {
#Bean
public String doInit() {
return "doInit";
}
#Bean
#DependsOn("doInit")
public String bean() {
return "overriding";
}
}
}
I think that problem start on ConfigurationClassParser:
// recursively process the configuration class and its superclass hierarchy
do {
metadata = doProcessConfigurationClass(configClass, metadata);
}
while (metadata != null);
That result on overriden method added to CongurationClass.beanMethods
It could be fixed checking if the beanMethod was already added from a superclass in ConfigurationClass.addBeanMethod()
public void addBeanMethod(BeanMethod method) {
// Check if already added a bean method from superclass
for (BeanMethod beanMethod : beanMethods) {
if (beanMethod.getMetadata().getMethodName().equals(method.getMetadata().getMethodName()) &&
!(beanMethod.getMetadata().getDeclaringClassName()).equals(method.getMetadata().getDeclaringClassName()))
// log and return.
return;
}
this.beanMethods.add(method);
}
As pointed out by Jose Luis Martin, this has been confirmed as a bug by Spring team.
I've workarounded it with:
#DependsOn("doInit")
#Bean
public Void notOverridingBean() {
return null;
}
#Bean
public Object bean(Object notOverridingBean) {
return "overriden";
}
an alternative is to override the bean in another #Configuration class.