参考:https://blog.csdn.net/u014091123/article/details/75433656 
https://blog.csdn.net/u013815546/article/details/68944039

Zuul是Netflix开源的微服务网关,他的核心是一系列的过滤器,通过这些过滤器我们可以轻松的实现服务的访问认证、限流、路由、负载、熔断等功能。

基于对已有项目代码零侵入的需求,本文没有将zuul网关项目注册到eureka中心,而是将zuul与springboot结合作为一个独立的项目进行请求转发,因此本项目是非spring cloud架构。

开始编写zuul网关项目 
首先,新建一个spring boot项目。加入zuul依赖,开启@EnableZuulProxy注解。 
pom.xml

 <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
<version>1.4.4.RELEASE</version>
</dependency>

application.properties

 server.port=8090
eureka.client.enable=false
zuul.ribbon.eager-load.enabled=true zuul.SendErrorFilter.post.disable=true

由于后续会使用到动态路由,所以这里我们并不需要在application.properties中做网关地址转发映射。

SpringBootZuulApplication.java

 package com.syher.zuul;

 import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.syher.zuul.core.zuul.router.PropertiesRouter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.cloud.netflix.zuul.RoutesRefreshedEvent;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.ComponentScan; import java.io.File;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; /**
* @author braska
* @date 2018/06/25.
**/
@EnableAutoConfiguration
@EnableZuulProxy
@ComponentScan(basePackages = {
"com.syher.zuul.core",
"com.syher.zuul.service"
})
public class SpringBootZuulApplication implements CommandLineRunner {
@Autowired
ApplicationEventPublisher publisher;
@Autowired
RouteLocator routeLocator; private ScheduledExecutorService executor;
private Long lastModified = 0L;
private boolean instance = true; public static void main(String[] args) {
SpringApplication.run(SpringBootZuulApplication.class, args);
} @Override
public void run(String... args) throws Exception {
executor = Executors.newSingleThreadScheduledExecutor(
new ThreadFactoryBuilder().setNameFormat("properties read.").build()
);
executor.scheduleWithFixedDelay(() -> publish(), 0, 1, TimeUnit.SECONDS);
} private void publish() {
if (isPropertiesModified()) {
publisher.publishEvent(new RoutesRefreshedEvent(routeLocator));
}
} private boolean isPropertiesModified() {
File file = new File(this.getClass().getClassLoader().getResource(PropertiesRouter.PROPERTIES_FILE).getPath());
if (instance) {
instance = false;
return false;
}
if (file.lastModified() > lastModified) {
lastModified = file.lastModified();
return true;
}
return false;
}
}

一、自定义过滤器

自定义zuul过滤器比较简单。我们先讲过滤器。 
zuul过滤器分为pre、route、post、error四种类型。作用我就不详细讲了,网上资料一大把。本文主要写路由前的过滤,即pre类型。 
要自定义一个过滤器,只需要要继承ZuulFilter,然后指定过滤类型、过滤顺序、是否执行这个过滤器、过滤内容就OK了。

为了便于扩展,这里用到了模板模式。 
AbstractZuulFilter.java

 package com.syher.zuul.core.zuul.filter;

 import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.syher.zuul.core.zuul.ContantValue; /**
* @author braska
* @date 2018/06/29.
**/
public abstract class AbstractZuulFilter extends ZuulFilter { protected RequestContext context; @Override
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return (boolean) (ctx.getOrDefault(ContantValue.NEXT_FILTER, true));
} @Override
public Object run() {
context = RequestContext.getCurrentContext();
return doRun();
} public abstract Object doRun(); public Object fail(Integer code, String message) {
context.set(ContantValue.NEXT_FILTER, false);
context.setSendZuulResponse(false);
context.getResponse().setContentType("text/html;charset=UTF-8");
context.setResponseStatusCode(code);
context.setResponseBody(String.format("{\"result\":\"%s!\"}", message));
return null;
} public Object success() {
context.set(ContantValue.NEXT_FILTER, true);
return null;
}
}

