SpringCloud源码剖析-Zuul的自动配置和核心Filter详解

简介: EnableZuulProxy的注释告诉我们,这里设设置Zuul服务器端点,和安装了一些反向代理过滤器,通过这些过滤器它可以转发请求到后端服务器,可以通过配置或通过DiscoveryClient手动注册后端服务(服务发现)

前言

在上一章节我们大致了解了Zuul的Filter的执行流程和核心的Filter,这一章节我们消息分析一下Zuul的自动配置,以及每个Filter的实现细节,这需要你有一定的耐心


一.Zuul的自动配置

1.EnableZuulProxy 开启zuul服务

我们在使用Zuul的时候需要在启动类贴上@EnableZuulProxy注解,我们就从这个注解入手分析Zuul,首先打开注解的源码看一下

/*** Sets up a Zuul server endpoint and installs some reverse proxy filters in it, so it can* forward requests to backend servers. The backends can be registered manually through* configuration or via DiscoveryClient.** @see EnableZuulServer for how to get a Zuul server without any proxying*/@EnableCircuitBreaker@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyMarkerConfiguration.class)
public@interfaceEnableZuulProxy {
}

EnableZuulProxy的注释告诉我们,这里设设置Zuul服务器端点,和安装了一些反向代理过滤器,通过这些过滤器它可以转发请求到后端服务器,可以通过配置或通过DiscoveryClient手动注册后端服务(服务发现)

不过这里@Import 导入了一个配类 ZuulProxyMarkerConfiguration,这个配置在干嘛呢?看一下源码

/**负责添加标记bean以触发 {@link ZuulProxyAutoConfiguration}的激活* Responsible for adding in a marker bean to trigger activation of * {@link ZuulProxyAutoConfiguration}** @author Biju Kunjummen*/@ConfigurationpublicclassZuulProxyMarkerConfiguration {
@BeanpublicMarkerzuulProxyMarkerBean() {
returnnewMarker();
    }
classMarker {
    }
}

翻译:“Responsible for adding in a marker bean to trigger activation of

  • {@link ZuulProxyAutoConfiguration}”
    这个类的作用就是用来激活 ZuulProxyAutoConfiguration Zuul的自动配置的

它在负责添加标记bean以触发激活 ZuulProxyAutoConfiguration 这个类,研究过springboot自动配置的同学就会知道 ,SpringBoot 中会有大量的 xxxAutoConfiguration 自动配置的类会在应用启动的过程中被激活实现自动装配,从而节省了我们很多的配置

2.Zuul自动配置ZuulProxyAutoConfiguration

ZuulProxyAutoConfiguration是zuul比较核心的一个自动配置类,很多的组件都是在这个类中注册的,该自动配置类在 spring-cloud-netflix-zuul.xx.RELEASE 包的spring.factories文件中有定义,随着SpringBoot启动该配置类被注册到Spring容器中(不理解的可以先了解一下SpringBoot自动配置)

自动配置类 ZuulProxyAutoConfiguration 需要通过ZuulProxyMarkerConfiguration 来激活才可能被注册到容器中,下面是它的源码

@Configuration//这里引入了几种客户端配置@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
//注册Bean的条件,必须有 ZuulProxyMarkerConfiguration.Marker 的实例@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
publicclassZuulProxyAutoConfigurationextendsZuulServerAutoConfiguration {
...省略...

3.Zuul自动配置ZuulServerAutoConfiguration

ZuulProxyAutoConfiguration继承了ZuulServerAutoConfiguration ,我们先跟一下它父类

/*** @author Spencer Gibb* @author Dave Syer* @author Biju Kunjummen*/@Configuration//开启配置:ZuulProperties ,这里面是以 zuul 打头的配置@EnableConfigurationProperties({ ZuulProperties.class })
@ConditionalOnClass(ZuulServlet.class)
//必须满足有 ZuulServerMarkerConfiguration.Marker 的实例才能被注册@ConditionalOnBean(ZuulServerMarkerConfiguration.Marker.class)
// Make sure to get the ServerProperties from the same place as a normal web app would// FIXME @Import(ServerPropertiesAutoConfiguration.class)publicclassZuulServerAutoConfiguration {
//绑定zuul的配置信息,这里面是以 zuul 打头的配置@AutowiredprotectedZuulPropertieszuulProperties;
//以server打头的服务器配置@AutowiredprotectedServerPropertiesserver;
//注入请求错误的控制器,主要返回错误页面的路径@Autowired(required=false)
privateErrorControllererrorController;
@BeanpublicHasFeatureszuulFeature() {
returnHasFeatures.namedFeature("Zuul (Simple)", ZuulServerAutoConfiguration.class);
    }
//RouteLocator that composes multiple RouteLocators. ://多路由组合定位器@Bean@PrimarypublicCompositeRouteLocatorprimaryRouteLocator(
Collection<RouteLocator>routeLocators) {
returnnewCompositeRouteLocator(routeLocators);
    }
//简单的路由定位器@Bean@ConditionalOnMissingBean(SimpleRouteLocator.class)
publicSimpleRouteLocatorsimpleRouteLocator() {
returnnewSimpleRouteLocator(this.server.getServlet().getServletPrefix(),
this.zuulProperties);
    }
//Zuul的请求入口控制器@BeanpublicZuulControllerzuulController() {
returnnewZuulController();
    }
// MVC HandlerMapping that maps incoming request paths to remote services.//他是做请求路径和远程服务的映射的,是 HandlerMapping的实现@BeanpublicZuulHandlerMappingzuulHandlerMapping(RouteLocatorroutes) {
ZuulHandlerMappingmapping=newZuulHandlerMapping(routes, zuulController());
mapping.setErrorController(this.errorController);
returnmapping;
    }
//定义ZuulRefreshListener  zuul刷新的监听器@BeanpublicApplicationListener<ApplicationEvent>zuulRefreshRoutesListener() {
returnnewZuulRefreshListener();
    }
// Core Zuul servlet which intializes and orchestrates zuulFilter execution//这里在注册ZuulServlet 这样的一个servlet, 这个东西了不得了,//他是负责核心ZuulServlet初始化和调用zuulFilter执行,跟DispatcherServlet差不多@Bean@ConditionalOnMissingBean(name="zuulServlet")
publicServletRegistrationBeanzuulServlet() {
ServletRegistrationBean<ZuulServlet>servlet=newServletRegistrationBean<>(newZuulServlet(),
this.zuulProperties.getServletPattern());
// The whole point of exposing this servlet is to provide a route that doesn't// buffer requests.servlet.addInitParameter("buffer-requests", "false");
returnservlet;
    }
