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

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
EMR Serverless StarRocks,5000CU*H 48000GB*H
网络型负载均衡 NLB,每月750个小时 15LCU
简介: 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串联起来的 。

相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
8天前
|
Java 开发者 微服务
手写模拟Spring Boot自动配置功能
【11月更文挑战第19天】随着微服务架构的兴起,Spring Boot作为一种快速开发框架,因其简化了Spring应用的初始搭建和开发过程,受到了广大开发者的青睐。自动配置作为Spring Boot的核心特性之一,大大减少了手动配置的工作量,提高了开发效率。
28 0
|
1月前
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
42 4
|
1月前
|
Java API 数据库
Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐
本文通过在线图书管理系统案例,详细介绍如何使用Spring Boot构建RESTful API。从项目基础环境搭建、实体类与数据访问层定义,到业务逻辑实现和控制器编写,逐步展示了Spring Boot的简洁配置和强大功能。最后,通过Postman测试API,并介绍了如何添加安全性和异常处理,确保API的稳定性和安全性。
38 0
|
26天前
|
Java API Spring
在 Spring 配置文件中配置 Filter 的步骤
【10月更文挑战第21天】在 Spring 配置文件中配置 Filter 是实现请求过滤的重要手段。通过合理的配置,可以灵活地对请求进行处理,满足各种应用需求。还可以根据具体的项目要求和实际情况,进一步深入研究和优化 Filter 的配置,以提高应用的性能和安全性。
|
18天前
|
Java Spring
[Spring]aop的配置与使用
本文介绍了AOP(面向切面编程)的基本概念和核心思想。AOP是Spring框架的核心功能之一,通过动态代理在不修改原代码的情况下注入新功能。文章详细解释了连接点、切入点、通知、切面等关键概念,并列举了前置通知、后置通知、最终通知、异常通知和环绕通知五种通知类型。
28 1
|
1月前
|
Java BI 调度
Java Spring的定时任务的配置和使用
遵循上述步骤,你就可以在Spring应用中轻松地配置和使用定时任务,满足各种定时处理需求。
125 1
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
46 0
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于XML配置的程序--源码解析
手动开发-简单的Spring基于XML配置的程序--源码解析
80 0
|
1月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
37 0
|
2月前
|
SpringCloudAlibaba API 开发者
新版-SpringCloud+SpringCloud Alibaba
新版-SpringCloud+SpringCloud Alibaba
下一篇
无影云桌面