定义preFilter的抽象类,继承AbstractZuulFilter。指定pre类型,之后所有的pre过滤器都可以继承这个抽象类。 
AbstractPreZuulFilter.java

 package com.syher.zuul.core.zuul.filter.pre;

 import com.syher.zuul.core.zuul.FilterType;
import com.syher.zuul.core.zuul.filter.AbstractZuulFilter; /**
* @author braska
* @date 2018/06/29.
**/
public abstract class AbstractPreZuulFilter extends AbstractZuulFilter {
@Override
public String filterType() {
return FilterType.pre.name();
}
}

接着编写具体一个具体的过滤器,比如限流。 
RateLimiterFilter.java

 package com.syher.zuul.core.zuul.filter.pre;

 import com.google.common.util.concurrent.RateLimiter;
import com.syher.zuul.core.zuul.FilterOrder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import javax.servlet.http.HttpServletRequest; /**
* @author braska
* @date 2018/06/29.
**/
public class RateLimiterFilter extends AbstractPreZuulFilter { private static final Logger LOGGER = LoggerFactory.getLogger(RateLimiterFilter.class); /**
* 每秒允许处理的量是50
*/
RateLimiter rateLimiter = RateLimiter.create(50); @Override
public int filterOrder() {
return FilterOrder.RATE_LIMITER_ORDER;
} @Override
public Object doRun() {
HttpServletRequest request = context.getRequest();
String url = request.getRequestURI();
if (rateLimiter.tryAcquire()) {
return success();
} else {
LOGGER.info("rate limit:{}", url);
return fail(401, String.format("rate limit:{}", url));
}
}
}

其他类型的过滤器也一样。创建不同的抽象类,比如AbstractPostZuulFilter,指定filterType,然后具体的postFilter只要继承该抽象类即可。

最后,将过滤器托管给spring。 
ZuulConfigure.java

 package com.syher.zuul.core.config;

 import com.netflix.loadbalancer.IRule;
import com.netflix.zuul.ZuulFilter;
import com.syher.zuul.core.ribbon.ServerLoadBalancerRule;
import com.syher.zuul.core.zuul.filter.pre.RateLimiterFilter;
import com.syher.zuul.core.zuul.filter.pre.TokenAccessFilter;
import com.syher.zuul.core.zuul.filter.pre.UserRightFilter;
import com.syher.zuul.core.zuul.router.PropertiesRouter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* @author braska
* @date 2018/07/05.
**/
@Configuration
public class ZuulConfigure { @Autowired
ZuulProperties zuulProperties;
@Autowired
ServerProperties server; /**
* 动态路由
* @return
*/
@Bean
public PropertiesRouter propertiesRouter() {
return new PropertiesRouter(this.server.getServletPrefix(), this.zuulProperties);
} /**
* 动态负载
* @return
*/
@Bean
public IRule loadBalance() {
return new ServerLoadBalancerRule();
} /**
* 自定义过滤器
* @return
*/
@Bean
public ZuulFilter rateLimiterFilter() {
return new RateLimiterFilter();
}
}

二、动态路由

接着写动态路由。动态路由需要配置可持久化且能动态刷新。 
zuul默认使用的路由是SimpleRouteLocator,不具备动态刷新的效果。DiscoveryClientRouteLocator具备刷新功能,但是需要已有的项目将服务注册到eureka,这不符合已有项目代码零侵入的需求所以排除。那么还有个办法就是自定义路由然后实现RefreshableRouteLocator类。