// pre filters  : //在pre过滤器中,它是第一个执行的过滤器,检测请求是用 DispatcherServlet还是 ZuulServlet,将结果设置到RequestContext中@BeanpublicServletDetectionFilterservletDetectionFilter() {
returnnewServletDetectionFilter();
    }
//前置过滤器,用于解析表单数据并将其重新编码,只是针对于表单数据,即:content-type中必须带有“application/x-www-form-urlencoded”或“multipart/form-data”@BeanpublicFormBodyWrapperFilterformBodyWrapperFilter() {
returnnewFormBodyWrapperFilter();
    }
//设置请求过程是否开启debug,将当前请求上下文中的debugRouting和debugRequest参数设置为true@BeanpublicDebugFilterdebugFilter() {
returnnewDebugFilter();
    }
//主要是将原始请求进行包装,将原始的HttpServletRequest请求包装成Servlet30RequestWrapper类型@BeanpublicServlet30WrapperFilterservlet30WrapperFilter() {
returnnewServlet30WrapperFilter();
    }
// post filters//下面是定义一系列的后置过滤器,SendResponseFilter是Zuul的最后一个Filter,负责最终响应结果的输出@BeanpublicSendResponseFiltersendResponseFilter(ZuulPropertiesproperties) {
returnnewSendResponseFilter(zuulProperties);
    }
//这个是用来发送错误的Filter@BeanpublicSendErrorFiltersendErrorFilter() {
returnnewSendErrorFilter();
    }
//它使用RequestDispatcher转发请求@BeanpublicSendForwardFiltersendForwardFilter() {
returnnewSendForwardFilter();
    } 
}

整理一下ZuulServerAutoConfiguration 配置类里面做的事情

  • 1.注册了多路由组合定位器 CompositeRouteLocator,它是由多个RouteLocator组成的 ,路由定位器就是根据请求的path来定位对应的微服务的。
  • 2.注册了简单的路由定位器SimpleRouteLocator
  • 3.注册了很多内置的FiIlter,包括前置FiIlter,后置FiIlter,路由FiIlter,异常FiIlter
  • 4.注册了ZuulHandlerMapping 是对path和远程服务的映射
  • 5.注册了zuulServlet : 请求的分发器类似于DispatcherServlet
  • 6.注册了ZuulControllerZuulServlet会通过调用其父类ServletWrappingController的service再实现对请求的调用

4.Zuul自动配置ZuulProxyAutoConfiguration

我们看了父类 ZuulServerAutoConfiguration 大概知道了里面配置的内容,回到ZuulProxyAutoConfiguration看一下它做了哪些配置

/*** @author Spencer Gibb* @author Dave Syer* @author Biju Kunjummen*/@Configuration@Import({ RibbonCommandFactoryConfiguration.RestClientRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.OkHttpRibbonConfiguration.class,
RibbonCommandFactoryConfiguration.HttpClientRibbonConfiguration.class,
HttpClientConfiguration.class })
//配置激活条件,必须有ZuulProxyMarkerConfiguration.Marker的实例@ConditionalOnBean(ZuulProxyMarkerConfiguration.Marker.class)
publicclassZuulProxyAutoConfigurationextendsZuulServerAutoConfiguration {
    ...省略代码...
//服务发现的路由定位器,它将静态的配置RouteLocator路由与DiscoveryClient路由组合在一起, 通过服务发现的方式进行路由@Bean@ConditionalOnMissingBean(DiscoveryClientRouteLocator.class)
publicDiscoveryClientRouteLocatordiscoveryRouteLocator() {
returnnewDiscoveryClientRouteLocator(this.server.getServlet().getServletPrefix(), this.discovery, this.zuulProperties,
this.serviceRouteMapper, this.registration);
    }
// pre filters  : 前置过滤//Pre ZuulFilter可以根据提供的RouteLocator确定在哪里以及如何进行路由。 还为下游请求设置各种与代理相关的标头。@BeanpublicPreDecorationFilterpreDecorationFilter(RouteLocatorrouteLocator, ProxyRequestHelperproxyRequestHelper) {
returnnewPreDecorationFilter(routeLocator, this.server.getServlet().getServletPrefix(), this.zuulProperties,
proxyRequestHelper);
    }
// route filters   :路由过滤,实现了Ribbon的负载均衡和hystrix@BeanpublicRibbonRoutingFilterribbonRoutingFilter(ProxyRequestHelperhelper,
RibbonCommandFactory<?>ribbonCommandFactory) {
RibbonRoutingFilterfilter=newRibbonRoutingFilter(helper, ribbonCommandFactory,
this.requestCustomizers);
returnfilter;
    }
//简单主机的路由Filter@Bean@ConditionalOnMissingBean({SimpleHostRoutingFilter.class, CloseableHttpClient.class})
publicSimpleHostRoutingFiltersimpleHostRoutingFilter(ProxyRequestHelperhelper,
ZuulPropertieszuulProperties,
ApacheHttpClientConnectionManagerFactoryconnectionManagerFactory,
ApacheHttpClientFactoryhttpClientFactory) {
returnnewSimpleHostRoutingFilter(helper, zuulProperties,
connectionManagerFactory, httpClientFactory);
    }
@Bean@ConditionalOnMissingBean({SimpleHostRoutingFilter.class})
publicSimpleHostRoutingFiltersimpleHostRoutingFilter2(ProxyRequestHelperhelper,
ZuulPropertieszuulProperties,
CloseableHttpClienthttpClient) {
returnnewSimpleHostRoutingFilter(helper, zuulProperties,
httpClient);
    }
//服务名和路由组件的映射器,提供一种在路由和发现的服务名称之间应用约定的方法@Bean@ConditionalOnMissingBean(ServiceRouteMapper.class)
publicServiceRouteMapperserviceRouteMapper() {
returnnewSimpleServiceRouteMapper();
    }
}

在 ZuulProxyAutoConfiguration 中做的事情比较简单,就是定义了一堆Filter,包括:

  • DiscoveryClientRouteLocator : 静态路由定位器,通过服务发现的方式找到要调用的服务
  • PreDecorationFilter :Pre Filter可以根据提供的RouteLocator确定在哪里以及如何进行路由。 还为下游请求设置各种与代理相关的标头。
  • RibbonRoutingFilter :route Filter:路由过滤,实现了Ribbon的负载均衡和hystrix
  • SimpleHostRoutingFilter : 简单主机的路由Filter,它通过HttpClient将请求发送到预定的URL, URL可在RequestContext.getRouteHost()中找到。

