I'm trying to disable a Zuul route to a microservice registered with Eureka at runtime (I'm using spring boot).
This is an example:
localhost/hello
localhost/world
Those two are the registered microservices. I would like to disable the route to one of them at runtime without shutting it down.
Is there a way to do this?
Thank you,
Nano
Alternatively to using Cloud Config, custom ZuulFilter can be used. Something like (partial implementation to show the concept):
public class BlackListFilter extends ZuulFilter {
#Override
public String filterType() {
return "pre";
}
...
#Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String uri = ctx.getRequest().getRequestURI();
String appId = uri.split("/")[1];
if (blackList.contains(appId)) {
ctx.setSendZuulResponse(false);
LOG.info("Request '{}' from {}:{} is blocked",
uri, ctx.getRequest().getRemoteHost(), ctx.getRequest().getRemotePort());
}
return null;
}
}
where blackList contains list of application IDs (Spring Boot application name) managed for example via some RESTful API.
After a lot of efforts I came up with this solution. First, I used Netflix Archaius to watch a property file. Then I proceeded as follows:
public class ApplicationRouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
public ApplicationRouteLocator(String servletPath, ZuulProperties properties) {
super(servletPath, properties );
}
#Override
public void refresh() {
doRefresh();
}
}
Made the doRefresh() method public by extending SimpleRouteLocator and calling its method in the overridden one of the interface RefreshableRouteLocator.
Then I redefined the bean RouteLocator with my custom implementation:
#Configuration
#EnableConfigurationProperties( { ZuulProperties.class } )
public class ZuulConfig {
public static ApplicationRouteLocator simpleRouteLocator;
#Autowired
private ZuulProperties zuulProperties;
#Autowired
private ServerProperties server;
#Bean
#Primary
public RouteLocator routeLocator() {
logger.info( "zuulProperties are: {}", zuulProperties );
simpleRouteLocator = new ApplicationRouteLocator( this.server.getServletPrefix(),
this.zuulProperties );
ConfigurationManager.getConfigInstance().addConfigurationListener( configurationListener );
return simpleRouteLocator;
}
private ConfigurationListener configurationListener =
new ConfigurationListener() {
#Override
public void configurationChanged( ConfigurationEvent ce ) {
// zuulProperties.getRoutes() do something
// zuulProperties.getIgnoredPatterns() do something
simpleRouteLocator.refresh();
}
}
}
Every time a property in the file was modified an event was triggered and the ConfigurationEvent was able to deal with it (getPropertyName() and getPropertyValue() to extract data from the event). Since I also Autowired the ZuulProperties I was able to get access to it. With the right rule I could find whether the property of Zuul
zuul.ignoredPatterns
was modified changing its value in the ZuulProperties accordingly.
Here refresh context should work (as long as you are not adding a new routing rule or removing a currently existing one), if you are adding or removing routing rules, you have to add a new bean for ZuulProperties and mark it with #RefreshScope, #Primary.
You can autowire refreshEndpoint bean for example and apply refreshEndpoint.refresh() on the listener.
Marking a custom RouteLocator as primary will cause problems as zuul already has bean of same type marked as primary.
Related
I have tried to find documentation on how to manually configure a RestController (i.e in a Configuation class). That means without using the RestController annotation. Considering all the other annotations, like mapping, pathvariables etc. is at all possible?
A controller is essentially a component with a request mapping. See RequestMappingHandlerMapping.
#Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
If you want to instantiate a "rest controller" through configuration, you can probably do so via the following:
#Configuration
public class MyConfiguration {
#Bean
public MyController() {
return new MyController();
}
}
#ResponseBody
public class MyController {
#RequestMapping("/test")
public String someEndpoint() {
return "some payload";
}
}
But I don't think you'll be able to configure the request mappings (path variables, etc) in the configuration though; at least I haven't seen an example nor figured out how.
I am trying to autowire a component into a custom JsonDeserializer but cannot get it right even with the following suggestions I found:
Autowiring in JsonDeserializer: SpringBeanAutowiringSupport vs HandlerInstantiator
Right way to write JSON deserializer in Spring or extend it
How to customise the Jackson JSON mapper implicitly used by Spring Boot?
Spring Boot Autowiring of JsonDeserializer in Integration test
My final goal is to accept URLs to resources in different microservices and store only the ID of the resource locally. But I don't want to just extract the ID from the URL but also verify that the rest of the URL is correct.
I have tried many things and lost track a bit of what I tried but I believe I tried everything mentioned in the links above. I created tons of beans for SpringHandlerInstantiator, Jackson2ObjectMapperBuilder, MappingJackson2HttpMessageConverter, RestTemplate and others and also tried with setting the SpringHandlerInstantiator in RepositoryRestConfigurer#configureJacksonObjectMapper.
I am using Spring Boot 2.1.6.RELEASE which makes me think something might have changed since some of the linked threads are quite old.
Here's my last attempt:
#Configuration
public class JacksonConfig {
#Bean
public HandlerInstantiator handlerInstantiator(ApplicationContext applicationContext) {
return new SpringHandlerInstantiator(applicationContext.getAutowireCapableBeanFactory());
}
}
#Configuration
public class RestConfiguration implements RepositoryRestConfigurer {
#Autowired
private Validator validator;
#Autowired
private HandlerInstantiator handlerInstantiator;
#Override
public void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) {
validatingListener.addValidator("beforeCreate", validator);
validatingListener.addValidator("beforeSave", validator);
}
#Override
public void configureJacksonObjectMapper(ObjectMapper objectMapper) {
objectMapper.setHandlerInstantiator(handlerInstantiator);
}
}
#Component
public class RestResourceURLSerializer extends JsonDeserializer<Long> {
#Autowired
private MyConfig config;
#Override
public Long deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
ServiceConfig serviceConfig = config.getServices().get("identity");
URI serviceUri = serviceConfig.getExternalUrl();
String servicePath = serviceUri.getPath();
URL givenUrl = p.readValueAs(URL.class);
String givenPath = givenUrl.getPath();
if (servicePath.equals(givenPath)) {
return Long.parseLong(givenPath.substring(givenPath.lastIndexOf('/') + 1));
}
return null;
}
}
I keep getting a NullPointerException POSTing something to the API endpoint that is deserialized with the JsonDeserializer above.
I was able to solve a similar problem by marking my deserializer constructor accept a parameter (and therefore removing the empty constructor) and marking constructor as #Autowired.
public class MyDeserializer extends JsonDeserializer<MyEntity> {
private final MyBean bean;
// no default constructor
#Autowired
public MyDeserializer(MyBean bean){
this.bean = bean
}
...
}
#JsonDeserialize(using = MyDeserializer.class)
public class MyEntity{...}
My entity is marked with annotation #JsonDeserialize so I don't have to explicitly register it with ObjectMapper.
My goal is to add some additional logic around the System Injection Resolver (to decorate it, so to speak). Basically, I want to register a custom injection resolver (e.g. described here), but for the javax.inject.#Inject Annotation. The setup works if I create a different custom annotation, but not when using #Inject. My implementation is as follows:
Injection Resolver:
#Singleton
#Rank(Integer.MAX_VALUE)
public class InjectInjectionResolver
implements InjectionResolver<Inject> {
private final InjectionResolver<Inject> injectionResolver;
#Inject
public InjectInjectionResolver(
#Named(InjectionResolver.SYSTEM_RESOLVER_NAME) final InjectionResolver<Inject> injectionResolver) {
this.injectionResolver = injectionResolver;
}
#Override
public Object resolve(final Injectee injectee, final ServiceHandle<?> root) {
throw new RuntimeException("Why is this never called?");
}
#Override
public boolean isConstructorParameterIndicator() {
return injectionResolver.isConstructorParameterIndicator();
}
#Override
public boolean isMethodParameterIndicator() {
return injectionResolver.isMethodParameterIndicator();
}
}
And I register the resolver as follows (inside ResourceConfig):
register(new AbstractBinder() {
#Override
protected void configure() {
bind(InjectInjectionResolver.class).to(new GenericType<InjectionResolver<Inject>>() {}).in(Singleton.class);
}
});
What I tried so far:
using #Rank
using both the org.glassfish.jersey.internal.inject.AbstractBinder or org.glassfish.hk2.utilities.binding.AbstractBinder with TypeLiteral, along with implementing the org.glassfish.hk2.api.InjectionResolver or org.glassfish.jersey.internal.inject.InjectionResolver
making sure the same setup works with #CustomAnnotation (where both the constructor and the resolve methods are called)
Does anyone know how to solve this issue?
Versions used: jersey 2.27
EDIT: When I #Inject the InjectionResolver itself, it seems that hk2's org.jvnet.hk2.internal.ThreeThirtyResolver is injected for the org.glassfish.hk2.api.InjectionResolver, while my custom injection resolver is injected for org.glassfish.jersey.internal.inject.InjectionResolver. However, the custom injection resolver is not used for injection.
The following configuration works for me, thanks to #jwells131313 for bringing me back on track. I tried to use fully qualified names, else it's probably from the javax.inject.* package.
#Singleton
#Rank(Integer.MAX_VALUE)
#org.jvnet.hk2.annotations.Service
public class MyHk2InjectionResolver
implements org.glassfish.hk2.api.InjectionResolver<Inject> {
private final org.glassfish.hk2.api.InjectionResolver<Inject> injectionResolver;
#Inject
public MyHk2InjectionResolver(
#Named(org.glassfish.hk2.api.InjectionResolver.SYSTEM_RESOLVER_NAME) final org.glassfish.hk2.api.InjectionResolver<Inject> injectionResolver) {
this.injectionResolver = injectionResolver;
}
// ... required methods, could just delegate to system resolver
}
To register the resolver, we bind it to the correct type (I do it in the ResourceConfig of a jersey application):
register(new org.glassfish.hk2.utilities.binding.AbstractBinder() {
#Override
protected void configure() {
bind(MyHk2InjectionResolver.class)
.to(new org.glassfish.hk2.api.TypeLiteral<org.glassfish.hk2.api.InjectionResolver<Inject>>() {})
.in(Singleton.class);
}
});
With this setup, you can keep using the #Inject annotation and write some custom injection logic around it, possibly using the default system injection resolver as a fallback.
In a spring based project I am working on, there's a layer of functionality for calling web service. For each web service operation, a method is created with almost same code but with some different, operation specific, information(e.g. service name, operation name, namespaces, etc).
I am replacing this layer with interfaces and annotated methods. For example, below code is provided for operation "fetchBar" of web service("foo").
package a.b.c.webservices;
#WebService(service="foo", namespace="...")
public interface FooWebService {
#WebServiceOperation(operation="fetchBar")
BarRespons fetchBar(BarRequest request) throws WebServiceException;
}
Now I want, with some mechanism, spring allow me to create dynamic proxy beans from some specified package(s) and I can use following code to call web service.
package a.b.c.business;
import a.b.c.webservices.FooWebService;
public class FooBusiness {
#Autowired
FooWebService fooWebService;
public Bar getBar() {
Bar bar = null;
BarRequest request;
//create request
BarResponse response = fooWebService.fetchBar(request);
//extrac bar from response
return bar;
}
}
To achieve this I have created dynamic beans instances using java.lang.reflect.Proxy.newProxyInstance by providing it implementation of InvocationHandler. But Autowiring doesn't work in provided implementation of invocationHandler and in its further dependencies.
I tried following ways to achieve this.
Implemented BeanFactoryPostProcessor.postProcessBeanFactory and registered beans using ConfigurableListableBeanFactory.registerSingleton method.
Implemented ImportBeanDefinitionRegistrar.registerBeanDefinitions and tried to use BeanDefinitionRegistry.registerBeanDefinition but I am confused how to provide correct Bean definition that supports Autowiring.
Can any one tell me what is missing? Please guide me if I am not going in right direction.
Here's how I implemented all the functionality that creates beans of 'WebService' annotated interfaces and also supports Autowiring inside proxy implementation. (package declaration and import statements are omitted in below code)
First of all I created WebService and WebServiceOperation annotation.
WebService Annotation
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface WebService {
String service();
String namespace();
}
WebService Operation Annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface WebServiceOperation {
String operation();
}
Next step is to scan all WebService annotated interfaces from specified packages. Spring provides ClassPathScanningCandidateComponentProvider for package scanning but it does not detect interfaces. Please see this question and it's answer for more details. So I extended ClassPathScanningCandidateComponentProvider and overrode isCandidateComponent method.
ClassPathScanner
public class ClassPathScanner extends ClassPathScanningCandidateComponentProvider {
public ClassPathScanner(final boolean useDefaultFilters) {
super(useDefaultFilters);
}
#Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isIndependent();
}
}
At this point I created EnableWebServices annotation to enable web services and to provide web service packages that contain WebService annotated interfaces.
EnableWebServices Annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Import({
WebServiceProxyConfig.class,
WebServiceProxyBeansRegistrar.class
})
public #interface EnableWebServices {
#AliasFor("basePackages")
String[] value() default {};
#AliasFor("value")
String[] basePackages() default {};
}
This annotation can be applied to some Configuration annotated class with packages to scan interfaces, as below.
#EnableWebServices({
"a.b.c.webservices",
"x.y.z.webservices"
})
It's time to think about dynamic proxy creation that will invoke actual web service from information given in WebService and WebServiceOperation annotations. Java provides a mechanism to create dynamic proxy which requires to provide implementation of InvocationHandler interface and provide logic in its invoke method. I named this implementaiton as WebServiceProxy
Suppose a bean of type 'TheWebServiceCaller' contains all nasty logic to call a web service. I just have inject it and to invoke it's call method with a TheWebServiceInfo (extracted from WebService and WebServiceOperation annotations) and request object.
TheWebServiceInfo(Suppose all fields have getters and setters)
public class TheWebServiceInfo {
private String service;
private String namespace;
private String operation;
}
WebServiceProxy
public class WebServiceProxy implements InvocationHandler {
#Autowired
private TheWebServiceCaller caller;
#Override
public Object invoke(Object target, Method method, Object[] args) throws Exception {
Object request = (null != args && args.length > 0) ? args[0] : null;
WebService webService = method.getDeclaringClass().getAnnotation(WebService.class);
WebServiceOperation webServiceOperation = method.getAnnotation(WebServiceOperation.class);
TheWebServiceInfo theInfo = createTheWebServiceInfo(webService, webServiceOperation);
return caller.call(theInfo, request);
}
private TheWebServiceInfo createTheWebServiceInfo(WebService webService, WebServiceOperation webServiceOperation) {
TheWebServiceInfo theInfo = new TheWebServiceInfo();
theInfo.setService(webService.service());
theInfo.setNamespace(webService.namespace());
theInfo.setOperation(webServiceOperation.operation());
return theInfo;
}
}
Implementaion of InvocationHandler is passed to Proxy.newProxyInstance (along with some other information) to create proxy objects. I need separat proxy objectes for each WebService annotated interface. I will now create a factory to proxy instances creation and name is as 'WebServiceProxyBeanFactory'. Instances created by this factory will become beans for corresponding WebService annotated interfaces.
A bit later, I will expose 'WebServiceProxy' and WebServiceProxyBeanFactory as beans. In 'WebServiceProxyBeanFactory', I will inject WebServiceProxy and used it. Please note that createWebServiceProxyBean uses generics. This is important.
WebServiceProxyBeanFactory
public class WebServiceProxyBeanFactory {
#Autowired
WebServiceProxy webServiceProxy;
#SuppressWarnings("unchecked")
public <WS> WS createWebServiceProxyBean(ClassLoader classLoader, Class<WS> clazz) {
return (WS) Proxy.newProxyInstance(classLoader, new Class[] {clazz}, webServiceProxy);
}
}
If you remember, earlier I have imported WebServiceProxyConfig in EnableWebServices annotations. WebServiceProxyConfig is used to expose WebServiceProxy and WebServiceProxyBeanFactory as beans.
WebServiceProxyConfig
#Configuration
public class WebServiceProxyConfig {
#Bean
public WebServiceProxy webServiceProxy() {
return new WebServiceProxy();
}
#Bean(name = "webServiceProxyBeanFactory")
public WebServiceProxyBeanFactory webServiceProxyBeanFactory() {
return new WebServiceProxyBeanFactory();
}
}
Now everything is in place. it's time to write a hook to start scanning Web service packages and register dynamic proxies as beans. I will provide implementation of ImportBeanDefinitionRegistrar.
WebServiceProxyBeansRegistrar
#Configuration
public class WebServiceProxyBeansRegistrar implements ImportBeanDefinitionRegistrar, BeanClassLoaderAware {
private ClassPathScanner classpathScanner;
private ClassLoader classLoader;
public WebServiceProxyBeansRegistrar() {
classpathScanner = new ClassPathScanner(false);
classpathScanner.addIncludeFilter(new AnnotationTypeFilter(WebService.class));
}
#Override
public void setBeanClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
#Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
String[] basePackages = getBasePackages(importingClassMetadata);
if (ArrayUtils.isNotEmpty(basePackages)) {
for (String basePackage : basePackages) {
createWebServicProxies(basePackage, registry);
}
}
}
private String[] getBasePackages(AnnotationMetadata importingClassMetadata) {
String[] basePackages = null;
MultiValueMap<String, Object> allAnnotationAttributes =
importingClassMetadata.getAllAnnotationAttributes(EnableWebServices.class.getName());
if (MapUtils.isNotEmpty(allAnnotationAttributes)) {
basePackages = (String[]) allAnnotationAttributes.getFirst("basePackages");
}
return basePackages;
}
private void createWebServicProxies(String basePackage, BeanDefinitionRegistry registry) {
try {
for (BeanDefinition beanDefinition : classpathScanner.findCandidateComponents(basePackage)) {
Class<?> clazz = Class.forName(beanDefinition.getBeanClassName());
WebService webService = clazz.getAnnotation(WebService.class);
String beanName = StringUtils.isNotEmpty(webService.bean())
? webService.bean() : ClassUtils.getShortNameAsProperty(clazz);
GenericBeanDefinition proxyBeanDefinition = new GenericBeanDefinition();
proxyBeanDefinition.setBeanClass(clazz);
ConstructorArgumentValues args = new ConstructorArgumentValues();
args.addGenericArgumentValue(classLoader);
args.addGenericArgumentValue(clazz);
proxyBeanDefinition.setConstructorArgumentValues(args);
proxyBeanDefinition.setFactoryBeanName("webServiceProxyBeanFactory");
proxyBeanDefinition.setFactoryMethodName("createWebServiceProxyBean");
registry.registerBeanDefinition(beanName, proxyBeanDefinition);
}
} catch (Exception e) {
System.out.println("Exception while createing proxy");
e.printStackTrace();
}
}
}
In this class, I extracted all packages provided in EnableWebServices annotation. for each extracted package, I used ClassPathScanner to scan. (Here logic can be refined to filter only WebService annotated interfaces). For each detected interface, I have registered a bean definitions. Please note I have used webServiceProxyBeanFactory and called its createWebServiceProxyBean with classLoader and type of interface. This factory method, when invoked by spring later, will return bean of same type as that of interface, so bean with correct type is registered. This bean can be injected anywhere with interface type. Moreover, WebServiceProxy can inject and use any other bean. So autowiring will also work as expected.
Is your InvocationHandler a bean? You should create it as a bean, not just a simple object to get Autowired working
I was thinking about the same problem but in a slightly more lightweight context. I don't need to load dynamicaly all the webservice clients. So instead I used a FactoryBean and within this factory bean I constructed the dynamic proxy. Here is one example where Autowiring of the service works:
public class CurrencyServiceWithDynamicProxy extends AbstractFactoryBean<CurrencyService> {
ServiceClientConfiguration clientConfiguration;
Object proxy;
#Autowired
public CurrencySyncFactoryDynamicProxy(ServiceClientConfigurationProvider serviceClientConfigurationProvider) {
this.clientConfiguration = serviceClientConfigurationProvider.createClientConfig("currency");
proxy = Proxy.newProxyInstance(getClass().getClassLoader(), new Class<?>[] { getObjectType() }, new MyInvocationHandler());
}
#Override
public Class<CurrencySync> getObjectType() {
// TODO Auto-generated method stub
return CurrencyService.class;
}
#Override
public CurrencySync createInstance() throws Exception {
// do some creational logic
return (CurrencySync)proxy;
}
public CurrencySync createService() {
JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
factory.setServiceClass(getObjectType());
factory.getFeatures().add(som features);
return getObjectType().cast(factory.create());
}
}
With respect of the accepted answer this factory example can easily be extended into a more dynamic version.
I am using a spring boot application with #EnableZuulProxy annotation. But I would like to add custom routes during runtime. How is this possible?
Existing documentation only shows static examples, in which routes are defined in the application.yml. Could you point me to code snippets of my use case.
In the ZuulConfiguration I found a possibility to add routes routeLocator().getRoutes().add(route); but they are not applied to the runtime. What am I missing?
Thanks a lot. Cheers
Gerardo
What I did was subclass the SimpleRouteLocator class with my own RouteLocator class. Here is sample of what I did:
public class RouteLocator extends SimpleRouteLocator implements RefreshableRouteLocator {
#Autowired
private ZuulHandlerMapping zuulHandlerMapping;
private Map<String, ZuulRoute> routes = new ConcurrentHashMap<>();
public RouteLocator(TaskExecutor executor, String servletPath, ZuulProperties properties) {
super(servletPath, properties);
executor.execute(new ServiceWatcher());
}
#Override
public Map<String, ZuulRoute> locateRoutes() {
return this.routes;
}
#Override void refresh() {
this.doRefresh();
}
private class ServiceWatcher implements Runnable {
#Override
public void run(){
// Add your routes to this.routes here.
ZuulRoute route1 = new ZuulRoute("/somePath", "http://someResourceUrl:8080");
ZuulRoute route2 = new ZuulRoute("/someOtherPath", "some-service-id");
routes.put("/somePath", route1);
routes.put("/someOtherPath", route2);
zuulHandlerMapping.setDirty(true);
}
}
}
I'm not exactly sure when the ServiceWatcher gets called since in my actual code the ServiceWatcher wraps around a Kubernetes Watcher (since I am running Zuul in an OpenShift environment), but this should provide the gist of how to get started.