部分代码如下: 
AbstractDynamicRouter.java

 package com.syher.zuul.core.zuul.router;

 import com.syher.zuul.core.zuul.entity.BasicRoute;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.cloud.netflix.zuul.filters.RefreshableRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; /**
* @author braska
* @date 2018/07/02.
**/
public abstract class AbstractDynamicRouter extends SimpleRouteLocator implements RefreshableRouteLocator { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractDynamicRouter.class); public AbstractDynamicRouter(String servletPath, ZuulProperties properties) {
super(servletPath, properties);
} @Override
public void refresh() {
doRefresh();
} @Override
protected Map<String, ZuulProperties.ZuulRoute> locateRoutes() {
LinkedHashMap<String, ZuulProperties.ZuulRoute> routes = new LinkedHashMap<String, ZuulProperties.ZuulRoute>();
routes.putAll(super.locateRoutes()); List<BasicRoute> results = readRoutes(); for (BasicRoute result : results) {
if (StringUtils.isEmpty(result.getPath()) ) {
continue;
}
ZuulProperties.ZuulRoute zuulRoute = new ZuulProperties.ZuulRoute();
try {
BeanUtils.copyProperties(result, zuulRoute);
} catch (Exception e) {
LOGGER.error("=============load zuul route info from db with error==============", e);
}
routes.put(zuulRoute.getPath(), zuulRoute);
}
return routes;
} /**
* 读取路由信息
* @return
*/
protected abstract List<BasicRoute> readRoutes();
}

由于本人比较懒。不想每次写个demo都要重新配置一大堆数据库信息。所以本文很多数据比如路由信息、比如负载策略。要么写在文本里面,要么直接java代码构造。 
本demo的路由信息就是从properties里面读取。嗯,继承AbstractDynamicRouter即可。 
PropertiesRouter.java

 package com.syher.zuul.core.zuul.router;

 import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.syher.zuul.common.Context;
import com.syher.zuul.core.zuul.entity.BasicRoute;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cloud.netflix.zuul.filters.ZuulProperties; import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.stream.Collectors; /**
* @author braska
* @date 2018/07/02.
**/
public class PropertiesRouter extends AbstractDynamicRouter { private static final Logger LOGGER = LoggerFactory.getLogger(PropertiesRouter.class);
public static final String PROPERTIES_FILE = "router.properties";
private static final String ZUUL_ROUTER_PREFIX = "zuul.routes"; public PropertiesRouter(String servletPath, ZuulProperties properties) {
super(servletPath, properties);
} @Override
protected List<BasicRoute> readRoutes() {
List<BasicRoute> list = Lists.newArrayListWithExpectedSize(3);
try {
Properties prop = new Properties();
prop.load(
this.getClass().getClassLoader().getResourceAsStream(PROPERTIES_FILE)
); Context context = new Context(new HashMap<>((Map) prop));
Map<String, String> data = context.getSubProperties(ZUUL_ROUTER_PREFIX);
List<String> ids = data.keySet().stream().map(s -> s.substring(0, s.indexOf("."))).distinct().collect(Collectors.toList());
ids.stream().forEach(id -> {
Map<String, String> router = context.getSubProperties(String.join(".", ZUUL_ROUTER_PREFIX, id)); String path = router.get("path");
path = path.startsWith("/") ? path : "/" + path; String serviceId = router.getOrDefault("serviceId", null);
String url = router.getOrDefault("url", null); BasicRoute basicRoute = new BasicRoute();
basicRoute.setId(id);
basicRoute.setPath(path);
basicRoute.setUrl(router.getOrDefault("url", null));
basicRoute.setServiceId((StringUtils.isBlank(url) && StringUtils.isBlank(serviceId)) ? id : serviceId);
basicRoute.setRetryable(Boolean.parseBoolean(router.getOrDefault("retry-able", "false")));
basicRoute.setStripPrefix(Boolean.parseBoolean(router.getOrDefault("strip-prefix", "false")));
list.add(basicRoute);
});
} catch (IOException e) {
LOGGER.info("error to read " + PROPERTIES_FILE + " :{}", e);
}
return list;
}
}

既然是动态路由实时刷新,那肯定需要一个定时器定时监控properties文件。所以我在启动类SpringBootZuulApplication加了个定时器监控properties是否发生过变更(之前有疑问的现在可以解惑了)。一旦文件被修改过就重新发布一下, 然后会触发routeLocator的refresh方法。

 public void publish() {
if (isPropertiesModified()) {
publisher.publishEvent(new RoutesRefreshedEvent(routeLocator));
}
}