二.Zuul的核心组件

1.ZuulController请求入口控制器

ZuulController还是挺重要的,它是zuul的请求入口控制器,它创建了ZuulServlet去执行请求,ZuulController的源码:

publicclassZuulControllerextendsServletWrappingController {
publicZuulController() {
//把 ZuulServlet 设置给父类setServletClass(ZuulServlet.class);
setServletName("zuul");
setSupportedMethods((String[]) null); // Allow all    }
@OverridepublicModelAndViewhandleRequest(HttpServletRequestrequest, HttpServletResponseresponse) throwsException {
try {
// We don't care about the other features of the base class, just want to// handle the request//执行父类的请求方法returnsuper.handleRequestInternal(request, response);
        }
finally {
// @see com.netflix.zuul.context.ContextLifecycleFilter.doFilterRequestContext.getCurrentContext().unset();
        }
    }
}

父类 ServletWrappingController 源码:

publicclassServletWrappingControllerextendsAbstractControllerimplementsBeanNameAware, InitializingBean, DisposableBean {
    ...省略...
@NullableprivateServletservletInstance;
//反射创建 ZuulServlet和初始化publicvoidafterPropertiesSet() throwsException {
if (this.servletClass==null) {
thrownewIllegalArgumentException("'servletClass' is required");
        } else {
if (this.servletName==null) {
this.servletName=this.beanName;
            }
//反射创建 ZuulServlet和初始化this.servletInstance= (Servlet)ReflectionUtils.accessibleConstructor(this.servletClass, newClass[0]).newInstance();
this.servletInstance.init(newServletWrappingController.DelegatingServletConfig());
        }
    }
//核心方法,调用 servletInstance 执行请求protectedModelAndViewhandleRequestInternal(HttpServletRequestrequest, HttpServletResponseresponse) throwsException {
Assert.state(this.servletInstance!=null, "No Servlet instance");
//调用 ZuulServlet执行请求this.servletInstance.service(request, response);
returnnull;
    }

2.RouteLocator路由定位器

RouteLocator是路由定位器,就是根据请求的path来定位对应的微服务的 ,在ZuulServerAutoConfiguration 中定义了 CompositeRouteLocator 多路由组合定位器 和 SimpleRouteLocator 简单路由定位器,在ZuulProxyAutoConfiguration中定义了 DiscoveryClientRouteLocator ,我们看一下 继承体系

RouteLocator 是路由定位器接口,提供了

  • getIgnoredPaths:获取忽略的服务paths集合,
  • getRoutes :获取服务路由Route集合 ,这里的Route是对yml中 zuul.routes的封装
  • getMatchingRoute :根据path获取路由Route
publicinterfaceRouteLocator {
/*** Ignored route paths (or patterns), if any.*/Collection<String>getIgnoredPaths();
/*** A map of route path (pattern) to location (e.g. service id or URL).*/List<Route>getRoutes();
/*** Maps a path to an actual route with full metadata.*/RoutegetMatchingRoute(Stringpath);
}

CompositeRouteLocator 是多路由组合定位器,它主要是调用多个路由定位器根据path找到Route

publicclassCompositeRouteLocatorimplementsRefreshableRouteLocator {
//省略@OverridepublicRoutegetMatchingRoute(Stringpath) {
//遍历所有的 routeLocatorsfor (RouteLocatorlocator : routeLocators) {
//通过 path找到 RouteRouteroute=locator.getMatchingRoute(path);
if (route!=null) {
returnroute;
            }
        }
returnnull;
    }
}

SimpleRouteLocator 是最简单的路由定位器,根据path找到route返回

protectedRoutegetSimpleMatchingRoute(finalStringpath) {
if (log.isDebugEnabled()) {
log.debug("Finding route for path: "+path);
    }
//获取所有的路由// This is called for the initialization done in getRoutesMap()getRoutesMap();
if (log.isDebugEnabled()) {
log.debug("servletPath="+this.dispatcherServletPath);
log.debug("zuulServletPath="+this.zuulServletPath);
log.debug("RequestUtils.isDispatcherServletRequest()="+RequestUtils.isDispatcherServletRequest());
log.debug("RequestUtils.isZuulServletRequest()="+RequestUtils.isZuulServletRequest());
    }
//调整路径StringadjustedPath=adjustPath(path);
//根据Path查找RouteZuulRouteroute=getZuulRoute(adjustedPath);
returngetRoute(route, adjustedPath);
}
//根据path匹配RouteprotectedZuulRoutegetZuulRoute(StringadjustedPath) {
//不忽略if (!matchesIgnoredPatterns(adjustedPath)) {
//遍历所有的routefor (Entry<String, ZuulRoute>entry : getRoutesMap().entrySet()) {
Stringpattern=entry.getKey();
log.debug("Matching pattern:"+pattern);
//根据path简单匹配 route,返回routeif (this.pathMatcher.match(pattern, adjustedPath)) {
returnentry.getValue();
            }
        }
    }
returnnull;
}

重点看一下 DiscoveryClientRouteLocator ,配置RouteLocator路由与DiscoveryClient路由组合在一起,优先使用服务发现方式,源码

/*** A {@link RouteLocator} that combines static, configured routes with those from a* {@link DiscoveryClient}. The discovery client takes precedence.** @author Spencer Gibb* @author Dave Syer*/publicclassDiscoveryClientRouteLocatorextendsSimpleRouteLocatorimplementsRefreshableRouteLocator {
privatestaticfinalLoglog=LogFactory.getLog(DiscoveryClientRouteLocator.class);
publicstaticfinalStringDEFAULT_ROUTE="/**";
//服务发现客户端,用来从注册中心找服务的privateDiscoveryClientdiscovery;
//zuul的配置,以zuul开头的配置项privateZuulPropertiesproperties;
//提供一种在 路由 和 发现的服务名称 之间应用约定的方法privateServiceRouteMapperserviceRouteMapper;
//初始化publicDiscoveryClientRouteLocator(StringservletPath, DiscoveryClientdiscovery,
ZuulPropertiesproperties, ServiceInstancelocalServiceInstance) {
super(servletPath, properties);
//添加忽略的服务,对应配置:ignored-services: "*"if (properties.isIgnoreLocalService() &&localServiceInstance!=null) {
StringlocalServiceId=localServiceInstance.getServiceId();
if (!properties.getIgnoredServices().contains(localServiceId)) {
properties.getIgnoredServices().add(localServiceId);
            }
        }
