How to expose endpoints REST lazily? - spring

Scenario:
The Spring Boot application should expose its REST endpoints
only when a specific action occurs.
Is there any way in Spring to lazily expose endpoints, or even the whole HTTP subsystem?
In Apache CXF, we can do something like this:
void exposeEndpoints() {
EndpointImpl endpoint = new EndpointImpl(cxfBus, serviceImpl);
endpoint.publish();
}
How to do the same thing in Spring?

You could take a look at #RefreshScope.
I would define my #RestController beans as follows:
#Configuration
#RefreshScope
public ControllerConfig {
#Bean
#ConditionalOnProperty(value = "should.initialize.rest", havingValue = true)
public SomeController someController(){
....
}
#Bean
#ConditionalOnProperty(value = "should.initialize.rest", havingValue = true)
public SomeOtherController someOtherController(){
....
}
}
and if you start your application with property should.initialize.rest with value false in your application.properties:
should.initialize.rest=false
Then your controllers won't be registered/initialized. When the application is running, you could then update your application.properties to:
should.initialize.rest=true
and make a call to /refresh, then your ApplicationContext will reload/refresh, this time with your REST controllers. You can find more about #RefreshScope below:
https://www.baeldung.com/spring-reloading-properties
https://andressanchezblog.wordpress.com/2016/09/15/refresh-scope-in-spring-cloud/

The solution I have provided below is one of the ways and it may or may not be suitable in your case.
It situation seems like more of design concern rather than particular implementation.
I would suggest you to go with little change in the design.
Store the event in DB or Session (as per requirement) as Boolean.
Create Aspect using AspectJ or Spring's own AOP.
Create Before aspect with specific package pointcuts.
In the #Before advice, check for the boolean flag, if the condition for publishing
satisfies than use joinpoint.proceed() else throw some kind of error saying service not available.
Another way is to create custom Annotation with Aspects. You can use that annotation as per requirement and not on the whole service layer.
The benefit of first approach is that you have the control at generic level and the second approach at service level.

I would suggest to create a new child context with your HTTP subsystem. Something like this:
#Service
public class MyBusinessService {
#Autowired
private final ApplicationContext parentContext;
private AnnotationConfigWebApplicationContext webContext;
public void myBusinessMethod() {
this.webContext = new AnnotationConfigWebApplicationContext();
this.webContext.setParent(parentContext);
this.webContext.scan("com.mybusiness.service.webcomponents");
this.webContext.refresh();
this.webContext.start();
}
}
DISCLAIMER: This is proof-of-concept code, I did not try to compile or run this. But hopefully it is enough to illustrate the concept.

Related

Spring Proxy Creation of Classes annotated with #Configuration or #Component