当然,如果是从数据库或者其他地方比如redis读取就不需要用到定时器,只要在增删改的时候直接publish就好了。

最后,记得PropertiesRouter类交由spring托管(在ZuulConfigure类中配置bean)。

router.properties文件:

 zuul.routes.dashboard.path=/**
zuul.routes.dashboard.strip-prefix=true ##不使用动态负载需指定url
##zuul.routes.dashboard.url=http://localhost:9000/
##zuul服务部署后,动态增加网关映射,无需重启即可实时路由到新的网关
##zuul.routes.baidu.path=/**

三、动态负载

负载也算比较简单,复杂点的是写负载算法。 
动态负载主要分两个步骤: 
1、根据网关项目配置的host和port去数据库(我是java直接造的数据)查找负载策略,比如轮询、比如随机、比如iphash等等。 
2、根据策略结合每台服务器分配的权重选出合适的服务。

实现动态负载需要自定义rule类然后继承AbstractLoadBalancerRule类。 
首先看负载策略的选择: 
ServerLoadBalancerRule.java

 package com.syher.zuul.core.ribbon;

 import com.google.common.base.Preconditions;
import com.netflix.client.config.IClientConfig;
import com.netflix.loadbalancer.AbstractLoadBalancerRule;
import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.syher.zuul.common.util.SystemUtil;
import com.syher.zuul.core.ribbon.balancer.LoadBalancer;
import com.syher.zuul.core.ribbon.balancer.RandomLoadBalancer;
import com.syher.zuul.core.ribbon.balancer.RoundLoadBalancer;
import com.syher.zuul.entity.GatewayAddress;
import com.syher.zuul.service.GatewayService;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; /**
* @author braska
* @date 2018/07/05.
**/
public class ServerLoadBalancerRule extends AbstractLoadBalancerRule { private static final Logger LOGGER = LoggerFactory.getLogger(ServerLoadBalancerRule.class); @Value("${server.host:127.0.0.1}")
private String host;
@Value("${server.port:8080}")
private Integer port; @Autowired
private GatewayService gatewayService; @Override
public void initWithNiwsConfig(IClientConfig iClientConfig) {
} @Override
public Server choose(Object key) {
return getServer(getLoadBalancer(), key);
} private Server getServer(ILoadBalancer loadBalancer, Object key) {
if (StringUtils.isBlank(host)) {
host = SystemUtil.ipList().get(0);
}
//Preconditions.checkArgument(host != null, "server.host must be specify.");
//Preconditions.checkArgument(port != null, "server.port must be specify."); GatewayAddress address = gatewayService.getByHostAndPort(host, port);
if (address == null) { //这里的逻辑可以改,找不到网关配置信息可以指定默认的负载策略
LOGGER.error(String.format("must be config a gateway info for the server[%s:%s].", host, String.valueOf(port)));
return null;
} LoadBalancer balancer = LoadBalancerFactory.build(address.getFkStrategyId()); return balancer.chooseServer(loadBalancer);
} static class LoadBalancerFactory { public static LoadBalancer build(String strategy) {
GatewayAddress.StrategyType type = GatewayAddress.StrategyType.of(strategy);
switch (type) {
case ROUND:
return new RoundLoadBalancer();
case RANDOM:
return new RandomLoadBalancer();
default:
return null;
}
}
}
}

然后是负载算法接口代码。 
LoadBalancer.java

 package com.syher.zuul.core.ribbon.balancer;

 import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server; /**
* @author braska
* @date 2018/07/06.
**/
public interface LoadBalancer { /**
* choose a loadBalancer
* @param loadBalancer
* @return
*/
Server chooseServer(ILoadBalancer loadBalancer);
}

定义抽象类,实现LoadBalancer接口 
AbstractLoadBalancer.java

 package com.syher.zuul.core.ribbon.balancer;

 import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.syher.zuul.core.SpringContext;