this.serviceRouteMapper=newSimpleServiceRouteMapper();
//初始化服务发现客户端this.discovery=discovery;
this.properties=properties;
    }
//定位路由,读取yml配置的zuul.routes中的路由,然后把需要忽略的忽略掉,加上前缀返回。@OverrideprotectedLinkedHashMap<String, ZuulRoute>locateRoutes() {
LinkedHashMap<String, ZuulRoute>routesMap=newLinkedHashMap<>();
//拿到所有的routes配置添加到map中,对应yml中的zuul.routes配置routesMap.putAll(super.locateRoutes());
if (this.discovery!=null) {
Map<String, ZuulRoute>staticServices=newLinkedHashMap<>();
for (ZuulRouteroute : routesMap.values()) {
//取到配置的每个路由的微服务的IDStringserviceId=route.getServiceId();
if (serviceId==null) {
serviceId=route.getId();
                }
if (serviceId!=null) {
//以 服务ID为key把每个路由配置放到staticServices中staticServices.put(serviceId, route);
                }
            }
// Add routes for discovery services by default//通过服务发现客户端找到服务注册表中的服务List<String>services=this.discovery.getServices();
//得到配置中需要忽略的服务的服务名String[] ignored=this.properties.getIgnoredServices()
                    .toArray(newString[0]);
//循环从注册表中找到的服务 , 这里要处理忽略的服务for (StringserviceId : services) {
// Ignore specifically ignored services and those that were manually// configuredStringkey="/"+mapRouteToService(serviceId) +"/**";
if (staticServices.containsKey(serviceId)
&&staticServices.get(serviceId).getUrl() ==null) {
// Explicitly configured with no URL, cannot be ignored// all static routes are already in routesMap// Update location using serviceId if location is nullZuulRoutestaticRoute=staticServices.get(serviceId);
if (!StringUtils.hasText(staticRoute.getLocation())) {
staticRoute.setLocation(serviceId);
                    }
                }
//通过工具判断哪些服务要忽略,不忽略的加入routesMap中if (!PatternMatchUtils.simpleMatch(ignored, serviceId)
&&!routesMap.containsKey(key)) {
// Not ignoredroutesMap.put(key, newZuulRoute(key, serviceId));
                }
            }
        }
if (routesMap.get(DEFAULT_ROUTE) !=null) {
ZuulRoutedefaultRoute=routesMap.get(DEFAULT_ROUTE);
// Move the defaultServiceId to the endroutesMap.remove(DEFAULT_ROUTE);
routesMap.put(DEFAULT_ROUTE, defaultRoute);
        }
LinkedHashMap<String, ZuulRoute>values=newLinkedHashMap<>();
//处理路径的 前缀for (Entry<String, ZuulRoute>entry : routesMap.entrySet()) {
Stringpath=entry.getKey();
// Prepend with slash if not already present.if (!path.startsWith("/")) {
path="/"+path;
            }
if (StringUtils.hasText(this.properties.getPrefix())) {
path=this.properties.getPrefix() +path;
//如果没有前缀就加上前缀if (!path.startsWith("/")) {
path="/"+path;
                }
            }
values.put(path, entry.getValue());
        }
returnvalues;
    }

3.Zuul的内置Filter详解

ServletDetectionFilter

前置通知 ,执行顺序 -3 , 该Filter是用来标记该请求是通过 DispatcherServlet处理还是通过 ZuulServlet处理run()把判断结果以boolean值的方式保存到HttpServletRequest中,后续的处理中就可以通过它获取到这个标记做不同的处理,而这个filter执行的顺序是 -3(filterOrder() 方法) ,越小越先执行

/*** Detects whether a request is ran through the {@link DispatcherServlet} or {@link ZuulServlet}.* The purpose was to detect this up-front at the very beginning of Zuul filter processing*  and rely on this information in all filters.*  RequestContext is used such that the information is accessible to classes *  which do not have a request reference.* @author Adrian Ivan*/publicclassServletDetectionFilterextendsZuulFilter {
publicServletDetectionFilter() {
    }
//前置通知@OverridepublicStringfilterType() {
returnPRE_TYPE;
    }
/*** Must run before other filters that rely on the difference between * DispatcherServlet and ZuulServlet.*///filterOrder 决定了这个过滤器的执行顺序 这里是 :-3 见//public static final int SERVLET_DETECTION_FILTER_ORDER = -3;@OverridepublicintfilterOrder() {
returnSERVLET_DETECTION_FILTER_ORDER;
    }
@OverridepublicbooleanshouldFilter() {
returntrue; 
    }
@OverridepublicObjectrun() {
RequestContextctx=RequestContext.getCurrentContext();
//判断结果保存到  HttpServletRequest中HttpServletRequestrequest=ctx.getRequest();
//如果请求中设置了DispatcherServletRequest属性就给RequestContext 设置 IS_DISPATCHER_SERVLET_REQUEST_KEY = trueif (!(requestinstanceofHttpServletRequestWrapper) 
&&isDispatcherServletRequest(request)) {
//上下文设置:isDispatcherServletRequest = truectx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, true);
        } else {
//上下文设置:isDispatcherServletRequest = falsectx.set(IS_DISPATCHER_SERVLET_REQUEST_KEY, false);
        }
returnnull;
    }
//判断当前请求是否是DispatcherServletRequestprivatebooleanisDispatcherServletRequest(HttpServletRequestrequest) {
returnrequest.getAttribute(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE) !=null;
    }       
}
FormBodyWrapperFilter

前置通知,执行顺序 -1 , 解析表单数据并为后续处理重新编码,由于后续的请求中,将符合要求的请求体包装成FormBodyRequestWrapper对象。