Spring uses either JDK dynamic proxies or CGLIB to create the proxy for a given target object. If a class is annotated with #Configuration, then CGLIB is used.
However, one limitation of Spring AOP is that once the call has finally reached the target object, any method calls that it may make on itself are going to be invoked against the this reference, and not the proxy. This piece of information is important to remember when using #Transactional and in other places as well.
So having that knowledge, in the code below, is Spring injecting the actual instance or the proxy of SimpleBean?
#Configuration
public class Config {
#Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
#Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean()); //<---
}
}
And what is the behavior if a class is annotation with #Component?
Let me give you another perspective.
Say there is an another bean AnotherBeanConsumer that also needs a simpleBean. Simple Bean has a Singleton scope:
#Configuration
public class Config {
#Bean
public SimpleBean simpleBean() {
return new SimpleBean();
}
#Bean
public SimpleBeanConsumer simpleBeanConsumer() {
return new SimpleBeanConsumer(simpleBean());
}
#Bean
public AnotherBeanConsumer anotherBeanConsumer() {
return new AnotherBeanConsumer(simpleBean());
}
}
Now the question is, how its possible that two calls to simpleBean() made from different methods simpleBeanConsumer and anotherBeanConsumer return the same instance of the simple bean (since its a singleton obviously)?
IMO (and disclaimer, I'm not affiliated with spring or something), This is the main reason of creating proxies that wrap Configurations.
Now indeed Spring AOP has a limitation of calling methods just as you've stated, however who said that spring under-the-hood uses spring AOP? The bytecode instrumentation done on much lower levels doesn't have a limitation like this. After all creating a proxy means: "create a proxy object that will have the same interface but will alter the behavior", right?
For example if you use CGLIB that uses inheritance you could create a proxy out of configuration that looks like this (schematically):
class CGLIB_GENERATED_PROXY extends Config {
private Map<String, Object> singletonBeans;
public SimpleBean simpleBean() {
String name = getNameFromMethodNameMaybePrecached();
if(singletonBeans.get(name) != null) {
return singletonBeans.get(name);
}
else {
SimpleBean bean = super.simpleBean();
singletonBeans.put(name, bean);
return bean;
}
}
....
}
Of course its only a schematic picture, in real life there is an application context that basically provides the access to the map like this, but you get the point.
If its not enough, then there are some even more sophisticated frameworks that spring must make use of in order to load a configuration (like ASM)...
Here is an example:
If you use #ConditionalOnClass(A.class) and the class doesn't really exist in runtime, how spring can load the bytecode of the configuration that uses this configuration and not fail on something like NoClassDefFoundException?
My point is that it goes far beyond the spring AOP, and has its quirks :)
Having said that, nothing that I've describe above requires the real components to be always wrapped in Proxies of any kind. So in the most trivial case, when SimpleBean does not by itself have some annotations that require proxy generation (stuff like #Cached, #Transactional and so forth), Spring won't wrap the object of that type and you'll get a plain SimpleBean object.

How to Inject custom method argument in Spring WebFlux using HandlerMethodArgumentResolver?

I want to create an custom method argument Resolver using Spring WebFlux. I am following link but its seem to be not working.
I am able to create the custom argument resolver using WebMvc.
import org.springframework.web.reactive.result.method.HandlerMethodArgumentResolver;
public class MyContextArgumentResolver implements HandlerMethodArgumentResolver {
#Override
public boolean supportsParameter(MethodParameter parameter) {
return MyCustomeObject.class.isAssignableFrom(parameter.getParameterType())
}
#Override
public Mono<Object> resolveArgument(MethodParameter parameter, BindingContext bindingContext,
ServerWebExchange exchange) {
.....
return Mono.just(new MyCustomeObject())
}
Please note that i am using HandlerMethodArgumentResolver from .web.reactive. package.
My AutoConfiguration file look like
#Configuration
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the class-path
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)//checks that the app is a reactive web-app
public class RandomWebFluxConfig implements WebFluxConfigurer {
#Override
public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
MyContextArgumentResolver[] myContextArgumentResolverArray = {contextArgumentResolver()};
configurer.addCustomResolver(myContextArgumentResolverArray );
}
#Bean
public MyContextArgumentResolver contextArgumentResolver() {
return new MyContextArgumentResolver ();
}
My spring.factories looks like
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.XXXX.XXX.XXX.RandomWebFluxConfig
Please note that above configuration is part of the jar which is added in Spring WebFlux Boot project enabled using #EnableWebFlux .
It seems you're conflating two different problems here.
First, you should make sure that your method argument resolver works in a regular project.
For that, you need a #Configuration class that implements the relevant method in WebFluxConfigurer. Your code snippet is doing that but with two flaws:
Your configuration is using #EnableWebFlux, which is disabling the WebFlux auto-configuration in Spring Boot. You should remove that
it seems you're trying to cast a list of MethodArgumentResolver into a single instance and that's probably why things aren't working here. I believe your code snippet could be just:
configurer.addCustomResolver(contextArgumentResolver());
Now the second part of this question is about setting this up as a Spring Boot auto-configuration. I guess that you'd like WebFlux applications to automatically get that custom argument resolvers if they depend on your library.
If you want to achieve that, you should first make sure to read up a bit about auto-configurations in the reference documentation. After that, you'll realize that your configuration class is not really an auto-configuration since it will be applied in all cases.
You should probably add a few conditions on that configuration like:
#ConditionalOnClass(EnableWebFlux.class) // checks that WebFlux is on the classpath
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE) // checks that the app is a reactive web app

