Access Service layer property in Controller - java

I'm trying to learn SpEL to inject a property in controller which is inside service layer. The output is however always null.
This is my controller class.
#RestController
#Validated
public class RestApp1 implements CONSTANTS, ApplicationContextAware {
#Value("dummy String 3")
String value3;
#Value("#{#dummyServiceLayer.dbProperty}")
String value4;
#Autowired
DummyServiceLayer dummyLayer;
public ApplicationContext context;
#Autowired
RestTemplate restTemplate;
#GetMapping("/dummyGet")
public ResponseEntity<?> getMethodDummy() throws Exception {
System.out.println("value 3 is : " + value3);
System.out.println("value 4 is : " + dummyLayer.dbProperty);
dummyLayer.doingDBstuff();
return new ResponseEntity("Api Completed", HttpStatus.OK);
}
}
This is service layer class
#Service
public class DummyServiceLayer {
#Autowired
JdbcTemplate jdbcTemplate;
#Autowired
NamedParameterJdbcTemplate namedJdbcTemplateMaster;
#Value("#{dummyLayer2.getDbProperty('dummyString')}")
public String dbProperty;
#Value("some value")
public String dbProperty1;
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Async("asyncTaskWorkerPool")
public void doingDBstuff() throws Exception {
System.out.println("object hash : "+this);
System.out.println("DB property fetched using spring expression library +"+dbProperty);
}
}
The output is like this:
value 3 is : dummy String 3
value 4 is : null
When i debugged the application it seems like when the service layer is called then some proxy object is substituted which contains the correct values.
Can anyone explain why value4 is always null in controller. Tried everything didn't help.

Related

JUnit with Spring Boot in n-tier Service Layer

I have a model
public class Student {
private String name;
// getter and setter
}
I have implemented a REST controller which generates a student object with random chars as name in it. I have a #RestController, bind with a #Service layer which serves student object and again a #Service layer which generates random strings. I want to test my application using JUnit by mocking my controller and services. The problem is I can mock my controller and service that serves student but stringGeneratorService service layer is not mocked.
My Controller
#RestController
public class StudentServer {
StudentService service;
#Autowired
public StudentServer(StudentService service) {
this.service = service;
}
#GetMapping("/generate-student")
public Student studentGenerator() {
return service.getRandomStudent();
}
}
My Service layer that serves student object
#Service("studentService")
public class StudentService {
StringGeneratorService stringService;
#Autowired
public StudentService(StringGeneratorService stringService) {
this.stringService = stringService;
}
public Student getRandomStudent() {
Student student = new Student();
student.setName(stringService.generateRandomAlphaString());
return student;
}
}
And my RandomStringGenertor Service
#Service("stringGeneratorService")
public class StringGeneratorService {
Random random = new Random();
public String generateRandomAlphaNumericString() {
// returns a randomly generated string
}
}
My JUnit test class as follows:
#RunWith(SpringRunner.class)
#WebMvcTest(StudentServer.class)
public class RestTest {
#Autowired
private MockMvc mockMvc;
#TestConfiguration
public static class TestConfig {
#Bean
public StudentService studentService(final StringGeneratorService stringGeneratorService){
return new StudentService(stringGeneratorService);
}
#Bean
public StringGeneratorService stringGeneratorService(){
return mock(StringGeneratorService.class);
}
}
#Autowired
private StudentService studentService;
#Autowired
public StringGeneratorService stringGeneratorService;
#Before
public void setUp() {
reset(stringGeneratorService);
}
#After
public void tearDown() {
verifyNoMoreInteractions(stringGeneratorService);
}
#Test
public void testGenerateStudent() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/generate-student"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(print())
.andExpect(MockMvcResultMatchers.jsonPath("$.name").isNotEmpty());
}
}
The result is Body = {"name":null}
Can anybody have any idea what am I doing wrong?
In your test configuration to define a mock for your StringGeneratorService: return mock(StringGeneratorService.class);, but you don't define a mocked behavior of a method. The default behavior of many method returning an Object is to return null, which you see in your result.
You need to define a mocked behavior of the method like this:
#Bean
public StringGeneratorService stringGeneratorService() {
StringGeneratorService mock = mock(StringGeneratorService.class);
// adding the missing mocked method behavior here
when(mock.generateRandomAlphaNumericString()).thenReturn("expectedMockedValue");
return mock;
}
As I see this is a unit test for your Rest Controller class.
I would suggest naming the Test Class as below as a good practice
Class Name:StudentServer.class
Junit Class Name: StudentServerTest.class
Now Coming to your real issue.
1] I see you have autowired your dependency class (StudentService.class). Instead you need to mock it as
#MockBean
private StudentService studentService;
2] You do not need StringGeneratorService bean/object in your test class because, its method is not directly called from your controller class.
Its method is instead called from your service class and since, you mock your service class itself, this class is not visible in your controller class at all.
Remove this
#Autowired
public StringGeneratorService stringGeneratorService;
3] Now define the mock behavior as below and then assert
#Test
public void testGenerateStudent() throws Exception {
Student student = new Student();
student.setName("TestStudent");
Mockito.when(studentService.getRandomStudent()).thenReturn(student);
mockMvc.perform(MockMvcRequestBuilders.get("/generate-student"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(print())
.andExpect(MockMvcResultMatchers.jsonPath("$.name").isNotEmpty());
}

#Autowire returns null after adding Spring AOP

After adding Spring AOP to my Spring Boot project, the following aspect produces a NullPointerException on an autowired service component in my controllers:
#Aspect
#Component
#Slf4j
public class LogRequestAspect {
#Around("#annotation(org.springframework.web.bind.annotation.RequestMapping) && execution(public * *(..))")
public Object log(final ProceedingJoinPoint joinPoint) throws Throwable {
final HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
.currentRequestAttributes())
.getRequest();
final Object proceed = joinPoint.proceed();
log.info(
"{} {} from {},{}",
request.getMethod(),
request.getRequestURI(),
request.getRemoteAddr(),
request.getHeader("X-Forwarded-For"));
return proceed;
}
}
Example controller:
#RestController
public class AController {
#Autowired
AService aService;
#RequestMapping("/doSomething")
private List<Map<String, Object>> doSomething() {
return aService.doSomething();
}
}
Example service:
#Service
public class AService {
public List<Map<String, Object>> doSomething() {
List<Map<String, Object>> results = new ArrayList<>();
return results;
}
}
Example configuration:
#EnableAspectJAutoProxy
#SpringBootApplication
public class Application implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... strings) {
}
}
As soon as i remove the aspect, everything works perfectly.
Any idea what i'm missing here?
Spring AOP, by default, works using proxies. IN this case a class based proxy is being used because no interface has been implemented. A class based proxy extends the actual class and overrides all the methods to apply the interceptors/aspects.
However a private method cannot be overriden in a subclass and as such your controller method will be invoked on the proxy instead of the proxied object. The proxy never has anything injected and hence the aService field is always null on there.
To fix make the method public or protected so that a subclass can override the method and eventually the method will be called on the proxied instance instead of the proxy.