import com.syher.zuul.service.ServerService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* @author braska
* @date 2018/07/06.
**/
public abstract class AbstractLoadBalancer implements LoadBalancer {
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractLoadBalancer.class);
protected ServerService serverService; @Override
public Server chooseServer(ILoadBalancer loadBalancer) {
this.serverService = SpringContext.getBean(ServerService.class);
Server server = choose(loadBalancer);
if (server != null) {
LOGGER.info(String.format("the server[%s:%s] has been select.", server.getHost(), server.getPort()));
} else {
LOGGER.error("could not find any server.");
}
return server;
} public abstract Server choose(ILoadBalancer loadBalancer);
}

轮询负载算法 
RoundLoadBalancer.java

 package com.syher.zuul.core.ribbon.balancer;

 import com.netflix.loadbalancer.ILoadBalancer;
import com.netflix.loadbalancer.Server;
import com.syher.zuul.common.Constant;
import com.syher.zuul.core.GlobalCache;
import com.syher.zuul.core.ribbon.LoadBalancerRuleUtil;
import com.syher.zuul.entity.ServerAddress; import java.util.List; /**
* 权重轮询
* 首次使用取最大权重的服务器。而后通过权重的不断递减,寻找适合的服务器。
* @author braska
* @date 2018/07/06.
**/
public class RoundLoadBalancer extends AbstractLoadBalancer { private Integer currentServer;
private Integer currentWeight;
private Integer maxWeight;
private Integer gcdWeight; @Override
public Server choose(ILoadBalancer loadBalancer) {
List<ServerAddress> addressList = serverService.getAvailableServer();
if (addressList != null && !addressList.isEmpty()) {
maxWeight = LoadBalancerRuleUtil.getMaxWeightForServers(addressList);
gcdWeight = LoadBalancerRuleUtil.getGCDForServers(addressList);
currentServer = Integer.parseInt(GlobalCache.instance().getOrDefault(Constant.CURRENT_SERVER_KEY, -1).toString());
currentWeight = Integer.parseInt(GlobalCache.instance().getOrDefault(Constant.CURRENT_WEIGHT_KEY, 0).toString()); Integer serverCount = addressList.size(); if (1 == serverCount) {
return new Server(addressList.get(0).getHost(), addressList.get(0).getPort());
} else {
while (true) {
currentServer = (currentServer + 1) % serverCount;
if (currentServer == 0) {
currentWeight = currentWeight - gcdWeight;
if (currentWeight <= 0) {
currentWeight = maxWeight;
if (currentWeight == 0) {
GlobalCache.instance().put(Constant.CURRENT_SERVER_KEY, currentServer);
GlobalCache.instance().put(Constant.CURRENT_WEIGHT_KEY, currentWeight);
Thread.yield();
return null;
}
}
} ServerAddress address = addressList.get(currentServer);
if (address.getWeight() >= currentWeight) {
GlobalCache.instance().put(Constant.CURRENT_SERVER_KEY, currentServer);
GlobalCache.instance().put(Constant.CURRENT_WEIGHT_KEY, currentWeight);
return new Server(address.getHost(), address.getPort());
}
}
} }
return null;
}
}

最后,ServerLoadBalancerRule交由spring托管。

至此,springboot+zuul实现自定义过滤器、动态路由、动态负载就都完成了。 
源码:https://github.com/rxiu/study-on-road/tree/master/trickle-gateway