How to tie annotations to spring properties?

In my Spring boot application I have around 30 controller classes. Each class has a #CrossOrigin annotation. When I work on it in eclipse it’s fine, but when I deploy to production I need to remove them.
So I was thinking to create a custom property in application.properties and somehow tie it to CrossOrigin annotations. So I can set property my-annotation=false and this will cancel the CrossOrigin annotations everywhere in the application. I tried looking into reflection but couldn’t figure out how to do it.
How can I make this work?
I'm afraid, spring doesn't work this way - once you've put an annotation its there for all controllers.
Technically this annotation is used somewhere deep inside in spring MVC (org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#initCorsConfiguration) so its not advisable to fiddle with it.
Probably it's possible to override the beans of this type by custom implementation and putting them into some configuration that will take place in production only and will not be loaded by default. But again, this is too "internal" solution.
Instead I suggest creating a global cors configuration outside the controller, so no #CrossOrigin annotation will stay in controllers.
So the first step would be defining a WebConfigurerAdapter:
#Configuration
class MyGlobalCorsConfiguration {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting").allowedOrigins("http://localhost:9000");
}
};
}
}
The second step is to apply this configuration only upon some certain conditions:
It's possible to do with #ConditionalOnProperties annotation that can be put on the whole configuration or one single bean:
#ConditionalOnProperty(value = "myproject.cors.enabled", havingValue = "true",
matchIfMissing = false)
#Configuration
class MyGlobalCorsConfiguration {
....
}
Now during the third step you should put the property myproject.cors.enabled=true into the application properties that gets loaded only in production environment or something

CamelTestSupport with routes using classes with #Autowired

After help from experts over at question Camel unit test with cametestsupport, template is always null, I ended up with one more issue.
Below is my test class - a simple test that tests a route which has only ValidationProcessor POJO.
public class RouteTests extends CamelTestSupport {
#Override
protected RouteBuilder createRouteBuilder() {
return new RouteBuilder() {
#Override
public void configure() {
from("direct:start")
.filter().method(ValidationProcessor.class, "validate")
.to("mock:result");
}
};
}
#Test
public void testSendMatchingMessage() throws Exception {
ObjectMapper objectmapper = new ObjectMapper();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
InputStream stream = loader.getResourceAsStream("test.json");
JSONObject testJson = new JSONObject(objectmapper.readValue(stream, Map.class));
MockEndpoint resultEndpoint = getMockEndpoint("mock:result");
resultEndpoint.expectedMessageCount(1);
template.sendBody("direct:start", testJson);
resultEndpoint.assertIsSatisfied();
}
}
The problem comes when this ValidationProcessor has an #Autowired component in it. My validation method needs data from Elasticsearch and hence I have an #Autowired for an elastic client.
When I run mvn clean test, I am getting a NullPointerException stating that this elastic client is null. I think the issue is that this test is devoid of anything to do with Spring and hence the issue, but I do not know how to overcome this.
My test needs to fetch data from Elasticsearch and hence the ValidationProcessor POJO does need #Autowired.
When you extend CamelTestSupport then it's not a Spring application. You need to extend CamelSpringTestSupport. That would create Camel in a Spring runtime, and then allow beans to have IoC via Spring. This kind of testing is often used with Camel XML routes where the routes are defined in XML files. However, you can have a plain XML file and refer to routes in Java DSL as well.
However, as Makoto answers, then vanilla Spring unit testing is of late often about using all those gazillion annotations. Camel has support for that as well as his answer shows. This is also how for example Spring Boot testing is done, etc.
You can find some unit tests in camel-test-spring you can use as inspiration as well.
What I've discovered is that it's unwise to extend CamelTestSupport when you want to use anything with Spring. In fact, there's a better way to do it - use the CamelSpringBootRunner instead.
Well...I say "better". You're going to find yourself attaching a ton of annotations. Of the things you'll need:
A boostrapper to ensure that you're bootstrapping Camel correctly
The routes you wish to add to the classpath (and all of the beans); this ensures that they get added to Camel's registry through Spring's registry
You have to dirty the context after every run or your tests get into a wonky state.
You can automock endpoints by specifying either #MockEndpoints or #MockEndpointsAndSkip. The former sends the data along to the actual route.
Below is just a start. Many of these annotations and their documentation can be found in the formal docs.
#RunWith(CamelSpringBootRunner.class)
#BootstrapWith(SpringBootTestContextBootstrapper.class)
#ActiveProfiles("test")
#SpringBootTest(classes = { YourEntryPointClass.class })
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
#UseAdviceWith
#MockEndpoints("direct:*")
public class RouteTests {
}