spring boot integration testing mock method returns null

I wrote simple spring boot application with Controller, Service and Business classes, but while writing integration test the mock method of Service is returning null
MockMainController
#RestController
public class MockMainController {
#Autowired
private MockBusiness mockBusiness;
#GetMapping("request")
public MockOutput mockRequest() {
return mockBusiness.businessLogic(new MockInput());
}
}
MockBusiness
#Service
public class MockBusiness {
#Autowired
private MockService mockService;
public MockOutput businessLogic(MockInput input) {
return mockService.serviceLogic(input);
}
}
MockService
#Service
public class MockService {
#Autowired
private MockUtil mockUtil;
public MockOutput serviceLogic(MockInput input) {
mockUtil.exchange(UriComponentsBuilder.fromUriString(" "), HttpMethod.GET, HttpEntity.EMPTY,
new ParameterizedTypeReference<MockOutput>() {
});
return new MockOutput();
}
}
I'm trying to mock the MockService bean in application context using #MockBean
MockControllerTest
#SpringBootTest
#ActiveProfiles("test")
#Profile("test")
#RunWith(SpringJUnit4ClassRunner.class)
public class MockControllerTest {
#Autowired
private MockMainController mockMainController;
#MockBean
private MockService mockService;
#Test
public void controllerTest() {
MockOutput output = mockMainController.mockRequest();
given(this.mockService.serviceLogic(ArgumentMatchers.any(MockInput.class)))
.willReturn(new MockOutput("hello", "success"));
System.out.println(output); //null
}
}
In the test method I created mock service bean using #MockBean I'm not having any error here but System.out.println(output); prints null
You are getting null because of wrong statements order in your test method. You first call controller method and you get what's inside default #MockBean which is in this case null. Swap statement:
MockOutput output = mockMainController.mockRequest();
with
given(this.mockService.serviceLogic(ArgumentMatchers.any(MockInput.class)))
.willReturn(new MockOutput("hello", "success"));
and you will get expected result.

Not able to access Spring boot application.properties