/*** Pre {@link ZuulFilter} that parses form data and reencodes it for downstream services** @author Dave Syer*/publicclassFormBodyWrapperFilterextendsZuulFilter {
    ...省略...
@OverridepublicStringfilterType() {
//前置FilterreturnPRE_TYPE;
    }
@OverridepublicintfilterOrder() {
//执行顺序 -1returnFORM_BODY_WRAPPER_FILTER_ORDER;
    }
@OverridepublicObjectrun() {
RequestContextctx=RequestContext.getCurrentContext();
//处理请求HttpServletRequestrequest=ctx.getRequest();
FormBodyRequestWrapperwrapper=null;
if (requestinstanceofHttpServletRequestWrapper) {
HttpServletRequestwrapped= (HttpServletRequest) ReflectionUtils                    .getField(this.requestField, request);
//包装成 FormBodyRequestWrapperwrapper=newFormBodyRequestWrapper(wrapped);
ReflectionUtils.setField(this.requestField, request, wrapper);
if (requestinstanceofServletRequestWrapper) {
ReflectionUtils.setField(this.servletRequestField, request, wrapper);
            }
        }
else {
//包装成 FormBodyRequestWrapperwrapper=newFormBodyRequestWrapper(request);
ctx.setRequest(wrapper);
        }
if (wrapper!=null) {
ctx.getZuulRequestHeaders().put("content-type", wrapper.getContentType());
        }
returnnull;
    }
...省略...
 }
DebugFilter

开启调试标记,如果请求中设置了“debug”请求参数, RequestContext调试属性设置为true。说白了就是通过 reques中的debug参数来激活调试信息,这样当线上环境出现问题的时候,可以通过请求参数的方式来激活这些debug信息以帮助分析问题,前置通知 ,执行顺序 1

/*** Pre {@link ZuulFilter} that sets {@link RequestContext} debug attributes to true if* the "debug" request parameter is set.** @author Spencer Gibb*/publicclassDebugFilterextendsZuulFilter {
privatestaticfinalDynamicBooleanPropertyROUTING_DEBUG=DynamicPropertyFactory            .getInstance().getBooleanProperty(ZuulConstants.ZUUL_DEBUG_REQUEST, false);
privatestaticfinalDynamicStringPropertyDEBUG_PARAMETER=DynamicPropertyFactory            .getInstance().getStringProperty(ZuulConstants.ZUUL_DEBUG_PARAMETER, "debug");
@OverridepublicStringfilterType() {
returnPRE_TYPE;
    }
@OverridepublicintfilterOrder() {
returnDEBUG_FILTER_ORDER;
    }
@OverridepublicbooleanshouldFilter() {
HttpServletRequestrequest=RequestContext.getCurrentContext().getRequest();
if ("true".equals(request.getParameter(DEBUG_PARAMETER.get()))) {
returntrue;
        }
returnROUTING_DEBUG.get();
    }
@OverridepublicObjectrun() {
RequestContextctx=RequestContext.getCurrentContext();
//设置上下文,开启Routing和Request的debug功能ctx.setDebugRouting(true);
ctx.setDebugRequest(true);
returnnull;
    }
}
Servlet30WrapperFilter

这里是对原始的HttpServletRequest请求包装成Servlet30RequestWrapper对象即要兼容3.0。zuul默认只是兼容2.5,前置通知 ,执行顺序 -2

/*** Pre {@link ZuulFilter} that wraps requests in a Servlet 3.0 compliant wrapper.* Zuul's default wrapper is only Servlet 2.5 compliant.* @author Spencer Gibb*/publicclassServlet30WrapperFilterextendsZuulFilter {
privateFieldrequestField=null;
publicServlet30WrapperFilter() {
this.requestField=ReflectionUtils.findField(HttpServletRequestWrapper.class,
"req", HttpServletRequest.class);
Assert.notNull(this.requestField,
"HttpServletRequestWrapper.req field not found");
this.requestField.setAccessible(true);
    }
protectedFieldgetRequestField() {
returnthis.requestField;
    }
@OverridepublicStringfilterType() {
returnPRE_TYPE;
    }
@OverridepublicintfilterOrder() {
returnSERVLET_30_WRAPPER_FILTER_ORDER;
    }
@OverridepublicObjectrun() {
//把请求包装成  Servlet30RequestWrapperRequestContextctx=RequestContext.getCurrentContext();
HttpServletRequestrequest=ctx.getRequest();
if (requestinstanceofHttpServletRequestWrapper) {
request= (HttpServletRequest) ReflectionUtils.getField(this.requestField,
request);
ctx.setRequest(newServlet30RequestWrapper(request));
        }
elseif (RequestUtils.isDispatcherServletRequest()) {
// If it's going through the dispatcher we need to buffer the bodyctx.setRequest(newServlet30RequestWrapper(request));
        }
returnnull;
    }
}

你现在知道为什么他叫 Servlet30WrapperFilter 了吗?

SendResponseFilter

后置通知 ,处理请求响应,执行顺序 1000

/*** Post {@link ZuulFilter} that writes responses from proxied requests to the current response.** @author Spencer Gibb* @author Dave Syer* @author Ryan Baxter*/publicclassSendResponseFilterextendsZuulFilter {
...省略...
@OverridepublicObjectrun() {
try {
//添加响应头addResponseHeaders();
//添加响应的内容writeResponse();
        }
catch (Exceptionex) {
ReflectionUtils.rethrowRuntimeException(ex);
        }
returnnull;
    }
privatevoidwriteResponse() throwsException {
RequestContextcontext=RequestContext.getCurrentContext();
// there is no body to sendif (context.getResponseBody() ==null&&context.getResponseDataStream() ==null) {
return;
        }
HttpServletResponseservletResponse=context.getResponse();
if (servletResponse.getCharacterEncoding() ==null) { // only set if not setservletResponse.setCharacterEncoding("UTF-8");
        }
OutputStreamoutStream=servletResponse.getOutputStream();
InputStreamis=null;
try {
if (RequestContext.getCurrentContext().getResponseBody() !=null) {
Stringbody=RequestContext.getCurrentContext().getResponseBody();
writeResponse(
newByteArrayInputStream(
body.getBytes(servletResponse.getCharacterEncoding())),
outStream);
return;
            }
        ...省略...
//写响应结果privatevoidwriteResponse(InputStreamzin, OutputStreamout) throwsException {
byte[] bytes=buffers.get();
intbytesRead=-1;
while ((bytesRead=zin.read(bytes)) !=-1) {
out.write(bytes, 0, bytesRead);
        }
    }
}

翻译大致意思为把代理请求的响应写入到当前响应,String body = RequestContext.getCurrentContext().getResponseBody(); 获取到响应内容 ,通过 servletResponse.getOutputStream(); 写出去 ,

我们从源码中可以看到该过滤器会检查请求上下文中是否包含请求响应相关的头信息、响应数据流或是响应体,然后利用请求上下文的响应信息来组织需要发送回客户端的响应内容。

