I have a service which caches the response. I use a simple Map for caching the response.
I also have two scopes: #RequestScope and #GrpcScope. #RequestScope is obviously for requests received from rest controller whereas #GrpcScope is for grpc requests.
My service doesn't know what scope it is currently running in. Both (grpc and rest) controllers uses the same service and there could be only one scope active at runtime.
I want FooService to use RequestScopedFooCache in if the the scope is #RequestScope and to use GrpcScopedFooCache if the scope is #GrpcScope. How can I do that?
// for RequestScope
#RestController
public class FooRestController{
#Autowired
FooService fooService;
#GetMapping
public BarResponse getFoo(){
return fooService.fooRequest(...)
}
}
// for #GrpcScope
#GrpcController
public class FooGrpcController{
#Autowired
FooService fooService;
public BarResponse getFoo(){
return fooService.fooRequest(...)
}
}
#Service
public class FooService {
#Autowired
FooCache cache; // doesn't know which bean to autowire however there could be only one of them at runtime
BarResponse fooRequest(String id) {
if(cache.get(id))
...
}
}
public interface FooCache {
...
}
#Component
#RequestScope
public class RequestScopedFooCache implements FooCache {
...
}
#Component
#GrpcScope
public class GrpcScopedFooCache implements FooCache {
...
}
Using SpringBoot version 2.4.+
Not exactly what you are asking for but I would probably create my own bean methods for FooService and give them qualifying names.
Changed FooService to take FooCache as a constructor:
public class FooService {
private final FooCache cache;
public FooService(FooCache cache) {
this.cache = cache;
}
BarResponse fooRequest(String id) {
if (cache.get(id))
...
}
}
Give the FooCache implementations qualifying names:
public interface FooCache {
...
}
#Component("GRPC_CACHE")
#RequestScope
public class RequestScopedFooCache implements FooCache {
...
}
#Component("REQUEST_CACHE")
#GrpcScope
public class GrpcScopedFooCache implements FooCache {
...
}
A bean manager to setup the FooService beans:
class FooServiceBeanManager {
#Bean("GRPC_SERVICE")
public FooService grpcFooService(#Qualifier("GRPC_CACHE") FooCache fooCache) {
return new FooService(fooCache);
}
#Bean("REQUEST_SERVICE")
public FooService requestFooService(#Qualifier("GRPC_CACHE") FooCache fooCache) {
return new FooService(fooCache);
}
}
Specify which bean you want in your controllers using #Qualifer:
// for RequestScope
#RestController
public class FooRestController {
private final FooService fooService;
#Autowired
public FooRestController(#Qualifier("REQUEST_SERVICE") FooService fooService) {
this.fooService = fooService;
}
#GetMapping
public BarResponse getFoo() {
return fooService.fooRequest(...)
}
}
// for #GrpcScope
#GrpcController
public class FooGrpcController {
private final FooService fooService;
#Autowired
public FooGrpcController(#Qualifier("GRPC_SERVICE") FooService fooService) {
this.fooService = fooService;
}
public BarResponse getFoo() {
return fooService.fooRequest(...)
}
}
I changed the controller classes to have #Autowired constructors instead of fields. It is the recommended usage (source: Spring #Autowire on Properties vs Constructor)
I also personally prefer to have my own #Bean methods as much as possible because it gives me much more control.
Related
Is it possible to externalize #MockBean definitions, and combine them with composition instead of inheritance?
Because I find myself often repeating mock definitions, and I would rather be able to load/inject them and define them outside of the test class.
Example:
#SpringBootTest
public class Service1Test {
#MockBean
private Service1 service1;
#BeforeEach
public void mock() {
when(service1.call()).thenReturn(result1);
}
}
#SpringBootTest
public class Service2Test {
#MockBean
private Service2 service2;
#BeforeEach
public void mock() {
when(service2.call()).thenReturn(result2);
}
}
#SpringBootTest
public class Service3Test {
#MockBean
private Service1 service1;
#MockBean
private Service2 service2;
#BeforeEach
public void mock() {
when(service1.call()).thenReturn(result1);
when(service2.call()).thenReturn(result2);
}
}
I would like to externalize the mocks somehow, so I could just load them.
Pseudocode as follows:
public class MockService1 {
#MockBean
private Service1 service1;
#BeforeEach
public void mock() {
when(service1.call()).thenReturn(result1);
}
}
#SpringBootTest
#Import({MockService1.class, MockService2.class})
public class Service3Test {
}
Yes, you can achieve it by using an external configuration class. For example:
#TestConfiguration
public class TestConfig {
#Bean
#Primary
public Foo foo() {
return mock(Foo.class); //you can use Mockito or a different approach
here
}
#Bean
#Primary
public Bar bar() {
return mock(Foo.class);
}
}
#Import(TestConfig.class)
public class MyTestClass {
#Autowire
private Foo foo;
#Autowire
private Bar bar;
}
I have this specific problem in which I can't use a #Qualifier cause I need the bean in the parent class. My idea is to remove the baseComponent propertie and make an abstract method in BaseController like getComponent() and return the desired bean for BaseComponent ... but perhaps there is a cleaner way to do this through configuration.
#RestController
public abstract class BaseController {
#Autowired
private BaseComponent baseComponent;
#GetMapping("/something")
public void doSomething() {
baseComponent.printSomething();
}
}
#RestController
#RequestMapping(value = "/foo")
public class FooController extends BaseController {
}
#RestController
#RequestMapping(value = "/bar")
public class BarController extends BaseController {
}
public interface BaseComponent {
void printSomething();
}
#Component
public class FooComponent implements BaseComponent {
#Override
public void printSomething() {
System.out.println("foo!");
}
}
#Component
public class BarComponent implements BaseComponent{
#Override
public void printSomething() {
System.out.println("bar!");
}
}
This is one of the reasons I don't like autowiring directly to a private field. I would do this by injecting BaseComponent through the constructor of BaseController:
public abstract class BaseController {
private final BaseComponent baseComponent;
protected BaseController(BaseComponent baseComponent){
this.baseComponent = baseComponent;
}
#GetMapping("/something")
public ResponseEntity<String> getSomething(){
return new ResponseEntity<String>(baseComponent.getSomething(), HttpStatus.OK);
}
}
#RestController
#RequestMapping("/foo")
public class FooController extends BaseController{
#Autowired
public FooController(#Qualifier("fooComponent") BaseComponent baseComponent) {
super(baseComponent);
}
}
#RestController
#RequestMapping("/bar")
public class BarController extends BaseController{
#Autowired
public BarController(#Qualifier("barComponent") BaseComponent baseComponent){
super(baseComponent);
}
}
#Component
public class BarComponent implements BaseComponent {
#Override
public String getSomething() {
return "bar";
}
}
#Component
public class FooComponent implements BaseComponent {
#Override
public String getSomething() {
return "foo";
}
}
Requests to /something/bar will return bar and requests to something/foo will return foo.
Note that the abstract BaseComponent is not actually declared as any kind of Spring component nor does it have any dependencies automatically injected. Instead, the subclasses are the components and the dependencies are wired into their constructors and passed through super to BaseComponent. The subclass constructors provide a place for the #Qualifier annotation to specify which BaseComponent you want.
In theory, I don't like declaring two classes that are identical other than the annotations. In practice, though, I have found that sometimes it is simplest just to declare classes to hold the Spring annotations. That's better than the old days of XML configuration.
You can do it in the following way
import org.springframework.beans.factory.BeanFactory;
#RestController
public abstract class BaseController {
#Autowired
private BeanFactory beanFactory;
#GetMapping("/something")
public void doSomething() {
BaseComponent baseComponent = beanFactory.getBean("Foo", // or "Bar"
BaseComponent.class);
baseComponent.printSomething();
}
}
#Component("Foo")
public class FooComponent implements BaseComponent {
...
}
#Component("Bar")
public class BarComponent implements BaseComponent{
...
}
If I right understand what you want.
You can use generics if you don't want to hardcode the bean name:
#RestController
public abstract class BaseController<T extends BaseComponent> {
#Autowired
private T baseComponent;
...
}
#RestController
public class FooController extends BaseController<FooComponent> {
...
}
I have a config class that provides two implemenations of the same base bean interface. I want these to be set on the autowired fields conditionally based on an annotation on the field.
public class MyController
{
#Autowired
private MyBeanInterface base;
#Autowired
#MyAnnotation
private MyBeanInterface special;
}
This is a pesudo-code of the config class:
#Configuration
public class ConfigClass
{
#Bean
#Primary
public MyBeanInterface getNormalBeanInterface()
{
return new MyBeanInterfaceImpl();
}
#Bean
//This doesn't work
#ConditionalOnClass(MyAnnotation.class)
public MyBeanInterface getSpecialBeanInterface()
{
return new MyBeanInterfaceForMyAnnotation();
}
}
How can I make the annotated field be populated by the second bean?
Use Qualifier annotation. Example:
Controller:
Add Qualifier annotation at the injected fields with bean id as parameter:
public class MyController
{
#Autowired
#Qualifier("normalBean")
private MyBeanInterface base;
#Autowired
#Qualifier("specialBean")
private MyBeanInterface special;
}
ConfigClass
Specify bean id:
#Configuration
public class ConfigClass
{
#Bean(name="normalBean")
#Primary
public MyBeanInterface getNormalBeanInterface()
{
return new MyBeanInterfaceImpl();
}
#Bean(name="specialBean")
public MyBeanInterface getSpecialBeanInterface()
{
return new MyBeanInterfaceForMyAnnotation();
}
}
I have a #Service bean that I need static access to:
#Service
public class MyBean implements InitializingBean {
private static MyBean instance;
#Override
public void afterPropertiesSet() throws Exception {
instance = this;
}
public static MyBean get() {
return instance;
}
public String someMethod(String param) {
return "some";
}
}
Usage:
#Service
public class OtherService {
public static void makeUse() {
MyBean myBean = MyBean.get();
}
}
Problem: when I write an integration junit test for OtherService that makes use of the stat MyBean access, the instance variable is always null.
#RunWith(SpringRunner.class)
#SpringBootTest
public class ITest {
#Autowired
private OtherService service;
#MockBean
private MyBean myBean;
#Before
public void mock() {
Mockito.when(myBean.someMethod(any()).thenReturn("testvalue");
}
#Test
public void test() {
service.makeUse(); //NullPointerException, as instance is null in MyBean
}
}
Question: how can I write an integration test when using such type of static access to a spring-managed bean?
If you want to influence the #Bean-creation, then use #Configuration
#Configuration
public class MyConfig {
#Bean
public MyBean myBean() {
return new MyBean;
}
#Bean
public OtherService otherService () {
return new OtherService(myBean());
}
}
Then mocking becomes really easy:
#RunWith(SpringRunner.class)
#SpringBootTest
public class ITest {
#MockBean
private MyBean myBean;
#Autowired
private OtherService service;
#Test
public void test() {
// service is initialised with myBean
...
}
}
When more control is needed, then you can choose the following approach. It provides sometimes more control than just a #MockBean. In your test you can easily mock a method just before calling it. This is my preferred way.
#Configuration
public class MyConfig {
...
#Bean
public MyBean getMyBean() {
return mock( MyBean.class);
}
#Bean
public OtherService otherService () {
return new OtherService( getMyBean());
}
}
In the application you can use #Autorwired to access it AND implement method overrule/mocking easily.
#RunWith(SpringRunner.class)
#SpringBootTest
public class AnotherTest {
#Autowired
private MyBean myBean;
#Autowired
private OtherService service;
#Test
public void test() {
when( myBean.method( a, b)).thenReturn( something);
service.doSomething( ....); // with myBean.method() overruled
}
}
I have two datasources that are equal in their structure but not in their Data.
My application has to deal with both of them at the same time.
I have controller, servie, dao structure that looks like this.
Controller Modell:
#Controller
public abstract class MyFancyControllerModell{
private MyService service;
public MyFancyControllerModell (MyService service){
this.service = service;
}
#RequestMapping(....)
public void editSomeData(String data){....}
}
Controller implementation:
#Controller
#RequestMapping(...)
public class MyControllerImpl1 extends MyFancyControllerModell{
#Autowired
public MyControllerImpl1(#Qualifier("myServiceInstance1") MyService service){
super(service);
}
}
#Controller
#RequestMapping(...)
public class MyControllerImpl2 extends MyFancyControllerModell{
#Autowired
public MyControllerImpl2(#Qualifier("myServiceInstance2") MyService service){
super(service);
}
}
And the Service:
public class MyService{
private MyDao myDao;
public MyService(MyDao myDao){
this.myDao = myDao;
}
#Transactional
public void editSomeData(String data){...}
}
I create the Beans in my configuration class like this:
private DataSource lookupDataSource(final String jndiName) {
final JndiDataSourceLookup dsLookup = new JndiDataSourceLookup();
dsLookup.setResourceRef(true);
return dsLookup.getDataSource(jndiName);
}
#Bean
public DataSource dataSource1(){
return lookUpDataSource("dataSource1");
}
#Bean
public DataSource dataSource2(){
return lookUpDataSource("dataSource2");
}
#Bean
public MyService myServiceInstance1(#Qualifier("dataSource1") DataSource dataSource){
return new(MyService(new MyDao(dataSource))));
}
#Bean
public MyService myServiceInstance1(#Qualifier("dataSource1") DataSource dataSource){
return new(MyService(new MyDao(dataSource)));
}
My question is, is it possible to create transactionmanagers for both datasources without the need to declare in the service layer which transactionmanager is used?
I tried creating them as bean just like the services but that did not work.
Check out here the answers for previous quesion related to transaction manager...
https://stackoverflow.com/a/1961853/7504001