I am trying to access application.properties values in spring boot application's service class. But every time value is null, so it throws NullPointException. I can get the right value for port in controller class(if i add #Autowired in controller) but not in service class. What changes are required to make this properties available through out the application?
Controller looks like:
#RestController
public class MyController {
MyService ss = new MyService();
#RequestMapping(value = "/myapp/abcd", method = RequestMethod.POST, consumes = {"application/json"})
public ResponseMessage sendPostMethod(#RequestBody String request) {
log.debug(" POST Request received successfully; And request body is:"+ request);
ResponseMessage response = ss.processRequest(request);
return response;
}
}
And Service class is:
#Service
public class MyService {
private ApplicationProperties p;
#Autowired
public setProps(ApplicationProperties config) {
this.p = config;
}
public ResponseMessage processRequest(String request) {
System.out.println("Property value is:"+ p.getPort());
}
}
ApplicationProperties.java looks like:
#Component
#Getter
#Setter
#ConfigurationProperties
public class ApplicationProperties {
String port;
}
Finally, application.properties file has:
port=1234
I have even tried passing ApplicationProperties instance from controller to service's default constructor, but it did not work. When I debug, value persist while application startup, but when I make a rest web service POST call, it is null.
In your controller MyController, you have to inject the service, replacing:
MyService ss = new MyService();
by:
#Autowired
MyService ss;
Also, instead of your ApplicationProperties class, you can use #Value annotation, from Spring, to load properties from application.properties file. Take a look at this code:
import org.springframework.beans.factory.annotation.Value;
// ...
#Service
public class MyService {
#Value("${port}")
private String port;
// ...
}
I had the same issue but for other reasons.
For me the solution was to add spring. before each parameter in application.properties.
So e.g. "spring.flyway.user=root".

Error 500: org.hibernate.HibernateException: No Session found for current thread [duplicate]

