I am building a REST API to access a database and having trouble / consistently getting a whitepage error. Running in circles trying to find my error and/or my error in the flow or logic of the program.
Here is my application:
package com.skilldistillery.myRest;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#SpringBootApplication
#ComponentScan(basePackages= {"com.skilldistillery.edgemarketing"})
#EntityScan("com.skilldistillery.edgemarketing")
#EnableJpaRepositories("com.skilldistillery.myRest.repositories")
public class MyRestApplication extends SpringBootServletInitializer {
#Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(MyRestApplication.class);
}
public static void main(String[] args) {
SpringApplication.run(MyRestApplication.class, args);
}
}
My controller:
package com.skilldistillery.myRest.controllers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.skilldistillery.edgemarketing.entities.House;
import com.skilldistillery.myRest.services.HouseService;
#RestController
#RequestMapping("api")
#CrossOrigin({ "*", "http://localhost:4200" })
public class HouseController {
#Autowired
HouseService houseServ;
#GetMapping("index/{id}")
public House show(#PathVariable("id") Integer id) {
return houseServ.show(id);
}
}
My repo:
package com.skilldistillery.myRest.repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.skilldistillery.edgemarketing.entities.House;
#Repository
public interface HouseRepo extends JpaRepository<House, Integer> {
}
My service:
package com.skilldistillery.myRest.services;
import java.util.List;
import org.springframework.stereotype.Service;
import com.skilldistillery.edgemarketing.entities.House;
#Service
public interface HouseService {
List<House> index();
House show(Integer id);
}
And my ServiceImpl:
package com.skilldistillery.myRest.services;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.skilldistillery.edgemarketing.entities.House;
import com.skilldistillery.myRest.repositories.HouseRepo;
#Service
public class HouseServiceImpl {
#Autowired
HouseRepo hRepo;
public House show(Integer id) {
Optional<House> opt = hRepo.findById(id);
House house = null;
if (opt.isPresent()) {
house = opt.get();
}
return house;
}
}
It compiles and launches but via postman and browser, I am getting whitepage errors. I've scoured the internets trying to understand where I'm going wrong but not finding it. Please advise.
You can use the following solution.
Change your main class to the following code
#SpringBootApplication
public class MyrestapplicationApplication {
public static void main(String[] args) {
SpringApplication.run(MyrestapplicationApplication.class, args);
}
}
Then create a separate class for your configurations.As well as running away from tight coupled architecture.
#Configuration
#EntityScan("com.skilldistillery.edgemarketing.entities")
#EnableJpaRepositories("com.skilldistillery.myRest.repositories")
public class BusinessConfig {
#Bean
public HouseService houseService(final HouseRepo houseRepo){
return new HouseServiceImpl(houseRepo);
}
}
Your controller will then change to the following.Utilising Dependency Injection
#RestController
#RequestMapping("api")
#CrossOrigin({ "*", "http://localhost:4200" })
public class HouseController {
private HouseService houseServ;
public HouseController(HouseService houseServ) {
this.houseServ = houseServ;
}
#GetMapping(value = "index/{id}",produces = MediaType.APPLICATION_JSON_VALUE,consumes = MediaType.APPLICATION_JSON_VALUE)
public House show(#PathVariable("id") Integer id) {
return houseServ.show(id);
}
}
HouseServiceImpl should also implement HouseService
public class HouseServiceImpl implements HouseService{
private HouseRepo hRepo;
public HouseServiceImpl(HouseRepo hRepo) {
this.hRepo = hRepo;
}
#Override
public List<House> index() {
return null;
}
public House show(Integer id) {
Optional<House> opt = hRepo.findById(id);
House house = new House();
if (opt.isPresent()) {
house = opt.get();
}
return house;
}
}
*NB - don't forget to remove the following configs #Autowired,#Repository as they are now handled within the BusinessConfig class.More Beans can be defined in the BusinessConfig Class
Related
I have just started with Java Spring and am getting familiar with the framework.
Let's say I have a controller with two endpoints
"/remove_old"
"/remove_new"
They do the same job: controller layer -> service layer -> DAO except for databases which should be used in dao methods - those are different. As I understand, this can be nicely handled by Spring with no change in the service layer. How should I organize my beans to make it the most appropriate way? The only solution I can think of so far is to autowire everything and then expose Dao::setDatabase method which would be called at the controller layer.
Here is a spring-boot3, ARD-solution:
(starter-used)
Simple entity:
package com.example.routingds.demo;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import lombok.Data;
#Data
#Entity
class SomeEntity {
#Id
#GeneratedValue
Long id;
}
Plus repo:
package com.example.routingds.demo;
import org.springframework.data.jpa.repository.JpaRepository;
public interface SomeRepository extends JpaRepository<SomeEntity, Long> {}
An enum (for all our data sources/tenants):
package com.example.routingds.demo;
public enum MyTenant {
OLD, NEW;
}
A thingy like (ref1, ref2, ref3...):
package com.example.routingds.demo;
public class MyTenantThreadLocalContextHolder {
private static ThreadLocal<MyTenant> threadLocal = new ThreadLocal<>();
public static void set(MyTenant tenant) {
threadLocal.set(tenant);
}
public static MyTenant get() {
return threadLocal.get();
}
}
...(we have plenty options here, but this is thread safe & easy to test/static!)
Then a (very) simple controller like:
package com.example.routingds.demo;
import static com.example.routingds.demo.MyTenant.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
#Controller
public class DemoController {
#Autowired
private SomeRepository someRepository;
#DeleteMapping("/remove_new/{id}")
public void removeNew(#PathVariable Long id) {
removeInternal(NEW, id);
}
#DeleteMapping("/remove_old/{id}")
public void removeOld(#PathVariable Long id) {
removeInternal(OLD, id);
}
private void removeInternal(MyTenant current, Long id) {
// set context ...
MyTenantThreadLocalContextHolder.set(current);
// and "just delete" (ard+context will choose correct DS):
someRepository.deleteById(id);
}
}
Lets go wire it:
application.properties:
fallback.datasource.url=jdbc:h2:./data/fallback
fallback.datasource.username=sa
fallback.datasource.password=
#fallback.datasource. ... more if you like/need
old.datasource.url=jdbc:h2:./data/old
old.datasource.username=sa
old.datasource.password=
# ...
new.datasource.url=jdbc:h2:./data/new
new.datasource.username=sa
new.datasource.password=
# ...
# assuming all dbs initialized , otherwise set these (+ un-comment main class):
#spring.sql.init.mode=always
#spring.sql.init.continue-on-error=false
# debug:
spring.jpa.show-sql=true
spring.h2.console.enabled=true
# https://github.com/spring-projects/spring-data-jpa/issues/2717:
spring.jpa.properties.jakarta.persistence.sharedCache.mode=UNSPECIFIED
App/Main/Config:
package com.example.routingds.demo;
import static com.example.routingds.demo.MyTenant.*;
import java.util.Map;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
//import org.springframework.boot.autoconfigure.sql.init.SqlDataSourceScriptDatabaseInitializer;
//import org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
//import org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
#SpringBootApplication
public class RoutingDsDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RoutingDsDemoApplication.class, args);
}
// load the props:
#Bean
#Primary // one should be primary ...
#ConfigurationProperties("fallback.datasource")
public DataSourceProperties fallbackDSProps() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("old.datasource")
public DataSourceProperties oldDSProps() {
return new DataSourceProperties();
}
#Bean
#ConfigurationProperties("new.datasource")
public DataSourceProperties newDSProps() {
return new DataSourceProperties();
}
// the main (abstract routing) data source:
#Bean
#Primary
public DataSource dataSource(
#Qualifier("masterDS") DataSource masterDS,
#Qualifier("newDS") DataSource newDS,
#Qualifier("oldDS") DataSource oldDS) {
return new AbstractRoutingDataSource() {
{ // inline instance:
setTargetDataSources( // ! we operationally use only OLD, NEW:
Map.of(
// lookup key, data source:
OLD, oldDS,
NEW, newDS
)
);
//... but as a default/fallback/no-context:
setDefaultTargetDataSource(masterDS);
afterPropertiesSet();
}
// inline override:
#Override
protected Object determineCurrentLookupKey() {
return MyTenantThreadLocalContextHolder.get();
}
};
}
// the "slaves" / underlying / your DS's:
#Bean // default/master/backup/unused:
DataSource masterDS(DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
#Bean
DataSource oldDS(#Qualifier("oldDSProps") DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
#Bean
DataSource newDS(#Qualifier("newDSProps") DataSourceProperties props) {
return props.initializeDataSourceBuilder().build();
}
// for (script) db initialization, we might need this (+'sql.init.mode=always'):
// #Bean
// DataSourceScriptDatabaseInitializer initOld(#Qualifier("oldDS") DataSource oldDS, SqlInitializationProperties settings) {
// return new SqlDataSourceScriptDatabaseInitializer(oldDS, settings);
// }
//
// #Bean
// DataSourceScriptDatabaseInitializer initNew(#Qualifier("newDS") DataSource newDS, SqlInitializationProperties settings) {
// return new SqlDataSourceScriptDatabaseInitializer(newDS, settings);
// }
}
Init schema (src/main/resources/schema.sql):
create table some_entity (id bigint not null, primary key (id));
create sequence some_entity_seq start with 1 increment by 50;
TESTING TIME!! :))
package com.example.routingds.demo;
import static com.example.routingds.demo.MyTenant.*;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.IntStream;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
#SpringBootTest
#AutoConfigureMockMvc // we will test the controller!
class RoutingDsDemoApplicationTests {
// tenant/DS dependent test IDs:
static final EnumMap<MyTenant, Set<Long>> TEST_IDS = new EnumMap<>(
Map.of(
OLD, new HashSet<>(),
NEW, new HashSet<>()
)
);
#TestConfiguration // some "test setup":
static class TestDataConfig {
#Bean
InitializingBean testData(SomeRepository repo) { // <- normal/autowired repo
return () -> {
// for OLD and NEW (tenant):
Arrays.stream(MyTenant.values())
.forEach((t) -> {
// set context/db:
MyTenantThreadLocalContextHolder.set(t);
// clean up (we shouldn't need this/DANGER):
// repo.deleteAll();
// save 100 SomeEntity's, and store ids to TEST_IDS:
IntStream.range(0, 100).forEach((i) -> {
TEST_IDS.get(t).add(
repo.save(new SomeEntity()).getId()
);
});
});
};
}
}
#Autowired
MockMvc mockMvc;
#Autowired
SomeRepository helper;
#Test
void testRemoveOld() {
// for each (known) OLD id:
TEST_IDS.get(OLD).stream().forEach((id) -> {
try {
mockMvc
.perform(delete("/remove_old/" + id))
.andExpect(status().isOk());
} catch (Exception ex) {
fail(ex);
}
});
// verify deleted:
MyTenantThreadLocalContextHolder.set(OLD);
TEST_IDS.get(OLD).stream().forEach((id) -> {
assertFalse(helper.existsById(id));
});
}
#Test
void testRemoveNew() {
// for each (known) NEW id:
TEST_IDS.get(NEW).stream().forEach((id) -> {
try {
mockMvc
.perform(delete("/remove_new/" + id))
.andExpect(status().isOk());
} catch (Exception ex) {
fail(ex);
}
});
// verify deleted:
MyTenantThreadLocalContextHolder.set(NEW);
TEST_IDS.get(NEW).stream().forEach((id) -> {
assertFalse(helper.existsById(id));
});
}
}
passes:
Results:
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
See also:
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-access.configure-two-datasources
https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.data-initialization
I have encountered an interesting yet a terrifying situation. My application has two features namely, OtpListener and CdmMonitor running on two separate threads. For now, only the OtpListener feature was required so I commented out CdmMonitor in the main class.
However, I still see the logs for the commented out class CdmMonitorService. This service was running (when it shouldn't as it was commented)
Do threads implemented from the Runnable class operate this way? I did not find anything of the sort in its documentation.
Main Class: OtpTrackerApp.java
package dev.xfoil.otpTracker;
import dev.xfoil.otpTracker.service.CdmMonitorService;
import dev.xfoil.otpTracker.service.OTPListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan({"dev.xfoil.otpTracker.*","dev.xfoil.shared.*", "dev.xfoil.shared.dal.repositories.springdata.*", "dev.xfoil.shared.dal.entities.*","dev.xfoil.shared.dal.cache.*"})
public class OtpTrackerApp implements CommandLineRunner {
#Autowired
public OTPListener otpListener;
// #Autowired
// public CdmMonitorService cdmMonitorService;
public static void main(String[] args){
SpringApplication.run(OtpTrackerApp.class, args);
}
#Override
public void run(String... args) throws Exception {
// Thread monitoringThread = new Thread(cdmMonitorService);
// monitoringThread.start();
otpListener.startListening();
}
}
CdmMonitorService.java
package dev.xfoil.otpTracker.service;
import com.google.common.flogger.FluentLogger;
import dev.xfoil.otpTracker.firebase.FirestoreManager;
import dev.xfoil.shared.dal.repositories.springdata.MachineMonitorRepo;
import dev.xfoil.shared.dal.repositories.springdata.MachineOperationsHistoryRepo;
import dev.xfoil.shared.dal.repositories.springdata.MachinesRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
#Service
public class CdmMonitorService implements Runnable {
FluentLogger logger = FluentLogger.forEnclosingClass();
#Autowired
MachineMonitorRepo machineMonitorRepo;
#Autowired
MachineOperationsHistoryRepo machineOperationsHistoryRepo;
#Autowired
MachinesRepo machinesRepo;
#Autowired
FirestoreManager firestoreManager;
public CdmMonitorService() {
}
#Override
#Scheduled(fixedDelay = 120000l)
public void run() {
try {
// code removed for brevity
Thread.sleep(10000);
} catch (Exception e) {
e.printStackTrace();
logger.atWarning().log("Exception in CDM Monitor Thread " + e);
}
}
}
OtpListener.java
package dev.xfoil.otpTracker.service;
import com.google.common.flogger.FluentLogger;
import dev.xfoil.shared.dal.entities.AccountLedger;
import dev.xfoil.shared.dal.models.OTP;
import dev.xfoil.shared.dal.repositories.springdata.LedgerRepo;
import dev.xfoil.shared.dal.repositories.springdata.NoteCountRepo;
import dev.xfoil.shared.dal.repositories.springdata.UtilRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
#Service
public class OTPListener {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
#Autowired
UtilRepo utilRepo;
#Autowired
ThirdPartyApiCalls discordServer;
#Autowired
NoteCountRepo noteCountRepo;
private static LocalDateTime latestOTPTime;
#Value("#{${webhooks}}")
HashMap<String, String> discordLink = new HashMap<String, String>();
#PostConstruct
public LocalDateTime getLatestOTPTimestamp() {
latestOTPTime = utilRepo.getTimeOfLatestOTP();
return latestOTPTime;
}
public void startListening() throws InterruptedException {
logger.atInfo().log("Current in-mem links:");
try {
// code commented for brevity
Thread.sleep(5 * 60 * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
I have a scenario where my method to be intercepted is in the parent class and is not overridden in the pointcut class.
Here is the sample classes:
public abstract class A{
#RequestMapping(value = "/data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public String getData(#RequestBody String request) throws Exception {
return "dummy";
}
}
#RestController
public class B extends A {
}
My Aspect is defined as:
#Aspect
#Component
public class RestCallLogger {
#Pointcut("within(com.test..*) && within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllers() {
}
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMappingAnnotations() {
}
#Around("restControllers() && requestMappingAnnotations()")
public Object onExecute(ProceedingJoinPoint jp) throws Throwable {
Object result = null;
try {
result = jp.proceed();
} catch (Exception ex) {
throw ex;
}
return result;
}
}
But its not working. If I mark class A with Annotation #RestController and make it concrete, then it works.
The question is how can I create a "pointcut for method in parent abstract class"?
PS: I can not change the hierarchy of the code as its the existing code.
For me this works. Here is an MCVE:
package de.scrum_master.app;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
public abstract class A {
#RequestMapping(value = "/data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public String getData(#RequestBody String request) throws Exception {
return request;
}
}
package de.scrum_master.app;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class B extends A {}
package de.scrum_master.app;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ComponentScan(basePackages = { "de.scrum_master" })
public class Application2 {
public static void main(String[] args) throws Exception {
ApplicationContext appContext = new AnnotationConfigApplicationContext(Application2.class);
B b = (B) appContext.getBean("b");
System.out.println(b.getData("bbb"));
A a = (A) appContext.getBean("b");
System.out.println(a.getData("aaa"));
}
}
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class RestCallLogger {
#Pointcut("within(de.scrum_master..*) && #target(org.springframework.web.bind.annotation.RestController)")
public void restControllers() {}
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMappingAnnotations() {
}
#Around("restControllers() && requestMappingAnnotations()")
public Object onExecute(ProceedingJoinPoint jp) throws Throwable {
System.out.println(jp);
Object result = null;
try {
result = jp.proceed();
} catch (Exception ex) {
throw ex;
}
return result;
}
}
The console log says:
execution(String de.scrum_master.app.A.getData(String))
bbb
execution(String de.scrum_master.app.A.getData(String))
aaa
What is different in your case?
I have encountered a very strange exception when I was learning AOP in Spring.
Here are my codes:
CompactDisc Interface:
public interface CompactDisc {
void play();
}
BlankDisc class:
import org.springframework.stereotype.Component;
import java.util.List;
#Component
public class BlankDisc implements CompactDisc {
private String title;
private String artist;
private List<String> tracks;
public BlankDisc(String title, String artist, List<String> tracks) {
this.title = title;
this.artist = artist;
this.tracks = tracks;
}
#Override
public void play() {
System.out.println("Playing "+title+" by "+artist);
for (String track : tracks)
System.out.println("-Track "+track);
}
public void playTrack(int trackNumber) {
System.out.println("-Track "+tracks.get(trackNumber));
}
TrackCounter class
package soundsystem;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import java.util.HashMap;
import java.util.Map;
#Aspect
public class TrackCounter {
private Map<Integer,Integer> trackCounts = new HashMap<>();
#Pointcut("execution(* BlankDisc.playTrack(int)) " +
"&& args(trackNumber)")
public void trackPlayed(int trackNumber) {}
#Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}
public int getPlayCount(int trackNumber) {
return trackCounts.getOrDefault(trackNumber, 0);
}
}
TrackCounterConfig class
package soundsystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import java.util.ArrayList;
import java.util.List;
#Configuration
#EnableAspectJAutoProxy
public class TrackCounterConfig {
#Bean(name = "compactDisc")
public BlankDisc blankDisc() {
List<String> list= new ArrayList<>(5);
list.add("Sgt.Pepper's Lonely Hearts Club Band");
list.add("With a Little Help from My Friends");
list.add("Lucy in the Sky with Diamonds");
list.add("Getting Better");
list.add("Fixing a Hole");
return new BlankDisc("Sgt.Pepper's Lonely Hearts Club Band","The Beatles",list);
}
#Bean
public TrackCounter trackCounter() {
return new TrackCounter();
}
}
TrackCounterTest class:
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import soundsystem.*;
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TrackCounterConfig.class)
public class TrackCounterTest {
#Autowired
private BlankDisc cd;
#Autowired
private TrackCounter counter;
#Test
public void testTrackCounter() {
//Question: cd must be CompactDisc not BlankDisc,
//otherwise will cause type exception, why?
cd.playTrack(0);
cd.playTrack(1);
cd.playTrack(2);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(3);
cd.playTrack(4);
assertEquals(1,counter.getPlayCount(1));
assertEquals(1,counter.getPlayCount(0));
assertEquals(1,counter.getPlayCount(2));
assertEquals(4,counter.getPlayCount(3));
assertEquals(1,counter.getPlayCount(4));
}
}
I ran the test,then I had the exception:
Caused by: org.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'compactDisc' is expected to be of type 'soundsystem.BlankDisc' but was actually of type 'com.sun.proxy.$Proxy22'
at org.springframework.beans.factory.support.DefaultListableBeanFactory.checkBeanNotOfRequiredType(DefaultListableBeanFactory.java:1510)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1489)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
The problems are:
1.If I change the type of cd in TrackCounterTest from BlankDisc to CompactDisc, the exception will not occur, why?
2.After the first problem being solved, if I haven't declared the method void playTrack(int) in the interface CompactDisc, the assertEquals() in test class will fail. That is, AOP does not work, why?
I have been spent a whole day working on this problem and after searching on the google I still could not be able to find the answer. Since I am a rookie in Spring, I sincerely hope that someone could help me to figure this out.
Thank you!!!
This issue is because of autowiring the bean of type class instead do for implemented interface:
Please check this stackoverflow link which explains the same issue:
BeanNotOfRequiredTypeException issue stackoverflow explanation
Im studying the Spring doc in https://docs.spring.io/spring-boot/docs/current/reference/html/getting-started-first-application.html. When I create another class, the methods are not mapped.
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
#RestController
#EnableAutoConfiguration
public class ExampleError {
#RequestMapping("/testmap")
public String test() {
return "TEST 2";
}
}
My main class works fine:
import org.springframework.boot.*;
import org.springframework.boot.autoconfigure.*;
import org.springframework.stereotype.*;
import org.springframework.web.bind.annotation.*;
#RestController
#EnableAutoConfiguration
public class Example {
#RequestMapping("/")
String home() {
return "Hello World!";
}
#RequestMapping("/test")
String teste() {
return "TEST 1";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Example.class, args);
}
}
Can someone clarify me?
Regards