Spring AOP: advice is not triggered

Trying to design simple aspect,that will print word "logg" to console,when any of public methods executed.
aspect:
#Aspect
public class LoggingAspect {
#Pointcut("execution(public * *(..))")
public void publicServices() {
};
#Before("publicServices()")
public void logg() {
System.out.println("logg");
}
}
xml config:
<context:component-scan base-package="aspectlogging" />
<aop:aspectj-autoproxy/>
<bean id="loggingAspectHolder" class="aspectlogging.LoggingAspect"/>
simple bean:
package aspectlogging;
#Component
public class TestableBean {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
test:
public class TestLogging {
public static void main(String[] args) {
TestableBean tb = new TestableBean();
tb.setName("yes");
tb.getName();
}
}
I expect,that result of running of TestLogging will be "logg" word in console,and no output returned.
Do I understand AOP correctly in this case?
With #Around advice, you need to have a ProceedingJoinPoint pjp argument to the advising method and to call pjp.proceed() at the point in the advisor when you want the wrapped method to be called. It's easier to use #Before advice really, when what you've done will otherwise work just fine.
[EDIT]: Also, you must let Spring construct your beans for you instead of directly calling new. This is because the bean object is actually a proxy for your real object (which sits inside it). Because your target object doesn't implement an interface, you will need to have the cglib library on your classpath in addition to the Spring libraries. (Alternatively, you can go with using AspectJ fully, but that requires using a different compiler configuration.)
To create your beans, you first need to create a Spring context and then query that for the bean instance. This means you change from:
TestableBean tb = new TestableBean();
To (assuming you're using Spring 3, and that your XML config is in "config.xml" somewhere on your classpath):
ApplicationContext context = new ClassPathXmlApplicationContext("config.xml");
TestableBean tb = context.getBean(TestableBean.class);
The rest of your code remains the same (after adjusting for import statements and possibly additional dependencies).
Not quite sure on this one, but maybe you need to use a spring managed TestableBean to have spring AOP pick up the method call.
edit: of course, you can't use #Around the way that you provided - but this subject has been addressed by another answer, so it's omitted here.
edit2: If you need help on how to get a spring managed bean, please feel free to ask. but since you already got your aspect bean set up, I believe you can handle this :)
edit3: Hehe. Ok.. maybe not :)
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
will load your application context.
Load beans from there by calling:
TestableBean testableBean = (TestableBean )ctx.getBean("testableBean ");
Define the TestableBean just like you did with your Aspect bean.
edit4: Now I'm pretty sure that the fault is the non-spring managed bean.
Use the simplest thing that can work. Spring AOP is simpler than using full AspectJ as there is no requirement to introduce the AspectJ compiler / weaver into your development and build processes. If you only need to advise the execution of operations on Spring beans, then Spring AOP is the right choice. If you need to advise domain objects, or any other object not managed by the Spring container, then you will need to use AspectJ.
Taken from: http://static.springsource.org/spring/docs/2.0.x/reference/aop.html

Resources