This question already has answers here:
Spring and hibernate: No Session found for current thread
(6 answers)
Closed 4 years ago.
I'm getting this error when I try to upload a picture in my project. The project executes fine until it has to effectively upload the picture to the database (I'm using postgresql), but this last step never works.
The following code was updated having considered the answers below.
Here's my controller (a part of it):
#Autowired
private FileUploadImpl fileUploadImpl;
...
#RequestMapping(value = "publish4" ,method = RequestMethod.POST)
public ModelAndView publish4(#Valid #ModelAttribute("fourthPublicationForm") final FourthPublicationForm form, final BindingResult errors,
#RequestParam("type") String type, #RequestParam("operation") String operation , #RequestParam CommonsMultipartFile[] fileUpload) {
if (errors.hasErrors()) {
//return helloPublish3(form,operation,type);
}
System.out.println("operation: "+ operation);
System.out.println("type: "+ type);
ps.create(form.getTitle(), form.getAddress(), operation, form.getPrice(), form.getDescription(),
type, form.getBedrooms(), form.getBathrooms(), form.getFloorSize(), form.getParking());
if (fileUpload != null && fileUpload.length > 0) {
for (CommonsMultipartFile aFile : fileUpload){
System.out.println("Saving file: " + aFile.getOriginalFilename());
UploadFile uploadFile = new UploadFile();
uploadFile.setAddress(form.getAddress());
uploadFile.setData(aFile.getBytes());
fileUploadImpl.save(uploadFile);
}
}
return new ModelAndView("redirect:/hello/home");
}
This is fileUploadDao in interface:
public interface FileUploadDao {
void save(UploadFile uploadFile);
}
This is in services:
#Service
public class FileUploadImpl {
#Autowired
private FileUploadDao fileUploadDao;
public FileUploadImpl() {
}
#Transactional
public void save(UploadFile uploadFile) {
fileUploadDao.save(uploadFile);
}
}
THe following in persistence:
#Repository
public class FileUploadDAOImpl implements FileUploadDao {
#Autowired
private SessionFactory sessionFactory;
public FileUploadDAOImpl() {
}
public FileUploadDAOImpl(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
public void save(UploadFile uploadFile) {
sessionFactory.getCurrentSession().save(uploadFile);
}
}
I got this in WebConfig.java (among other stuff)
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(dataSource());
sessionFactory.setPackagesToScan(
new String[] { "ar.edu.itba.paw" }
);
//sessionFactory.setHibernateProperties(hibernateProperties());
return sessionFactory;
}
#Autowired
#Bean(name = "fileUploadDao")
public FileUploadDao getUserDao(SessionFactory sessionFactory) {
return new FileUploadDAOImpl(sessionFactory);
}
#Bean(name = "multipartResolver")
public CommonsMultipartResolver getCommonsMultipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(20971520); // 20MB
multipartResolver.setMaxInMemorySize(1048576); // 1MB
return multipartResolver;
}
#Bean
#Autowired
public HibernateTransactionManager transactionManager(
SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory);
return txManager;
}
A little bit more of the error:
org.hibernate.HibernateException: No Session found for current thread
at org.springframework.orm.hibernate4.SpringSessionContext.currentSession(SpringSessionContext.java:106)
at org.hibernate.internal.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:1014)
at ar.edu.itba.paw.persistence.FileUploadDAOImpl.save(FileUploadDAOImpl.java:25)
at ar.edu.itba.paw.webapp.controller.HelloWorldController.publish4(HelloWorldController.java:260)
I've seen other questions where the answer was the lack of use of "transactional". I'm using that annotation here, but I'm not sure if the way it's 100% correct.
First remove #Transactional from FileUploadDAOImpl.
Change base package accordingly,
sessionFactory.setPackagesToScan(
new String[] { "base.package.to.scan" }
);
base.package.to.scan seems like invalid base package naming, change it to ar.edu.itba.paw.
You need a transaction manager to get use of #Transactional. Add it to WebConfig
#Bean
#Autowired
public HibernateTransactionManager transactionManager(
SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager();
txManager.setSessionFactory(sessionFactory());
return txManager;
}
This might get this code work, give it a try.
UPDATE: Also make sure following annotations present on WebConfig class,
#Configuration
#ComponentScan({"ar.edu.itba.paw"})
#EnableTransactionManagement(mode = AdviceMode.PROXY)
public class WebConfig {
// code
}
As you said from the first place, you have confused the actual layers. Still you could make it work properly in your situation but lets discuss a bit your implementation.
FileUploadDao is it a DAO or is it a Service ?
FileUploadImpl seems that you're confusing #Service with #Repository ,
maybe reading this out might help you. Spring Data Repositories , Spring Service Annotation
You ve made a transactional method , save in which i cannot say what you want to achieve exactly. You are also autowiring both FileUploadDao and SessionFactory, although you want to implement the first and inside the method you are trying to persist the object twice by first calling save upon the repository (thats a StackOverflowError from the first place, but you are lucky because Spring knows what to autowire) and then you are trying to call save a second time upon the Hibernate's SessionFactory , which breaks the abstract JPA contract. Also if you noticed , the error at the logs you posted , comes from the second save.
#Transactional not going to discuss how is this working , as you haven't posted your whole app-config. But again , you could read this for more info.
So based on the examples you shared , i am going to prepare 2 cases which might help you understand whats going on underneath.
First Case , Spring DATA , not really care if its Hibernate or another JPA provider underneath.
Your FileUploadImpl Becomes : FileUploadService
#Service
public class FileUploadService {
#Autowired
private FileUploadDao fileUploadDao;
public FileUploadService() {
}
#Transactional
public void save(UploadFile uploadFile) {
fileUploadDao.save(uploadFile);
}
}
Inside your controller , you are Autowiring the Service (layer) not directly the Repository/DAO(layer). There is not anything that stops you tho , its just a matter of design(if you still not get that point, raise another question).
A part of your part's Controller
#Autowired
private FileUploadService fileUploadService;
#RequestMapping(value = "publish4" ,method = RequestMethod.POST)
public ModelAndView publish4(#Valid #ModelAttribute("fourthPublicationForm") final FourthPublicationForm form, final BindingResult errors,
#RequestParam("type") String type, #RequestParam("operation") String operation , #RequestParam CommonsMultipartFile[] fileUpload) {
.........
fileUploadService.save(uploadFile);
}
Second Case , if you really want to use hibernate goodies , then there is not any reason autowiring the Repository , but simply implement those calls by yourself.
import org.springframework.stereotype.Component;
#Component
public class FileUploadDao {
#Autowired
private SessionFactory sessionFactory;
public FileUpload save(FileUpload obj) {
return sessionFactory.getCurrentSession().save(obj);
}
public FileUpload merge(FileUpload obj) {
return sessionFactory.getCurrentSession().merge(obj);
}
..... delete / update / or custom queries(SQL/JPQL/HQL) can be placed here
}
Your service simply exposes those methods , check the difference , i am applying the #Transactional annotation on this layer(ofc again you can put it in the DAO layer, but as i said its a matter of design).
#Service
public class FileUploadService {
#Autowired
private FileUploadDao fileUploadDao;
public FileUploadService() {
}
#Transactional
public UploadFile save(UploadFile uploadFile) {
fileUploadDao.save(uploadFile);
}
#Transactional
public UploadFile merge(UploadFile uploadFile) {
fileUploadDao.merge(uploadFile);
}
....rest of the methods you want to expose , or combinations of mulitple DAOs
}
Your controller remains the same , and thats the actual reason you need to have layers.

Categories