public class simpleroutelocator implements routelocator {
//配置文件中的路由信息配置
private zuulproperties properties;
//路径正则配置器,即作用于path:/books/**
private pathmatcher pathmatcher = new antpathmatcher();
private string dispatcherservletpath = "/";
private string zuulservletpath;
private atomicreference<map<string, zuulroute>> routes = new atomicreference<>();
public simpleroutelocator(string servletpath, zuulproperties properties) {
this.properties = properties;
if (servletpath != null && stringutils.hastext(servletpath)) {
this.dispatcherservletpath = servletpath;
}
this.zuulservletpath = properties.getservletpath();
}
//路由定位器和其他组件的交互,是最终把定位的routes以list的方式提供出去,核心实现
@override
public list<route> getroutes() {
if (this.routes.get() == null) {
this.routes.set(locateroutes());
}
list<route> values = new arraylist<>();
for (string url : this.routes.get().keyset()) {
zuulroute route = this.routes.get().get(url);
string path = route.getpath();
values.add(getroute(route, path));
}
return values;
}
@override
public collection<string> getignoredpaths() {
return this.properties.getignoredpatterns();
}
//这个方法在网关产品中也很重要,可以根据实际路径匹配到route来进行业务逻辑的操作,进行一些加工
@override
public route getmatchingroute(final string path) {
if (log.isdebugenabled()) {
log.debug("finding route for path: " + path);
}
if (this.routes.get() == null) {
this.routes.set(locateroutes());
}
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());
}
string adjustedpath = adjustpath(path);
zuulroute route = null;
if (!matchesignoredpatterns(adjustedpath)) {
for (entry<string, zuulroute> entry : this.routes.get().entryset()) {
string pattern = entry.getkey();
log.debug("matching pattern:" + pattern);
if (this.pathmatcher.match(pattern, adjustedpath)) {
route = entry.getvalue();
break;
}
}
}
if (log.isdebugenabled()) {
log.debug("route matched=" + route);
}
return getroute(route, adjustedpath);
}
private route getroute(zuulroute route, string path) {
if (route == null) {
return null;
}
string targetpath = path;
string prefix = this.properties.getprefix();
if (path.startswith(prefix) && this.properties.isstripprefix()) {
targetpath = path.substring(prefix.length());
}
if (route.isstripprefix()) {
int index = route.getpath().indexof("*") - 1;
if (index > 0) {
string routeprefix = route.getpath().substring(0, index);
targetpath = targetpath.replacefirst(routeprefix, "");
prefix = prefix + routeprefix;
}
}
boolean retryable = this.properties.getretryable();
if (route.getretryable() != null) {
retryable = route.getretryable();
}
return new route(route.getid(), targetpath, route.getlocation(), prefix,
retryable,
route.iscustomsensitiveheaders() ? route.getsensitiveheaders() : null);
}
//注意这个类并没有实现refresh接口,但是却提供了一个protected级别的方法,旨在让子类不需要重复维护一个private atomicreference<map<string, zuulroute>> routes = new atomicreference<>();也可以达到刷新的效果
protected void dorefresh() {
this.routes.set(locateroutes());
}
//具体就是在这儿定位路由信息的,我们之后从数据库加载路由信息,主要也是从这儿改写
/**
* compute a map of path pattern to route. the default is just a static map from the
* {@link zuulproperties}, but subclasses can add dynamic calculations.
*/
protected map<string, zuulroute> locateroutes() {
linkedhashmap<string, zuulroute> routesmap = new linkedhashmap<string, zuulroute>();
for (zuulroute route : this.properties.getroutes().values()) {
routesmap.put(route.getpath(), route);
}
return routesmap;
}
protected boolean matchesignoredpatterns(string path) {
for (string pattern : this.properties.getignoredpatterns()) {
log.debug("matching ignored pattern:" + pattern);
if (this.pathmatcher.match(pattern, path)) {
log.debug("path " + path + " matches ignored pattern " + pattern);
return true;
}
}
return false;
}
private string adjustpath(final string path) {
string adjustedpath = path;
if (requestutils.isdispatcherservletrequest()
&& stringutils.hastext(this.dispatcherservletpath)) {
if (!this.dispatcherservletpath.equals("/")) {
adjustedpath = path.substring(this.dispatcherservletpath.length());
log.debug("stripped dispatcherservletpath");
}
}
else if (requestutils.iszuulservletrequest()) {
if (stringutils.hastext(this.zuulservletpath)
&& !this.zuulservletpath.equals("/")) {
adjustedpath = path.substring(this.zuulservletpath.length());
log.debug("stripped zuulservletpath");
}
}
else {
// do nothing
}
log.debug("adjustedpath=" + path);
return adjustedpath;
}
}
重写过后的自定义路由定位器如下:
public class customroutelocator extends simpleroutelocator implements refreshableroutelocator{
public final static logger logger = loggerfactory.getlogger(customroutelocator.class);
private jdbctemplate jdbctemplate;
private zuulproperties properties;
public void setjdbctemplate(jdbctemplate jdbctemplate){
this.jdbctemplate = jdbctemplate;
}
public customroutelocator(string servletpath, zuulproperties properties) {
super(servletpath, properties);
this.properties = properties;
logger.info("servletpath:{}",servletpath);
}
//父类已经提供了这个方法,这里写出来只是为了说明这一个方法很重要!!!
// @override
// protected void dorefresh() {
// super.dorefresh();
// }
@override
public void refresh() {
dorefresh();
}
@override
protected map<string, zuulroute> locateroutes() {
linkedhashmap<string, zuulroute> routesmap = new linkedhashmap<string, zuulroute>();
//从application.properties中加载路由信息
routesmap.putall(super.locateroutes());
//从db中加载路由信息
routesmap.putall(locateroutesfromdb());
//优化一下配置
linkedhashmap<string, zuulroute> values = new linkedhashmap<>();
for (map.entry<string, zuulroute> entry : routesmap.entryset()) {
string path = 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());
}
return values;
}
private map<string, zuulroute> locateroutesfromdb(){
map<string, zuulroute> routes = new linkedhashmap<>();
list<zuulroutevo> results = jdbctemplate.query("select * from gateway_api_define where enabled = true ",new beanpropertyrowmapper<>(zuulroutevo.class));
for (zuulroutevo result : results) {
if(org.apache.commons.lang3.stringutils.isblank(result.getpath()) || org.apache.commons.lang3.stringutils.isblank(result.geturl()) ){
continue;
}
zuulroute zuulroute = new zuulroute();
try {
org.springframework.beans.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;
}
public static class zuulroutevo {
/**
* the id of the route (the same as its map key by default).
*/
private string id;
/**
* the path (pattern) for the route, e.g. /foo/**.
*/
private string path;
/**
* the service id (if any) to map to this route. you can specify a physical url or
* a service, but not both.
*/
private string serviceid;
/**
* a full physical url to map to the route. an alternative is to use a service id
* and service discovery to find the physical address.
*/
private string url;
/**
* flag to determine whether the prefix for this route (the path, minus pattern
* patcher) should be stripped before forwarding.
*/
private boolean stripprefix = true;
/**
* flag to indicate that this route should be retryable (if supported). generally
* retry requires a service id and ribbon.
*/
private boolean retryable;
private boolean enabled;
public string getid() {
return id;
}
public void setid(string id) {
this.id = id;
}
public string getpath() {
return path;
}
public void setpath(string path) {
this.path = path;
}
public string getserviceid() {
return serviceid;
}
public void setserviceid(string serviceid) {
this.serviceid = serviceid;
}
public string geturl() {
return url;
}
public void seturl(string url) {
this.url = url;
}
public boolean isstripprefix() {
return stripprefix;
}
public void setstripprefix(boolean stripprefix) {
this.stripprefix = stripprefix;
}
public boolean getretryable() {
return retryable;
}
public void setretryable(boolean retryable) {
this.retryable = retryable;
}
public boolean getenabled() {
return enabled;
}
public void setenabled(boolean enabled) {
this.enabled = enabled;
}
}
}
配置这个自定义的路由定位器:
@configuration
public class customzuulconfig {
@autowired
zuulproperties zuulproperties;
@autowired
serverproperties server;
@autowired
jdbctemplate jdbctemplate;
@bean
public customroutelocator routelocator() {
customroutelocator routelocator = new customroutelocator(this.server.getservletprefix(), this.zuulproperties);
routelocator.setjdbctemplate(jdbctemplate);
return routelocator;
}
}