How to create a spring object mid-method? - java

In the old pre spring version I had something like this:
public String jobRun(int projectID)
{
JobHelper jobHelper = new JobHelper(projectID);
jobHelper.this();
jobHelper.that();
}
How can I create the JobHelper object using spring? I made it a spring component, but it can't be global.
I guess I want something like this:
public String jobRun(int projectID)
{
#Autowired
#Scope("prototype")
JobHelper jobHelper;
jobHelper.this();
jobHelper.that();
}
However, that's not how spring works.
Thanks,

For that to work, you need to let Spring now that JobHelper is "Autowireable". It'd be like this for the class where you need JobHelper to do things:
public class YourClass{
#Autowired
JobHelper jobHelper;
public String jobRun(int projectID){
jobHelper.this();
jobHelper.that();
return "something";
}
}
And JobHelper itself would be like
#Component
#Scope("prototype")
public class JobHelper {
public void this(){}
public void that(){}
}
Injection is a broad topic, so I'd suggest you read guides/articles such like this on #Autowired or this on #Component from Baeldung for a start.

Related

Spring Boot application properties #Value

I'm trying to create a simple Spring Boot Application, creating a GET using resttemplate.getForObject and instead of hard coding the url within the parameter, I want to create a string and leverage #Value to call the url from application.properties. I've checked videos and the board (#Value in Springboot returns null) and every time I run my JUnit test the endpoint is null. I was wondering if someone can take a look at my code and point out what am I doing incorrectly, why is #Value always null and what can I do to fix this?
#SpringBootApplication
public class NhlTeamsApplication {
public static void main(String[] args) {
SpringApplication.run(NhlTeamsApplication.class, args);
}
}
//my controller
public class NhlTeamsController {
#Autowired
private NhlTeamsService nhlTeamsService;
#RequestMapping(method = RequestMethod.GET, value = "/requestAllTeams")
public #ResponseBody String requestAllTeams() {
return nhlTeamsService.allTeamsService();
}
}
//my interface
#Component
public interface NhlTeamsService {
String allTeamsService();
}
//implementation
#Service
#Data
#Configuration
#PropertySource("classpath:application.properties")
public class NhlTeamsServiceImpl implements NhlTeamsService{
#Value("${nhl.endpoint}")
private String endpoint;
#Override
public String allTeamsService() {
RestTemplate restTemplate = new RestTemplate();
return restTemplate.getForObject(getEndpoint(), String.class);
}
}
//application.properties
nhl.endpoint=https://statsapi.web.nhl.com/api/v1/teams
//JUnitTest
#Test
public void nhlAllTeamsTest(){
NhlTeamsServiceImpl nhlTeamsService = new NhlTeamsServiceImpl();
System.out.println(nhlTeamsService.allTeamsService());
}
Am I missing any annotations? Any insight is greatly appreciated.
When you running tests that time you create a new object of NhlTeamsServiceImpl.
But in spring have spring container inside that at run time spring had to initialize that object with all variable & stored.
So when you create a new object it will initialize of spring container & in that it doesn't have to initialize all required thing as #value variable
so do one thing in test case class modified as follows,
class junit{
#Autowired
public NhlTeamsServiceImpl nhlTeamsServiceImpl;
//JUnitTest
#Test
public void nhlAllTeamsTest(){
//NhlTeamsServiceImpl nhlTeamsService = new NhlTeamsServiceImpl();
System.out.println(nhlTeamsService.allTeamsService());
}
}
So it will work.
Find below the working snap.
& same working code add here https://github.com/MaheshMore4321/RunTestCases

#ConditionalOnExpression enabling/disabling #RestController

I am trying to enable/disable controller depending on value in properties file.
My Controller looks like this:
#RestController
#ConditionalOnExpression("${properties.enabled}")
public class Controller{
public String getSomething() {
return "Something";
}
}
My properties file looks like this:
properties.enabled= false
And controller is always enabled (I can access method getSomething). I also tried combinations like this:
#ConditionalOnExpression("${properties.enabled:true}")
#ConditionalOnExpression("${properties.enabled}==true")
#ConditionalOnExpression("${properties.enabled}=='true'")
#ConditionalOnExpression("'${properties.enabled}'=='true'")
Edit:
Also tried different annotation:
#ConditionalOnProperty(prefix = "properties", name="enabled")
I finally found the problem. This Bean wasn't created by Spring but in WebConfiguration class so i had to also add annotation there
public class CommonWebConfiguration extends WebMvcConfigurationSupport {
#Bean
#ConditionalOnProperty(prefix="properties",name = "enabled")
public Controller controller() {
return new Controller ();
}
}
My Controller now looks like this:
#RestController
#ConditionalOnProperty(prefix="properties",name = "enabled")
public class Controller{
public String getSomething() {
return "Something";
}
}

How to extend #Service?