SendErrorFilter

错误处理过滤器 ,把错误重定向到/error路径上,执行顺序 0

/*** Error {@link ZuulFilter} that forwards to /error (by default) if {@link RequestContext#getThrowable()} is not null.** @author Spencer Gibb*///TODO: move to error package in EdgwarepublicclassSendErrorFilterextendsZuulFilter {
privatestaticfinalLoglog=LogFactory.getLog(SendErrorFilter.class);
protectedstaticfinalStringSEND_ERROR_FILTER_RAN="sendErrorFilter.ran";
//异常重定向路径@Value("${error.path:/error}")
privateStringerrorPath;
@OverridepublicStringfilterType() {
returnERROR_TYPE;
    }
@OverridepublicintfilterOrder() {
returnSEND_ERROR_FILTER_ORDER;
    }
@OverridepublicbooleanshouldFilter() {
RequestContextctx=RequestContext.getCurrentContext();
// only forward to errorPath if it hasn't been forwarded to alreadyreturnctx.getThrowable() !=null&&!ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
    }
@OverridepublicObjectrun() {
try {
RequestContextctx=RequestContext.getCurrentContext();
//找到异常ZuulExceptionexception=findZuulException(ctx.getThrowable());
HttpServletRequestrequest=ctx.getRequest();
//处理异常错误码等request.setAttribute("javax.servlet.error.status_code", exception.nStatusCode);
log.warn("Error during filtering", exception);
request.setAttribute("javax.servlet.error.exception", exception);
if (StringUtils.hasText(exception.errorCause)) {
request.setAttribute("javax.servlet.error.message", exception.errorCause);
            }
RequestDispatcherdispatcher=request.getRequestDispatcher(
this.errorPath);
if (dispatcher!=null) {
ctx.set(SEND_ERROR_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
ctx.setResponseStatusCode(exception.nStatusCode);
dispatcher.forward(request, ctx.getResponse());
                }
            }
        }
catch (Exceptionex) {
ReflectionUtils.rethrowRuntimeException(ex);
        }
returnnull;
    }
}
SendForwardFilter

用来处理路由规则中的forward本地跳转配置 ,执行顺序 5000

/*** Route {@link ZuulFilter} that forwards requests using the {@link RequestDispatcher}.* Forwarding location is located in the {@link RequestContext} attribute {@link org.springframework.cloud.netflix.zuul.filters.support.FilterConstants#FORWARD_TO_KEY}.* Useful for forwarding to endpoints in the current application.用户RequestDispatcher 进行本地应用端点的Forwarding* @author Dave Syer*/publicclassSendForwardFilterextendsZuulFilter {
    ...省略...
@OverridepublicObjectrun() {
try {
RequestContextctx=RequestContext.getCurrentContext();
Stringpath= (String) ctx.get(FORWARD_TO_KEY);
RequestDispatcherdispatcher=ctx.getRequest().getRequestDispatcher(path);
if (dispatcher!=null) {
ctx.set(SEND_FORWARD_FILTER_RAN, true);
if (!ctx.getResponse().isCommitted()) {
//请求跳转dispatcher.forward(ctx.getRequest(), ctx.getResponse());
ctx.getResponse().flushBuffer();
                }
            }
        }
catch (Exceptionex) {
ReflectionUtils.rethrowRuntimeException(ex);
        }
returnnull;
    }
}

下面是 ZuulProxyAutoConfiguration 中还定义了一些过滤器

PreDecorationFilter

Pre 前置filter可以根据提供的RouteLocator确定在哪里以及如何进行路由。 还为下游请求设置各种与代理相关的请求头,执行顺序 5

