I have a plain POJO being autowired in Spring whose properties appear to persist.
I find that the happy path is OK - Set bean properties and return then, however when I'm not on the happy path and I no longer wish to set a property (in this case responseCode), I find it is still set to the previous value (when a call was successful).
I would like this value to not be set and be equal to what I have currently specified in the model.
I have the following POJO Prototype bean
package com.uk.jacob.model;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
#Component
#Scope("prototype")
public class Website {
public String url;
public boolean response;
public int responseCode = 0;
}
I am setting it's information within a service class
package com.uk.jacob.service;
import java.net.HttpURLConnection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.uk.jacob.adapter.HttpAdapter;
import com.uk.jacob.model.Website;
#Component
public class PingerService {
#Autowired
HttpAdapter httpAdapter;
#Autowired
Website website;
public Website ping(String urlToPing) {
website.url = urlToPing;
try {
HttpURLConnection connection = httpAdapter.createHttpURLConnection(urlToPing);
website.response = true;
website.responseCode = connection.getResponseCode();
} catch (Exception e) {
website.response = false;
}
return website;
}
}
Which is called from a RestController
package com.uk.jacob.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.uk.jacob.model.Website;
import com.uk.jacob.service.PingerService;
#RestController
#RequestMapping("api/v1/")
public class PingController {
#Autowired
PingerService pingerService;
#RequestMapping(value = "ping", method = RequestMethod.GET)
public Website getPing(#RequestParam String url){
return pingerService.ping(url);
}
}
The fact that your Website bean is #Scope("prototype") means that every time that it gets injected as a dependency in another bean upon this bean's creation, a new instance gets created and injected. In this case PingerService gets a new instance of Website. If say Website is also injected on another bean called Website2 then a different (new) instance gets injected.
If your anticipation is Website to be new upon each invocation within Website then this cannot be done simply with the prototype annotation. You'll need to expose the context and invoke ApplicationContext#getBean("website").
For your use case, I understand that you need a fresh instance of Website bean for every request.
Use #Scope("request").
Prototype scope on the other hand means it will be creating a separate instance for every Autowiring of Website it sees everywhere. For example, PingerService will have its own Website bean and won't be shared on other classes with the same Autowiring but its values will persist across http requests on PingerService.
Related
I already try to search through stackoverflow, and I don't think I find the solution I want...
Also I try to use answer https://stackoverflow.com/questions/45970442/spring-boot-value-returning-null and still doesn't work...
Here is my controller class
package com.vincent.springoauth.controller;
import com.vincent.springoauth.model.GiftCardRequest;
import com.vincent.springoauth.model.GiftCardResponse;
import com.vincent.springoauth.service.InCommGiftCardServiceImpl;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("/api/gift-card")
public class GiftCardController{
#PostMapping("/activate")
public #ResponseBody
GiftCardResponse activate(GiftCardRequest request) {
GiftCardServiceImpl giftCardService = new GiftCardServiceImpl("");
return giftCardService.activate(request);
}
}
And here is my service class
package com.vincent.springoauth.service;
import com.vincent.springoauth.model.GiftCardRequest;
import com.vincent.springoauth.model.GiftCardResponse;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
#Service
#Log4j2
public class GiftCardServiceImpl {
private final String baseEndpoint;
public GiftCardServiceImpl( #Value("${webserviceurl}")String baseEndpoint){
this.baseEndpoint = baseEndpoint;
}
public String accessToken() {
log.info("Access oauth token url address: " + baseEndpoint);
// will be use that base endpoint to manipulate stuff later
return "abcdefg";
}
public GiftCardResponse activate(GiftCardRequest request) {
log.info("Calling token ...");
accessToken();
log.info("Incomm Pre Auth Service");
// Generate preAuth request;
//RetailTransactionGenericRequestWrapper retailTransactionGenericRequest = buildRequest(request);
//log.info("RetailTransactionGenericRequest: " + retailTransactionGenericRequest);
GiftCardResponse response = GiftCardResponse.builder().responseCode("0").responseMessage("Success").build();
return response;
}
}
And in my application.properties I have following line webserviceurl=https://localhost/giftcard
The issue that in my service class the webserviceurl return null. How can I fix this?
In your controller you are creating your own instance of the service and so Spring is unaware of it and cannot inject the value. Your service class is annotated as a Service so Spring will create an instance, which will have the value injected but that is not the instance that your controller is using.
Instead you should declare that service as an instance variable in your controller and use either Autowire annotation on that instance variable or use constructor autowiring to ensure that the bean created by Spring is the one that is used by your controller.
#RestController
#RequestMapping("/api/gift-card")
public class GiftCardController{
private GiftCardServiceImpl giftCardService;
#Autowired
public GiftCardController(GiftCardServiceImpl giftCardService) {
this.giftCardService = giftCardService;
}
#Value annotation uses for injecting values into fields in Spring-managed beans. In your example, you create GiftCardServiceImpl on your own and Spring cannot control the creation and inject the webserviceurl value from application.properties. You can change GiftCardController to allow Spring to do this.
package com.vincent.springoauth.controller;
import com.vincent.springoauth.model.GiftCardRequest;
import com.vincent.springoauth.model.GiftCardResponse;
import com.vincent.springoauth.service.InCommGiftCardServiceImpl;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("/api/gift-card")
public class GiftCardController{
private final GiftCardServiceImpl giftCardService;
public GiftCardController(GiftCardServiceImpl giftCardService) {
this.giftCardService = giftCardService;
}
#PostMapping("/activate")
public #ResponseBody
GiftCardResponse activate(GiftCardRequest request) {
return giftCardService.activate(request);
}
}
Try to inject GiftCardServiceImpl via Spring DI like in the example below
package com.vincent.springoauth.controller;
import com.vincent.springoauth.model.GiftCardRequest;
import com.vincent.springoauth.model.GiftCardResponse;
import com.vincent.springoauth.service.InCommGiftCardServiceImpl;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("/api/gift-card")
public class GiftCardController{
#Autowired
private GiftCardServiceImpl giftCardService;
#PostMapping("/activate")
public #ResponseBody
GiftCardResponse activate(GiftCardRequest request) {
return giftCardService.activate(request);
}
}
The #Value annotation only works for Spring Beans, when you create the class via simple new keyword. Spring doesn't catch that should inject property in the constructor.
We just created a custom AmazonS3Client with credentials on a project that was already using Amazon S3 functionality:
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
#Configuration
public class S3Config {
#Bean
public static AmazonS3Client amazonS3Client(final AWSCredentialsProvider awsCredentialsProvider) {
return (AmazonS3Client) AmazonS3ClientBuilder.standard()
.withCredentials(awsCredentialsProvider)
.build();
}
}
It has worked just fine on all other project, but for some reason, when starting up the application, we get this error:
Parameter 0 of constructor in foo.bar.MyService required a single bean, but 2 were found:
- amazonS3Client: defined by method 'amazonS3Client' in class path resource [foo/bar/S3Config.class]
- amazonS3: defined in null
Nowhere, absolutely nowhere on the project we have an amazonS3 Bean defined.
So, what are the contents of this Service class? Well, nothing special:
import com.amazonaws.services.s3.AmazonS3Client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.ByteArrayInputStream;
import java.net.URL;
#Service
public class MyService {
private final AmazonS3Client s3Client;
#Autowired
public MyService(AmazonS3Client s3Client) {
this.s3Client = s3Client;
}
...
}
It is supposed to use the AmazonS3Client we just created, and according to the first match of the error message it matched it just fine. If I delete my S3Config class, the bean duplication error is gone.
We don't want to force the project to use our AmazonS3Client implementation by adding the #Primary annotation.
So, what could we be doing wrong?
After some hours of debugging, we realized the Service's constructor's parameter name was not exactly named as the Bean. We renamed it so that it matched the Bean's name:
#Service
public class MyService {
private final AmazonS3Client s3Client; //Just fine
#Autowired
public MyService(AmazonS3Client amazonS3Client) { // Must match the bean name
this.s3Client = amazonS3Client;
}
...
}
And the Bean duplication error was gone. All we have to do is name the constructor's parameter just like the bean.
I'd like to be able to instantiate a request-scoped bean which is also immutable by using constructor parameters.
Something like the following (which of course doesn't work):
RequestContextFactory.java
package org.springframework.samples.mvc.requestscope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.annotation.RequestScope;
#Configuration
public class RequestContextFactory {
#Bean
#RequestScope
public RequestContext getRequestContext(TestBean bean) {
return new RequestContext(bean);
}
}
MyController.java
package org.springframework.samples.mvc.requestscope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class MyController {
#Autowired
RequestContextFactory requestContextFactory;
#RequestMapping("/my")
public #ResponseBody String simple(TestBean bean) {
RequestContext requestContext = requestContextFactory.getRequestContext(bean);
return "Hello world!";
}
}
Spring complains that it cannot autowire a TestBean bean to create RequestContext.
How can I achieve immutability of a request-scoped bean which needs constructor parameters only known in the controller?
I'd like to be able to inject RequestContext into other beans (either request scope or other scopes). Is this an antipattern? Should something like RequestContext (or any other object with request lifecycle) be in the signature of all call hierarchy under the controller?
Note:
I thought of as a solution like for example having a RequestContext with default constructor and an init(...) method which can be called only once (would throw the second time). I don't like it.
When i try to use #Inject to inject my DAO class, in a manager to make it able to give a gson file to a current address i get this exception when i go to the specific web address.Can someone explain me what is the problem. I thought that the problem come maybe from the #Inject and it does not work correctly, but i am not sure.
java.lang.RuntimeException: org.apache.cxf.interceptor.Fault: Cannot obtain a free instance.; nested exception is:
javax.enterprise.inject.UnsatisfiedResolutionException: Api type [cinema.dao.ProjectionDAO] is not found with the qualifiers
Qualifiers: [#javax.enterprise.inject.Default()]
for injection into Field Injection Point, field name : projectionDAO, Bean Owner : [ProjectionManager, Name:null, WebBeans Type:DEPENDENT, API Types:[cinema.services.ProjectionManager,java.lang.Object], Qualifiers:[javax.enterprise.inject.Any,javax.enterprise.inject.Default]] while invoking public java.util.Collection cinema.services.ProjectionManager.getAllProjections() with params [].
org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:116)
org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:324)
org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:121)
org.apache.cxf.transport.http.AbstractHTTPDestination.invoke(AbstractHTTPDestination.java:240)
org.apache.openejb.server.cxf.rs.CxfRsHttpListener.onMessage(CxfRsHttpListener.java:187)
org.apache.openejb.server.rest.RsServlet.service(RsServlet.java:53)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
Code for the manager:
package cinema.services;
import java.util.Collection;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import cinema.dao.ProjectionDAO;
import cinema.model.Projection;
#Stateless
#Path("projection")
public class ProjectionManager {
#Inject
private ProjectionDAO projectionDAO;
#GET
#Produces("application/json")
public Collection<Projection> getAllProjections(){
return projectionDAO.getAllProjections();
}
}
Here is the ProjectionDAO:
package cinema.dao;
import java.util.Collection;
import javax.inject.Singleton;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import cinema.model.Projection;
import cinema.model.User;
#Singleton
public class ProjectionDAO {
#PersistenceContext
private EntityManager em;
public Collection<Projection> getAllProjections(){
return em.createNamedQuery("getAllProjections", Projection.class).getResultList();
}
public void addProjection(Projection projection){
em.persist(projection);
}
public Projection findProjectionByMovieTitle(String movieTitle){
try {
return em.createNamedQuery("getProjectionByMovieTitle", Projection.class)
.setParameter("movieTitle", movieTitle).getSingleResult();
} catch (NoResultException e){
return null;
}
}
public void buyTicket(Projection projection, User user){
Projection foundProjection = findProjectionByMovieTitle(projection.getMovieTitle());
if(foundProjection != null){
user.getCurrentProjections().add(projection);
int newFreeSpaces = foundProjection.getFreeSpaces() - 1;
foundProjection.setFreeSpaces(newFreeSpaces);
}
}
}
Projection is a simple model which give the movieTitle and start time of different projections in a cinema.
Your question doesn't state important information such as versions in use or how you're deploying, so I'm going to take a wild stab.
You're not including a beans.xml file in your deployment. I'm not sure though if you're deploying a WAR or a JAR file.
Assuming you have a properly placed beans.xml file, try swapping the #Singleton with an #ApplicationScope. This should more correctly discover your class.
Ive Added Spring annotation's to my code
but when connecting via visual vm the method "myExample()" isn't showing in the JMX bean list
My code :
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jmx.export.annotation.ManagedAttribute;
import org.springframework.jmx.export.annotation.ManagedResource;
import org.springframework.stereotype.Component;
#Component
#ManagedResource
public class MyClass {
#Autowired
private Example exampleService;
#ManagedAttribute
public String myExample() {
return exampleService.getSomething().toString();
}
}
any idea why this is happening ?
You should use #ManagedOperation instead. #ManagedAttribute is for a getter / setter methods only.