Here is a sample class below:
#Service("testService")
public class TestService {
public String something() {
return "abc";
}
}
I want to extend the class and let the container know that it needs to pick up my extended class from now.
#Service("extendedTestService")
public class ExtendedTestServiceMock extends TestService {
#Override
public String something() {
return "xyz";
}
}
Test class:
public class TestClass extends SpringTest {
#Autowired
#Qualifier("extendedTestService")
private ExtendedTestService testService;
public void testMethod() {
......
}
}
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [TestService] is defined: expected single matching bean but found 2: ExtendedTestServiceMock,testService
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:865) ~[spring-beans-3.2.8.RELEASE.jar:3.2.8.RELEASE]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:770) ~[spring-beans-3.2.8.RELEASE.jar:3.2.8.RELEASE]
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:489) ~[spring-beans-3.2.8.RELEASE.jar:3.2.8.RELEASE]
... 91 common frames omitted
How to resolve it?
Try using interfaces.
public interface TestService {
String something();
}
Implementations:
#Service
#Qualifier("testService")
public class TestServiceImpl implements TestService { ... }
#Service
#Qualifier("testServiceMock")
public class TestServiceMockImpl implements TestService { ... }
And the test class:
public class TestClass extends SpringTest {
#Autowired
#Qualifier("extendedTestService")
private TestService testService;
...
}
One solution that would work in your case is the #Primary annotation.
Your TestServiceMockImpl would look like:
#Service("extendedTestService ")
#Primary
public class ExtendedTestServiceMock extends TestService {
#override
public String something() {
return "xyz";
}
}
Check out this for more details on #Primary
I however suggest that you don't follow the above solution (since this will get out of hand very quick if you start using #Primary everywhere), that you instead take a look at Spring Profiles
There are a lot of way you could create your Spring configuration using profiles, but regardless of how you end up configuring the beans, the end result would be a more clean design.
If you have an identifier to help you decide which service to initialize, then you can use ConditionlOnProperty annotation
Ex:
#Service
#ConditionlOnProperty(value = "test.service.extension.enabled")
public class TestService {
}
#Service
#ConditionlOnProperty(value = "test.service.extension.enabled", havingValue = "false")
public class ExtendedTestServiceMock extends TestService {
}
If you want to use the extended test service, you can set the property test.service.extension.enabled=true in your application.properties
It depends on your definition order if your service define on the xml file.
Otherwise, you could use a BeanFactoryPostProcessor to do this, which is only registered in the test scenarios that you want this mocked.
public class SystemTestBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
// put your custom code in here
}
}
Or you can use #DependsOn to make sure the parent bean should be deploy firstly then your extend bean
#Service("testService")
#DependsOn("testService")
public class ExtendedTestService extends TestService {
}
Hope this helps.

Spring MVC multiple controllers of the same class

I want to have one controller class, but 4 instances of it, each of instance will have own datasource and controller path, everything else (methods, validations rules, views names) will be the same;
So i need something like this :
class MyController{
private MyService service;
#RequestMapping("somework")
public String handleRequest(){
........
}
....................
}
Configuration class :
#Configuration
#EnableWebMvc
public class AppConfiguration {
#Controller // assuming it exists to get the
#RequestMapping('con1') // desired result
MyController controller1(){
MyController con = new MyController();
con.setService(service1Bean);
return con;
}
#Controller // assuming it exists to get the
#RequestMapping('con2') // desired result
MyController controller2(){
MyController con = new MyController();
con.setService(service2Bean);
return con;
}
...............................
}
No, you can't do this.
First, annotations are a set in stone at compile time. They are constant meta data that you cannot modify. So even though, they are accessible at run time through reflection, you cannot modify them.
Second, the #Controller annotation call only be used to annotate types. You cannot use it on a method. There is no corresponding annotation in Spring MVC that does what you want in your example. (You could always write your own.)
Finally, the Spring MVC stack registers your #Controller beans' methods as handlers mapping them to the various URL patterns you provide. If it tries to register a pattern that has already been registered, it fails because duplicate mappings are not allowed.
Consider refactoring. Create a #Controller class for each path you want but move the logic to a #Service bean which you can customize to use whatever data source you need.
You may achieve what you want by implementing an abstract superclass of
your controller, with constructor parameters for your service.
Then you should write derive your controllers from the abstract superclass,
with a constructor, where you inject your concrete service implementation:
public abstract class MyBaseController {
private MyService service;
public MyBaseController(final MyService service) {
this.service = service;
}
...
#RequestMapping("method1")
public ... method1( ... ) {
...
}
}
#Controller
#RequestMapping("con1")
public MyController1 extends MyBaseController {
#Autowired
public MyController1(#Qualifier("con1") final MyService service) {
super(service);
}
}
#Controller
#RequestMapping("con2")
public MyController2 extends MyBaseController {
#Autowired
public MyController1(#Qualifier("con2") final MyService service) {
super(service);
}
}
#Configuration
public class MyConfiguration {
#Bean(name = "con1")
public MyService serviceCon1() {
return ...;
}
#Bean(name = "con2")
public MyService serviceCon2() {
return ...;
}
}

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