/*** Pre {@link ZuulFilter} that determines where and how to route based on the supplied {@link RouteLocator}.* Also sets various proxy related headers for downstream requests.*/publicclassPreDecorationFilterextendsZuulFilter {
    ...省略...
@OverridepublicObjectrun() {
//请求上下文RequestContextctx=RequestContext.getCurrentContext();
//请求路径finalStringrequestURI=this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
//根据请求地址,匹配匹配路由(url对应要访问的服务)Routeroute=this.routeLocator.getMatchingRoute(requestURI);
if (route!=null) {
//从路由中获取请求服务idStringlocation=route.getLocation();
if (location!=null) {
//设置请求上下文相关信息//请求的资源路径  pathctx.put(REQUEST_URI_KEY, route.getPath());
//路由的idctx.put(PROXY_KEY, route.getId());
if (!route.isCustomSensitiveHeaders()) {
//忽略敏感的请求头this.proxyRequestHelper                        .addIgnoredHeaders(this.properties.getSensitiveHeaders().toArray(newString[0]));
                }
else {
this.proxyRequestHelper.addIgnoredHeaders(route.getSensitiveHeaders().toArray(newString[0]));
                }
if (route.getRetryable() !=null) {
//可否重试ctx.put(RETRYABLE_KEY, route.getRetryable());
                }
if (location.startsWith(HTTP_SCHEME+":") ||location.startsWith(HTTPS_SCHEME+":")) {
ctx.setRouteHost(getUrl(location));
ctx.addOriginResponseHeader(SERVICE_HEADER, location);
                }
elseif (location.startsWith(FORWARD_LOCATION_PREFIX)) {
ctx.set(FORWARD_TO_KEY,
StringUtils.cleanPath(location.substring(FORWARD_LOCATION_PREFIX.length()) +route.getPath()));
ctx.setRouteHost(null);
returnnull;
                }
else {
//设置服务id绑定到上下文个,在RibbonReques中使用// set serviceId for use in filters.route.RibbonRequestctx.set(SERVICE_ID_KEY, location);
ctx.setRouteHost(null);
ctx.addOriginResponseHeader(SERVICE_ID_HEADER, location);
                }
//添加代理请求头if (this.properties.isAddProxyHeaders()) {
addProxyHeaders(ctx, route);
Stringxforwardedfor=ctx.getRequest().getHeader(X_FORWARDED_FOR_HEADER);
StringremoteAddr=ctx.getRequest().getRemoteAddr();
if (xforwardedfor==null) {
xforwardedfor=remoteAddr;
                    }
elseif (!xforwardedfor.contains(remoteAddr)) { // Prevent duplicatesxforwardedfor+=", "+remoteAddr;
                    }
ctx.addZuulRequestHeader(X_FORWARDED_FOR_HEADER, xforwardedfor);
                }
if (this.properties.isAddHostHeader()) {
ctx.addZuulRequestHeader(HttpHeaders.HOST, toHostHeader(ctx.getRequest()));
                }
                ...省略...
RibbonRoutingFilter

Routing过滤器,使用Ribbon和Hystrix来向服务实例发起请求 ,有服务熔断机制,执行顺序 10

/*** Route {@link ZuulFilter} that uses Ribbon, Hystrix and pluggable http clients to send requests.* ServiceIds are found in the {@link RequestContext} attribute {@link org.springframework.cloud.netflix.zuul.filters.support.FilterConstants#SERVICE_ID_KEY}.通过  Ribbon 和  Hystrix 向http客户端发送请求通过 RequestContext找到  ServiceIds服务id ,* @author Spencer Gibb* @author Dave Syer* @author Ryan Baxter*/publicclassRibbonRoutingFilterextendsZuulFilter {
    ...省略...
@OverridepublicObjectrun() {
//获取请求上下文 RequestContextcontext=RequestContext.getCurrentContext();
this.helper.addIgnoredHeaders();
try {
//创建一个 RibbonCommandContext Ribbon命令上下文,用来发请求RibbonCommandContextcommandContext=buildCommandContext(context);
//发送请求,获取到结果ClientHttpResponseresponse=forward(commandContext);
setResponse(response);
returnresponse;
        }
catch (ZuulExceptionex) {
thrownewZuulRuntimeException(ex);
        }
catch (Exceptionex) {
thrownewZuulRuntimeException(ex);
        }
    }
//根据RequestContext 请求上下文,获取请求服务id,url等封装成RibbonCommandContextprotectedRibbonCommandContextbuildCommandContext(RequestContextcontext) {
HttpServletRequestrequest=context.getRequest();
MultiValueMap<String, String>headers=this.helper            .buildZuulRequestHeaders(request);
MultiValueMap<String, String>params=this.helper            .buildZuulRequestQueryParams(request);
Stringverb=getVerb(request);
InputStreamrequestEntity=getRequestBody(request);
if (request.getContentLength() <0&&!verb.equalsIgnoreCase("GET")) {
context.setChunkedRequestBody();
        }
StringserviceId= (String) context.get(SERVICE_ID_KEY);
Booleanretryable= (Boolean) context.get(RETRYABLE_KEY);
ObjectloadBalancerKey=context.get(LOAD_BALANCER_KEY);
Stringuri=this.helper.buildZuulRequestURI(request);
// remove double slashesuri=uri.replace("//", "/");
longcontentLength=useServlet31?request.getContentLengthLong(): request.getContentLength();
returnnewRibbonCommandContext(serviceId, verb, uri, retryable, headers, params,
requestEntity, this.requestCustomizers, contentLength, loadBalancerKey);
    }
protectedClientHttpResponseforward(RibbonCommandContextcontext) throwsException {
Map<String, Object>info=this.helper.debug(context.getMethod(),
context.getUri(), context.getHeaders(), context.getParams(),
context.getRequestEntity());
RibbonCommandcommand=this.ribbonCommandFactory.create(context);
try {
//执行请求ClientHttpResponseresponse=command.execute();
this.helper.appendDebug(info, response.getRawStatusCode(), response.getHeaders());
returnresponse;
        }
catch (HystrixRuntimeExceptionex) {
//处理异常returnhandleException(info, ex);
        }
    }
//处理异常protectedClientHttpResponsehandleException(Map<String, Object>info,
HystrixRuntimeExceptionex) throwsZuulException {
intstatusCode=HttpStatus.INTERNAL_SERVER_ERROR.value();
Throwablecause=ex;
Stringmessage=ex.getFailureType().toString();
ClientExceptionclientException=findClientException(ex);
if (clientException==null) {
clientException=findClientException(ex.getFallbackException());
        }
if (clientException!=null) {
if (clientException                .getErrorType() ==ClientException.ErrorType.SERVER_THROTTLED) {
statusCode=HttpStatus.SERVICE_UNAVAILABLE.value();
            }
cause=clientException;
message=clientException.getErrorType().toString();
        }
info.put("status", String.valueOf(statusCode));
thrownewZuulException(cause, "Forwarding error", statusCode, message);
    }
SimpleHostRoutingFilter

通过RequestContext#getRouteHost()找到调用的服务地址 ,使用http客户端实现调用 ,他和RibbonRoutingFilter的区别是没有使用Hystrix所以并没有线程隔离和断路器的保护。

执行顺序 100

/*** Route {@link ZuulFilter} that sends requests to predetermined URLs via apache* {@link HttpClient}. URLs are found in {@link RequestContext#getRouteHost()}.通过RequestContext#getRouteHost()找到调用的服务地址 ,使用apache的http客户端实现调用** @author Spencer Gibb* @author Dave Syer* @author Bilal Alp* @author Gang Li*/publicclassSimpleHostRoutingFilterextendsZuulFilter {
    ...省略...
@OverridepublicObjectrun() {
RequestContextcontext=RequestContext.getCurrentContext();
HttpServletRequestrequest=context.getRequest();
MultiValueMap<String, String>headers=this.helper            .buildZuulRequestHeaders(request);
MultiValueMap<String, String>params=this.helper            .buildZuulRequestQueryParams(request);
Stringverb=getVerb(request);
InputStreamrequestEntity=getRequestBody(request);
if (request.getContentLength() <0) {
context.setChunkedRequestBody();
        }
//获取请求地址Stringuri=this.helper.buildZuulRequestURI(request);
this.helper.addIgnoredHeaders();
try {
//发送请求CloseableHttpResponseresponse=forward(this.httpClient, verb, uri, request,
headers, params, requestEntity);
setResponse(response);
        }
catch (Exceptionex) {
thrownewZuulRuntimeException(ex);
        }
returnnull;
    }
------------------------------------//使用 httpclient 发送请求privateCloseableHttpResponseforward(CloseableHttpClienthttpclient, Stringverb,
Stringuri, HttpServletRequestrequest, MultiValueMap<String, String>headers,
MultiValueMap<String, String>params, InputStreamrequestEntity)
throwsException {
Map<String, Object>info=this.helper.debug(verb, uri, headers, params,
requestEntity);
//请求主机URLhost=RequestContext.getCurrentContext().getRouteHost();
HttpHosthttpHost=getHttpHost(host);
//请求地址uri=StringUtils.cleanPath((host.getPath() +uri).replaceAll("/{2,}", "/"));
intcontentLength=request.getContentLength();
ContentTypecontentType=null;
if (request.getContentType() !=null) {
contentType=ContentType.parse(request.getContentType());
        }
InputStreamEntityentity=newInputStreamEntity(requestEntity, contentLength,
contentType);
HttpRequesthttpRequest=buildHttpRequest(verb, uri, entity, headers, params,
request);
try {
log.debug(httpHost.getHostName() +" "+httpHost.getPort() +" "+httpHost.getSchemeName());
//发送请求CloseableHttpResponsezuulResponse=forwardRequest(httpclient, httpHost,
httpRequest);
this.helper.appendDebug(info, zuulResponse.getStatusLine().getStatusCode(),
revertHeaders(zuulResponse.getAllHeaders()));
returnzuulResponse;
        }
finally {
// When HttpClient instance is no longer needed,// shut down the connection manager to ensure// immediate deallocation of all system resources// httpclient.getConnectionManager().shutdown();        }
    }
------------------------------------protectedHttpRequestbuildHttpRequest(Stringverb, Stringuri,
InputStreamEntityentity, MultiValueMap<String, String>headers,
MultiValueMap<String, String>params, HttpServletRequestrequest) {
HttpRequesthttpRequest;
StringuriWithQueryString=uri+ (this.forceOriginalQueryStringEncoding?getEncodedQueryString(request) : 
this.helper.getQueryString(params));
//处理各种请求方式switch (verb.toUpperCase()) {
case"POST":
HttpPosthttpPost=newHttpPost(uriWithQueryString);
httpRequest=httpPost;
httpPost.setEntity(entity);
break;
case"PUT":
HttpPuthttpPut=newHttpPut(uriWithQueryString);
httpRequest=httpPut;
httpPut.setEntity(entity);
break;
case"PATCH":
HttpPatchhttpPatch=newHttpPatch(uriWithQueryString);
httpRequest=httpPatch;
httpPatch.setEntity(entity);
break;
case"DELETE":
BasicHttpEntityEnclosingRequestentityRequest=newBasicHttpEntityEnclosingRequest(
verb, uriWithQueryString);
httpRequest=entityRequest;
entityRequest.setEntity(entity);
break;
default:
httpRequest=newBasicHttpRequest(verb, uriWithQueryString);
log.debug(uriWithQueryString);
        }
httpRequest.setHeaders(convertHeaders(headers));
returnhttpRequest;
    }