springboot+zuul(一)------实现自定义过滤器、动态路由、动态负载。的更多相关文章

  1. Express全系列教程之(二):Express的路由以及动态路由

    一.Express路由简介 路由表示应用程序端点 (URI) 的定义以及响应客户端请求的方式.它包含一个请求方时(methods).路径(path)和路由匹配时的函数(callback); app.m ...

  2. vue路由--动态路由

    前面介绍的路由都是路径和组件一对一映射的 有时候需要多个路径映射到一个组件,这个组件根据参数的不同动态改变,这时候需要用到动态路由 动态路由这样定义路由路径: path: '/foo/:id'--可以 ...

  3. spring-cloud-gateway动态路由

    概述 线上项目发布一般有以下几种方案: 停机发布 蓝绿部署 滚动部署 灰度发布 停机发布 这种发布一般在夜里或者进行大版本升级的时候发布,因为需要停机,所以现在大家都在研究 Devops 方案. 蓝绿 ...

  4. 06 Vue路由简介,原理,实现及嵌套路由,动态路由

    路由概念 路由的本质就是一种对应关系,比如说我们在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源. 那么url地址和真实的资源之间就有一种对应的关系,就是路由. 路 ...

  5. Flask(5)- 动态路由

    前言 前面几篇文章讲的路由路径(rule)都是固定的,就是一个路径和一个视图函数绑定,当访问这条路径时会触发相应的处理函数 这样无法处理复杂的情况,比如常见的一个课程分类下有很多个课程,那么他们的 p ...

  6. Spring Cloud (十三) Zuul:静态路由、静态过滤器与动态路由的实现

    前言 本文起笔于2018-06-26周二,接了一个这周要完成的开发任务,需要先等其他人的接口,可能更新的会慢一些,还望大家见谅.这篇博客我们主要讲Spring Cloud Zuul.项目地址:我的gi ...

  7. SpringCloud系列——Zuul 动态路由

    前言 Zuul 是在Spring Cloud Netflix平台上提供动态路由,监控,弹性,安全等边缘服务的框架,是Netflix基于jvm的路由器和服务器端负载均衡器,相当于是设备和 Netflix ...

  8. 跟我学SpringCloud | 第十七篇:服务网关Zuul基于Apollo动态路由

    目录 SpringCloud系列教程 | 第十七篇:服务网关Zuul基于Apollo动态路由 Apollo概述 Apollo相比于Spring Cloud Config优势 工程实战 示例代码 Spr ...

  9. SpringCloud之初识Zuul(网关)---动态路由,权限验证

    通过前面的学习,使用Spring Cloud实现微服务的架构基本成型,大致是这样的: 我们使用Spring Cloud Netflix中的Eureka实现了服务注册中心以及服务注册与发现:而服务间通过 ...

随机推荐

  1. text 文本。

    //支持转义符"\".   <text/> 组件内只支持 <text/> 嵌套. //除了文本节点以外的其他节点都无法长按选中. <view clas ...

  2. C++二级指针第一种内存模型(指针数组)

    二级指针第一种内存模型(指针数组) 指针的输入特性:在主调函数里面分配内存,在被调用函数里面使用指针的输出特性:在被调用函数里面分配内存,主要是把运算结果甩出来 指针数组 在C语言和C++语言中,数组 ...

  3. noip第12课资料

  4. Greys--JVM异常诊断工具

    https://github.com/oldmanpushcart/greys-anatomy/wiki/greys-pdf 一.简介 我们平时在线上或者开发中会遇到各种性能.功能等问题,在运行过程中 ...

  5. C++ 中的异常机制分析

    C++异常机制概述 异常处理是C++的一项语言机制,用于在程序中处理异常事件.异常事件在C++中表示为异常对象.异常事件发生时,程序使用throw关键字抛出异常表达式,抛出点称为异常出现点,由操作系统 ...

  6. scikit-FEM-例2-用Morley元在方形区域上解板弯曲问题

    """ Author: kinnala Solve the Kirchhoff plate bending problem in a unit square with c ...

  7. [正则表达式] PHP 中使用正则表达式收集(2016/01/08 - )

    // 1. 过滤字符串中src 属性为空的img 标签 $filterBack = preg_replace("/<img[^<>]*src\=[\'\"][\' ...

  8. Android-Java-封装

    先看一个未封装的Demo案例一: package android.java.oop03; class Person { int age; } public class PottingDemo { pu ...

  9. 分形之拆分三角形(Split Triangle)

    前面讲了谢尔宾斯基三角形,它是不停地将一个三角形拆分三个与之相似的三角形.这一节给大家展示的图形是将一个等腰钝角三角形不停地拆分两个与之相似的三角形. 核心代码: static void SplitT ...

  10. [NewCode 4] 替换空格

    题目描述 请实现一个函数,将一个字符串中的空格替换成"%20".例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy. 最直接的方式, ...