Calling service depend on PathVariable using single controller - java

I need to call implementation service from single controller depend on PathVariable
/{variable}/doSomething
public void controller(#PathVariable("variable") variable)
if variable == 1
call service1Impl();
else if variable == 2
call service2Impl();
but I need my controller plain like this and not using if, else
public void controller(...) {
call service();
}
I need to find some solution for auto-configuration my app when getting any PathVariable, it should know which service needs to call.
I try to using
load Config.class as context - #Configuration
#Configuration
public class AppConfig {
#Bean(name = "variableValue1")
public DummyService getService1() {
return new DummyServiceImpl();
}
#Bean(name = "variableValue2")
public AnotherService getService2() {
return new AnotherServiceImpl();
}
but in controller I need to load this config as context then its not plain enough
bean factory
its work but my controller not enough plain for me
I need to do like this one but it must based on PathVariable not property name.
#Configuration
public class GreetingServiceConfig {
#Bean
#ConditionalOnProperty(name = "language.name", havingValue = "english", matchIfMissing = true)
public GreetingService englishGreetingService() {
return new EnglishGreetingService();
}
#Bean
#ConditionalOnProperty(name = "language.name", havingValue = "french")
public GreetingService frenchGreetingService() {
return new FrenchGreetingService();
}
}
------------------------------------------------
#RestController
public class HomeController {
#Autowired
GreetingService greetingService;
#RequestMapping("/")
public String home() {
return greetingService.greet();
}
}

So based on the pathvariable, the specific method needs to be executed..
This is just a suggestion, since you dont want to go for if else
you can use Hashmap for this,
HashMap<Integer, Runnable> hm = new HashMap<Integer, Runnable> ();
For example,
pathvariable is 1 -> method be executed is method1()
pathvariable is 2 -> method be executed is method2()
hm.put(1, method1())
hm.put(2, method2())
So in controller,
if PathVariable is 1,
hm.get(1).run(); // hm.get(variable).run()

Related

Spring AOP doesn't work with #Component class

I'm using Spring AOP for exception handling but there is one point that I guess my component class is out of Spring Proxy so Spring AOP annotation that I created doesn't work in that class.
#Configuration
#AllArgsConstructor
public class MGRuleConfig {
private final GRepository repository;
private final GInitializer initializer;
private final GMapper mapper;
#Bean
#Qualifier("mRules")
public List<GRules> mRules(){
SSRule rule1 = new SSRule();
CSRule rule2 = new CSRule();
MPRule rule3 = new MPRule();
EGRule rule4 = new EGRule();
return List.of(rule1, rule2, rule3, rule4);
}
#Bean
public GService gService() {
return new MGServiceImpl(repository, initializer, mapper);
}
}
Then I have this service;
#Service
#RequiredArgsConstructor
public class MGServiceImpl implements GService {
............
#Override
public GaDTO executeRules(String gId, Integer pN) {
Ga ga = repository.findById(gId);
GaDTO gaDTO = mapper.toDTO(ga);
List<GaRules> mRules = (List<GaRules>) applicationContext.getBean("mRules");
mRules.forEach(rule -> rule.apply(gaDTO, pN));
repository.save(mapper.toEntity(gaDTO));
return gaDTO;
}
I need to put my exception handling annotation into that apply method but aspect doesn't work in that method.
#Component
public class SSRule implements GaRules {
#Override
#IPException
public void apply(GaDTO gaDTO, Integer pN) {
PDTO p1 = gaDTO.getP1();
PDTO p2 = gaDTO.getP2();
if (PTEnum.P_1.equals(gaDTO.getPT())) {
sS(gaDTO, pN, p1, p2);
} else {
sS(gaDTO, pN, p2, p1);
}
}
Annotation doesn't work in there. Here's my aspect class;
#Aspect
#Component
public class IPExceptionAspect {
#Around("execution(public * c.m.s.r.i.SSRule.apply(..)) && " +
"#annotation(c.m.s.i.a.IPException)")
public Object checkIP(ProceedingJoinPoint pjp) throws Throwable {
pjp.proceed();
return pjp;
}
}
So, what should I do to make IPException annotation and my Spring AOP work and why doesn't it work?
The problem is your code, you are creating instances of those rules yourself inside a bean method and expose them as a List. Which means the bean is of type List not your own SSRule and thus it won't work.
Instead make an #Bean method, or use the detected instance to inject into the list. As your SSRule is annotated you will already have an instance, just inject that into your #Bean method.
Bean
#Qualifier("mRules")
public List<GRules> mRules(SSRule rule1){
CSRule rule2 = new CSRule();
MPRule rule3 = new MPRule();
EGRule rule4 = new EGRule();
return List.of(rule1, rule2, rule3, rule4);
}
Now you will get the Spring managed instance which will have AOP applied.
Although I would hardly call this AOP as it is too specific for one class (not really crosscutting in that regard).

How to dynamically autowire beans in Spring with annotations?

A user can write down a url, and then, depending on the pattern of the url, my interface should use the correct implementation. Therefore, want to dynamically change my Spring's bean logic execution depending on that url that my controller receives.
Here is my controller:
#PostMapping(value = "/url",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.TEXT_HTML_VALUE)
public ResponseEntity<InputStreamResource> parseUrl(#RequestParam String url) throws IOException {
myInterface.dosomething(url);
return ResponseEntity.ok();
}
My interface :
public interface Myterface {
void myInterface(String url);
}
and my implementations:
#Service
public class myImpl1 implements myInterface {
#Override
public void doSomething(String url) {}
}
I already tried #Qualifier, but it is not dynamic. The thing is that I will have a lot of different url patterns and therefore implementations overtime, I'd like to have to add only one class per pattern and not to have to modify anything.
You can try something like this in a configuration class or you can use #Profile annotation :
#Configuration
public class MyConfig {
#Bean
public MyInterface createBean(String URL) {
MyInterface bean;
switch(URL) {
case url1:
bean = new Implementation1();
break;
case url2:
bean = new Implementation2();
break;
default: bean = new DefaultImplementation();
}
return bean;
}
}
Check this answer for more details.

Alternative to ApplicationContext.getBean() in Spring

I am using SpringBoot in my application and am currently using applicationContext.getBean(beanName,beanClass) to get my bean before performing operations on it. I saw in a couple of questions that it is discouraged to use getBean(). Since I am very new to Spring I don't know all the best practices and am conflicted. The solutions posed in the above linked question probably won't work in my use case. How should I approach this?
#RestController
#RequestMapping("/api")
public class APIHandler {
#Value("${fromConfig}")
String fromConfig;
private ApplicationContext applicationContext;
public Bot(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
#PostMapping(value = "")
public ResponseEntity post(#RequestBody HandlingClass requestBody) {
SomeInterface someInterface = applicationContext.getBean(fromConfig, SomeInterface.class);
someInterface.doSomething();
}
}
I have an interface called SomeInterface defined like:
public interface SomeInterface {
void doSomething();
}
And I have 2 classes which implements this interface called UseClass1 and UseClass2. My config file stores a string with the bean name of a class which I need to know in run-time and call the appropriate implementation of the method.
Any directions would be appreciated.
Since Spring 4.3 you can autowire all implementations into a Map consisting of pairs beanName <=> beanInstance:
public class APIHandler {
#Autowired
private Map<String, SomeInterface> impls;
public ResponseEntity post(#RequestBody HandlingClass requestBody) {
String beanName = "..."; // resolve from your requestBody
SomeInterface someInterface = impls.get(beanName);
someInterface.doSomething();
}
}
assuming you have two implementations like following
// qualifier can be omitted, then it will be "UseClass1" by default
#Service("beanName1")
public class UseClass1 implements SomeInterface { }
// qualifier can be omitted, then it will be "UseClass2" by default
#Service("beanName2")
public class UseClass2 implements SomeInterface { }
This is only code works for me to get beans dynamically from ApplicationContext
#Service
public class AuthenticationService {
#Autowired
private ApplicationContext сontext;
public boolean authenticate(...) {
boolean useDb = ...; //got from db
IAuthentication auth = context.getBean(useDb ? DbAuthentication.class : LdapAuthentication.class);
return auth.authenticate(...);
}
}
You can define your spring bean component with
#Profile("dev") , #Profile("test")
and inject as mention comment, then switch profile with
-Dspring.profiles.active=test jvm argument
The real question is not how to solve this, but why would you inject something different based on a configuration value?
If the answer is testing, then perhaps it's better to use #Profiles as #murat suggested.
Why are different implementations of an interface there on your classpath?
Can't you package your application in a way that only one is there for one use case? (see ContextConfiguration)
I think you should probably use a configuration class to produce your bean based on the fromConfig string value:
Your controller:
#RestController
#RequestMapping("/api")
public class APIHandler {
#Autowired
SomeInterface someInterface;
#PostMapping(value = "")
public ResponseEntity post(#RequestBody HandlingClass requestBody) {
someInterface.doSomething();
}
}
The bean producer:
#Configuration
public class SomeInterfaceProducer {
#Value("${fromConfig}")
String fromConfig;
#Bean
public SomeInterface produce() {
if (fromConfig.equals("aValueForUseClass1") {
return new UseClass1();
} else {
return new UseClass2();
}
//...
}
}
or if you have DI in UseClass1 and/or UseClass2:
#Configuration
public class SomeInterfaceProducer {
#Value("${fromConfig}")
String fromConfig;
#Bean
public SomeInterface produce(#Autowired YourComponent yourComponent) {
SomeInterface someInterface;
if (fromConfig.equals("aValueForUseClass1") {
someInterface = new UseClass1();
someInterface.setYourComponent(yourComponent);
// or directly with the constructor if you have one with yourComponent as parameter.
} else {
someInterface = new UseClass2();
someInterface.setYourComponent(yourComponent);
}
//...
}
}

How to test #Cacheable?

I am struggling with testing #Cacheable within a Spring Boot Integration Test. This is my second day learning how to do Integration Tests and all of the examples I have found use older versions. I also saw an example of assetEquals("some value", is()) but nothing with an import statement to know which dependency "is" belongs to. The test fails at the second
This is my integration test....
#RunWith(SpringRunner.class)
#DataJpaTest // used for other methods
#SpringBootTest(classes = TestApplication.class)
#SqlGroup({
#Sql(executionPhase = ExecutionPhase.BEFORE_TEST_METHOD,
scripts = "classpath:data/Setting.sql") })
public class SettingRepositoryIT {
#Mock
private SettingRepository settingRepository;
#Autowired
private Cache applicationCache;
#Test
public void testCachedMethodInvocation() {
List<Setting> firstList = new ArrayList<>();
Setting settingOne = new Setting();
settingOne.setKey("first");
settingOne.setValue("method invocation");
firstList.add(settingOne);
List<Setting> secondList = new ArrayList<>();
Setting settingTwo = new Setting();
settingTwo.setKey("second");
settingTwo.setValue("method invocation");
secondList.add(settingTwo);
// Set up the mock to return *different* objects for the first and second call
Mockito.when(settingRepository.findAllFeaturedFragrances()).thenReturn(firstList, secondList);
// First invocation returns object returned by the method
List<Setting> result = settingRepository.findAllFeaturedFragrances();
assertEquals("first", result.get(0).getKey());
// Second invocation should return cached value, *not* second (as set up above)
List<Setting> resultTwo = settingRepository.findAllFeaturedFragrances();
assertEquals("first", resultTwo.get(0).getKey()); // test fails here as the actual is "second."
// Verify repository method was invoked once
Mockito.verify(settingRepository, Mockito.times(1)).findAllFeaturedFragrances();
assertNotNull(applicationCache.get("findAllFeaturedFragrances"));
// Third invocation with different key is triggers the second invocation of the repo method
List<Setting> resultThree = settingRepository.findAllFeaturedFragrances();
assertEquals(resultThree.get(0).getKey(), "second");
}
}
ApplicationContext, components, entities, repositories and service layer for tests. The reason why I do it this way is because this maven module is used in other modules as a dependency.
#ComponentScan({ "com.persistence_common.config", "com.persistence_common.services" })
#EntityScan(basePackages = { "com.persistence_common.entities" })
#EnableJpaRepositories(basePackages = { "com.persistence_common.repositories" })
#SpringBootApplication
public class TestApplication {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Cache config....
#Configuration
#EnableCaching
public class CacheConfig {
public static final String APPLICATION_CACHE = "applicationCache";
#Bean
public FilterRegistrationBean registerOpenSessionInViewFilterBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
OpenEntityManagerInViewFilter filter = new OpenEntityManagerInViewFilter();
registrationBean.setFilter(filter);
registrationBean.setOrder(5);
return registrationBean;
}
#Bean
public Cache applicationCache() {
return new GuavaCache(APPLICATION_CACHE, CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.DAYS)
.build());
}
}
The repository under test....
public interface SettingRepository extends JpaRepository<Setting, Integer> {
#Query(nativeQuery = true, value = "SELECT * FROM Setting WHERE name = 'featured_fragrance'")
#Cacheable(value = CacheConfig.APPLICATION_CACHE, key = "#root.methodName")
List<Setting> findAllFeaturedFragrances();
}
The first problem with SettingRepositoryIT is, the #Mock anotation on the field settingRepository. This is paradox for any normal-test, integration-test or any else.
You should let Spring bring in the dependencies for the class-under-test, which is SettingRepository in your case.
Please look at this example how #Autowired is used for the class-under-test, which is OrderService in this example:
#RunWith(SpringRunner.class)
// ApplicationContext will be loaded from the
// static nested Config class
#ContextConfiguration
public class OrderServiceTest {
#Configuration
static class Config {
// this bean will be injected into the OrderServiceTest class
#Bean
public OrderService orderService() {
OrderService orderService = new OrderServiceImpl();
// set properties, etc.
return orderService;
}
}
#Autowired
private OrderService orderService;
#Test
public void testOrderService() {
// test the orderService
}
}
Go for the documentation with the full example: § 15. Integration Testing
The second problem is that you do not have to test #Cachable. You should only test your implementation. Here is a very good example from Oliver Gierke on how you should test it: How to test Spring's declarative caching support on Spring Data repositories?
In my case I wanted to validate the expression in the unless expression in the #Cacheable annotation, so I think it makes perfect sense and I'm not testing Spring's code.
I managed to test it without using Spring Boot, so it is plain Spring test:
#RunWith(SpringRunner.class)
#ContextConfiguration
public class MyTest {
private static MyCacheableInterface myCacheableInterfaceMock = mock(MyCacheableInterface.class);
#Configuration
#EnableCaching
static class Config {
#Bean
public MyCacheableInterface myCacheableInterface() {
return myCacheableInterfaceMock;
}
#Bean
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager("myObject");
}
}
#Autowired
private MyCacheableInterface myCacheableInterface;
#Test
public void test() {
when(myCacheableInterfaceMock.businessMethod(anyString())).then(i -> {
List<MyObject> list = new ArrayList<>();
list.add(new MyObject(new Result("Y")));
return list;
});
myCacheableInterface.businessMethod("test");
verify(myCacheableInterfaceMock).businessMethod(anyString());
myCacheableInterface.businessMethod("test");
verifyNoMoreInteractions(myCacheableInterfaceMock);
}
}
In MyCacheableInterface I have the following annotation:
public interface MyCacheableInterface {
#Cacheable(value = "myObject", unless = "#result.?[Result.getSuccess() != 'Y'].size() == #result.size()")
List<MyObject> businessMethod(String authorization);
}

Why do I get 404 for rest with spring-boot

I am implementing rest services with Spring Boot. The entity classes are defined in a separate package. So I added that with Component annotation in Application.java.
#Configuration
#EnableAutoConfiguration
#ComponentScan("org.mdacc.rists.cghub.model")
#EnableJpaRepositories(basePackages = "org.mdacc.rists.cghub.model")
public class Application
{
public static void main( String[] args )
{
SpringApplication.run(Application.class, args);
}
}
Here is my controller class:
// SeqController.java
#RestController
public class SeqController {
#Autowired
private SeqService seqService;
#RequestMapping(
value = "/api/seqs",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<SeqTb>> getSeqs() {
List<SeqTb> seqs = seqService.findAll();
return new ResponseEntity<List<SeqTb>>(seqs, HttpStatus.OK);
}
}
I also created a JPA data repository that extends JPARepository in which I added custom query code.
// SeqRepository.java
#Repository
public interface SeqRepository extends JpaRepository<SeqTb, Integer> {
#Override
public List<SeqTb> findAll();
#Query("SELECT s FROM SeqTb s where s.analysisId = :analysisId")
public SeqTb findByAnalysisId(String analysisId);
}
Below is the servicebean class that implements a service interface
// SeqServiceBean.java
#Service
public class SeqServiceBean implements SeqService {
#Autowired
private SeqRepository seqRepository;
#Override
public List<SeqTb> findAll() {
List<SeqTb> seqs = seqRepository.findAll();
return seqs;
}
public SeqTb findByAnalysisId(String analysisId) {
SeqTb seq = seqRepository.findByAnalysisId(analysisId);
return seq;
}
}
When I started the application and type the following url in the browser "http://localhost:8080/api/seqs" , I got 404 error. What did I miss?
Edit #1:
I decided to take out the JPA repository stuff and change the controller class to the following:
#RestController
//#RequestMapping("/")
public class SeqController {
private static BigInteger nextId;
private static Map<BigInteger, Greeting> greetingMap;
private static Greeting save(Greeting greeting) {
if(greetingMap == null) {
greetingMap = new HashMap<BigInteger, Greeting>();
nextId = BigInteger.ONE;
}
greeting.setId(nextId);
nextId = nextId.add(BigInteger.ONE);
greetingMap.put(greeting.getId(), greeting);
return greeting;
}
static {
Greeting g1 = new Greeting();
g1.setText("Hello World!");
save(g1);
Greeting g2 = new Greeting();
g1.setText("Hola Mundo!");
save(g2);
}
#RequestMapping(
value = "/api/greetings",
method = RequestMethod.GET,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Collection<Greeting>> getGreetings() {
Collection<Greeting> greetings = greetingMap.values();
return new ResponseEntity<Collection<Greeting>>(greetings, HttpStatus.OK);
}
}
When I started the application and put "localhost:8080/api/greetings" in my browser I still got 404.
==>Did you make sure that your Spring Boot application class and your Rest Controller are in the same base package? For Example if your package for Spring Boot application class is com.example.demo, then your Rest Controller should be in same base package as com.example.demo.controller.
==>I think that is the reason boot is unable to map to the uri of your rest controller. Because #SpringBootApplication has #ComponentScan and #Configuration embedded in it already. Try doing this. I hope it works.
If spring boot starter web is not there in your pom.xml then add the same as the reason could be the code not being able to map the endpoints.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
The first thing I would try is to put #RequestMapping("/") on the class definition of the controller. Keep the same value on the method.
Another thing, unrelated to your problem, is that you do not need to define that custom query. JPA is actually smart enough to do the query you defined just by using that method name. Check out the findByLastName example here: https://spring.io/guides/gs/accessing-data-jpa/.

Categories