------------------------------------//最终执行请求privateCloseableHttpResponseforwardRequest(CloseableHttpClienthttpclient,
HttpHosthttpHost, HttpRequesthttpRequest) throwsIOException {
returnhttpclient.execute(httpHost, httpRequest);
    }

到这里我们把 ZuulProxyAutoConfiguration 自动配置类中定义的比较重要的一些过滤器都介绍完了 ,zuul在执行过程中就会按照这些filter的调用顺序去执行,我们来用表格整理一下

类型 过滤器 描述 顺序
pre ServletDetectionFilter 在pre过滤器中,ServletDetectionFilter是第一个执行的过滤器,检测请求是用 DispatcherServlet还是 ZuulServlet,将结果设置到RequestContext中 -3
pre Servlet30WrapperFilter 主要是将原始请求进行包装,将原始的HttpServletRequest请求包装成Servlet30RequestWrapper类型 -2
pre FormBodyWrapperFilter 同Servlet30RequestWrapper一样,也是对请求的一个包装,只不过他只包装表单数据,即:content-type中必须带有“application/x-www-form-urlencoded”或“multipart/form-data” -1
error SendErrorFilter 这个是用来发送错误的Filter 0
pre DebugFilter 设置请求过程是否开启debug,将当前请求上下文中的debugRoutingdebugRequest参数设置为true 1
pre PreDecorationFilter 基本的路由转发配置,根据uri调用哪一个route过滤器 5
route RibbonRoutingFilter 服务路由的过滤器,使用用Ribbon 做负载均衡,hystrix做熔断 10
route SimpleHostRoutingFilter 简单主机路由过滤器,如果使用url路由,则用这个过滤器 100
route SendForwardFilter 它使用RequestDispatcher转发请求 500
post SendResponseFilter SendResponseFilter是Zuul的最后一个Filter,负责最终响应结果的输出。 1000

那这一章我们分析到这里 ,下一章我们跟踪一下zuul的执行流程,看他是如果把这些filter串联起来的 。

相关实践学习
部署高可用架构
本场景主要介绍如何使用云服务器ECS、负载均衡SLB、云数据库RDS和数据传输服务产品来部署多可用区高可用架构。
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
15天前
|
存储 Java 数据安全/隐私保护
|
18天前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
31 0
|
1天前
|
安全 Java 开发者
深入理解Spring Boot配置绑定及其实战应用
【4月更文挑战第10天】本文详细探讨了Spring Boot中配置绑定的核心概念,并结合实战示例,展示了如何在项目中有效地使用这些技术来管理和绑定配置属性。
8 1
|
2天前
|
XML SQL Java
SpringCloud 基础配置
SpringCloud 基础配置
9 0
|
3天前
|
Java Spring
Spring文件配置以及获取
Spring文件配置以及获取
10 0
|
9天前
|
Java 微服务 Spring
Spring Boot中获取配置参数的几种方法
Spring Boot中获取配置参数的几种方法
20 2
|
11天前
|
消息中间件 安全 Java
在Spring Bean中,如何通过Java配置类定义Bean?
【4月更文挑战第30天】在Spring Bean中,如何通过Java配置类定义Bean?
20 1
|
13天前
|
Java 开发者 Spring
Spring Boot中的资源文件属性配置
【4月更文挑战第28天】在Spring Boot应用程序中,配置文件是管理应用程序行为的重要组成部分。资源文件属性配置允许开发者在不重新编译代码的情况下,对应用程序进行灵活地配置和调整。本篇博客将介绍Spring Boot中资源文件属性配置的基本概念,并通过实际示例展示如何利用这一功能。
22 1
|
16天前
|
Java Spring 容器
如何用基于 Java 配置的方式配置 Spring?
如何用基于 Java 配置的方式配置 Spring?
|
17天前
|
存储 前端开发 Java
第十一章 Spring Cloud Alibaba nacos配置中心
第十一章 Spring Cloud Alibaba nacos配置中心
22 0