Spring Cloud Zuul遗失的世界(三)

× 文章目录
  1. 一.Zuul Filter的抽象设计
    1. 1.1 自定义Zuul Filter
    2. 1.2 IZuulFilter代码
    3. 1.2 抽象类ZuulFilter代码
  2. 二.Spring Cloud Zuul的Filter管理
    1. 2.1 FilterFileManager
    2. 2.2 FilterLoader
    3. 2.3 FilterRegistry
    4. 2.4 DynamicCodeCompiler
  3. 三.参考文章

摘要: 接着上一篇《Spring Cloud Zuul遗失的世界(二)》,本文主要介绍Netflix Zuul core模块的Filter链的设计和Fifter Loader和Filter Manager相关代码的设计与分析。

一.Zuul Filter的抽象设计

1.1 自定义Zuul Filter

当我们使用Spring Cloud Zuul都会直接继承ZuulFilter,覆盖实现抽象类中定义的方法,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class PreFilter extends ZuulFilter {
private static final Logger logger = LoggerFactory.getLogger(PreFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
String token = ctx.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
String labels = TOKEN_LABEL_MAP.get(token);
logger.info("label: " + labels);
CoreHeaderInterceptor.initHystrixRequestContext(labels); // zuul本身调用微服务
ctx.addZuulRequestHeader(CoreHeaderInterceptor.HEADER_LABEL, labels); // 传递给后续微服务
return null;
}
}

1.2 IZuulFilter代码

定义 IZuulFilter interface,共同的常用方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface IZuulFilter {
/**
* a "true" return from this method means that the run() method should be invoked
*
* @return true if the run() method should be invoked. false will not invoke the run() method
*/
boolean shouldFilter();
/**
* if shouldFilter() is true, this method will be invoked. this method is the core method of a ZuulFilter
*
* @return Some arbitrary artifact may be returned. Current implementation ignores it.
*/
Object run();
}

1.2 抽象类ZuulFilter代码

抽象类ZuulFilter实现IZuulFilter,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public abstract class ZuulFilter implements IZuulFilter, Comparable<ZuulFilter> {
private final DynamicBooleanProperty filterDisabled =
DynamicPropertyFactory.getInstance().getBooleanProperty(disablePropertyName(), false);
//filter类型
abstract public String filterType();
// Filter的执行顺序
abstract public int filterOrder();
//是否是静态Filter
public boolean isStaticFilter() {
return true;
}
public String disablePropertyName() {
return "zuul." + this.getClass().getSimpleName() + "." + filterType() + ".disable";
}
//Filter是否启动
public boolean isFilterDisabled() {
return filterDisabled.get();
}
public ZuulFilterResult runFilter() {
ZuulFilterResult zr = new ZuulFilterResult();
if (!isFilterDisabled()) {
if (shouldFilter()) {
Tracer t = TracerFactory.instance().startMicroTracer("ZUUL::" + this.getClass().getSimpleName());
try {
Object res = run();
zr = new ZuulFilterResult(res, ExecutionStatus.SUCCESS);
} catch (Throwable e) {
t.setName("ZUUL::" + this.getClass().getSimpleName() + " failed");
zr = new ZuulFilterResult(ExecutionStatus.FAILED);
zr.setException(e);
} finally {
t.stopAndLog();
}
} else {
zr = new ZuulFilterResult(ExecutionStatus.SKIPPED);
}
}
return zr;
}
public int compareTo(ZuulFilter filter) {
return Integer.compare(this.filterOrder(), filter.filterOrder());
}
}

二.Spring Cloud Zuul的Filter管理

zuul支持动加载Filter类文件。实现原理是监控存放Filter文件的目录,定期扫描这些目录,如果发现有新Filter源码文件或者Filter源码文件有改动,则对文件进行编译加载。目前zuul支持使用Groovy编写的Filter。

2.1 FilterFileManager

groovy的文件filter加载,通过FilterFileManager,开启一个线程,开始轮询GroovyFilterFile的目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
public class FilterFileManager {
private static final Logger LOG = LoggerFactory.getLogger(FilterFileManager.class);
String[] aDirectories;
int pollingIntervalSeconds;
Thread poller;
boolean bRunning = true;
static FilenameFilter FILENAME_FILTER;
static FilterFileManager INSTANCE;
private FilterFileManager() {
}
public static void setFilenameFilter(FilenameFilter filter) {
FILENAME_FILTER = filter;
}
/**
* Initialized the GroovyFileManager.
*
* @param pollingIntervalSeconds the polling interval in Seconds
* @param directories Any number of paths to directories to be polled may be specified
* @throws IOException
* @throws IllegalAccessException
* @throws InstantiationException
*/
public static void init(int pollingIntervalSeconds, String... directories) throws Exception, IllegalAccessException, InstantiationException {
if (INSTANCE == null) INSTANCE = new FilterFileManager();
INSTANCE.aDirectories = directories;
INSTANCE.pollingIntervalSeconds = pollingIntervalSeconds;
INSTANCE.manageFiles();
INSTANCE.startPoller();
}
public static FilterFileManager getInstance() {
return INSTANCE;
}
/**
* Shuts down the poller
*/
public static void shutdown() {
INSTANCE.stopPoller();
}
void stopPoller() {
bRunning = false;
}
// 开启一个线程,开始轮询
void startPoller() {
poller = new Thread("GroovyFilterFileManagerPoller") {
public void run() {
while (bRunning) {
try {
sleep(pollingIntervalSeconds * 1000);
manageFiles();
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
poller.setDaemon(true);
poller.start();
}
/**
* Returns the directory File for a path. A Runtime Exception is thrown if the directory is in valid
*
* @param sPath
* @return a File representing the directory path
*/
public File getDirectory(String sPath) {
File directory = new File(sPath);
if (!directory.isDirectory()) {
URL resource = FilterFileManager.class.getClassLoader().getResource(sPath);
try {
directory = new File(resource.toURI());
} catch (Exception e) {
LOG.error("Error accessing directory in classloader. path=" + sPath, e);
}
if (!directory.isDirectory()) {
throw new RuntimeException(directory.getAbsolutePath() + " is not a valid directory");
}
}
return directory;
}
/**
* Returns a List<File> of all Files from all polled directories
*
* @return
*/
List<File> getFiles() {
List<File> list = new ArrayList<File>();
for (String sDirectory : aDirectories) {
if (sDirectory != null) {
File directory = getDirectory(sDirectory);
File[] aFiles = directory.listFiles(FILENAME_FILTER);
if (aFiles != null) {
list.addAll(Arrays.asList(aFiles));
}
}
}
return list;
}
/**
* puts files into the FilterLoader. The FilterLoader will only addd new or changed filters
*
* @param aFiles a List<File>
* @throws IOException
* @throws InstantiationException
* @throws IllegalAccessException
*/
void processGroovyFiles(List<File> aFiles) throws Exception, InstantiationException, IllegalAccessException {
for (File file : aFiles) {
//更新则通过FilterLoader.putFilter()置入FilterRegistr
FilterLoader.getInstance().putFilter(file);
}
}
//每次轮询,处理目录内的所有*.groovy文件,即调用FilterLoader.getInstance().putFilter(file);
void manageFiles() throws Exception, IllegalAccessException, InstantiationException {
List<File> aFiles = getFiles();
processGroovyFiles(aFiles);
}
}

2.2 FilterLoader

com.netflix.zuul.FilterLoader,编译、加载filter文件,并且检查源文件是否有变更,除此之外,它还按照filterType组织并维护List

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
public class FilterLoader {
final static FilterLoader INSTANCE = new FilterLoader();
private static final Logger LOG = LoggerFactory.getLogger(FilterLoader.class);
private final ConcurrentHashMap<String, Long> filterClassLastModified = new ConcurrentHashMap<String, Long>();
private final ConcurrentHashMap<String, String> filterClassCode = new ConcurrentHashMap<String, String>();
private final ConcurrentHashMap<String, String> filterCheck = new ConcurrentHashMap<String, String>();
private final ConcurrentHashMap<String, List<ZuulFilter>> hashFiltersByType = new ConcurrentHashMap<String, List<ZuulFilter>>();
private FilterRegistry filterRegistry = FilterRegistry.instance();
static DynamicCodeCompiler COMPILER;
static FilterFactory FILTER_FACTORY = new DefaultFilterFactory();
/**
* Sets a Dynamic Code Compiler
*
* @param compiler
*/
public void setCompiler(DynamicCodeCompiler compiler) {
COMPILER = compiler;
}
// overidden by tests
public void setFilterRegistry(FilterRegistry r) {
this.filterRegistry = r;
}
/**
* Sets a FilterFactory
*
* @param factory
*/
public void setFilterFactory(FilterFactory factory) {
FILTER_FACTORY = factory;
}
/**
* @return Singleton FilterLoader
*/
public static FilterLoader getInstance() {
return INSTANCE;
}
/**
* Given source and name will compile and store the filter if it detects that the filter code has changed or
* the filter doesn't exist. Otherwise it will return an instance of the requested ZuulFilter
*
* @param sCode source code
* @param sName name of the filter
* @return the ZuulFilter
* @throws IllegalAccessException
* @throws InstantiationException
*/
public ZuulFilter getFilter(String sCode, String sName) throws Exception {
if (filterCheck.get(sName) == null) {
filterCheck.putIfAbsent(sName, sName);
if (!sCode.equals(filterClassCode.get(sName))) {
LOG.info("reloading code " + sName);
filterRegistry.remove(sName);
}
}
ZuulFilter filter = filterRegistry.get(sName);
if (filter == null) {
Class clazz = COMPILER.compile(sCode, sName);
if (!Modifier.isAbstract(clazz.getModifiers())) {
filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
}
}
return filter;
}
/**
* @return the total number of Zuul filters
*/
public int filterInstanceMapSize() {
return filterRegistry.size();
}
/**
* From a file this will read the ZuulFilter source code, compile it, and add it to the list of current filters
* a true response means that it was successful.
*
* @param file
* @return true if the filter in file successfully read, compiled, verified and added to Zuul
* @throws IllegalAccessException
* @throws InstantiationException
* @throws IOException
*/
public boolean putFilter(File file) throws Exception {
String sName = file.getAbsolutePath() + file.getName();
// 如果文件在上次加载后发生了变化,重新编译加载
if (filterClassLastModified.get(sName) != null && (file.lastModified() != filterClassLastModified.get(sName))) {
LOG.debug("reloading filter " + sName);
filterRegistry.remove(sName);
}
ZuulFilter filter = filterRegistry.get(sName);
if (filter == null) {
// 编译、加载文件
Class clazz = COMPILER.compile(file);
if (!Modifier.isAbstract(clazz.getModifiers())) {
filter = (ZuulFilter) FILTER_FACTORY.newInstance(clazz);
// 清空filter.filterType()类型的List<Filter>缓存,重新构建
List<ZuulFilter> list = hashFiltersByType.get(filter.filterType());
if (list != null) {
//重新构建某种类型Filter的List
hashFiltersByType.remove(filter.filterType()); //rebuild this list
}
//向Filter Registry放入新的Filter
filterRegistry.put(file.getAbsolutePath() + file.getName(), filter);
filterClassLastModified.put(sName, file.lastModified());
return true;
}
}
return false;
}
/**
* 根据Filter类型返回同一类型的Filter
*
* @param filterType
* @return a List<ZuulFilter>
*/
public List<ZuulFilter> getFiltersByType(String filterType) {
List<ZuulFilter> list = hashFiltersByType.get(filterType);
if (list != null) return list;
list = new ArrayList<ZuulFilter>();
Collection<ZuulFilter> filters = filterRegistry.getAllFilters();
for (Iterator<ZuulFilter> iterator = filters.iterator(); iterator.hasNext(); ) {
ZuulFilter filter = iterator.next();
if (filter.filterType().equals(filterType)) {
list.add(filter);
}
}
//根据
Collections.sort(list);
hashFiltersByType.putIfAbsent(filterType, list);
return list;
}
}

把Groovy源码进行编译并加载进jvm里。

2.3 FilterRegistry

com.netflix.zuul.filters.FilterRegistry可以理解为就是用ConcurrentHashMap,在运行过程中存储Filter的数据结构,进行Put或Revome操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class FilterRegistry {
private static final FilterRegistry INSTANCE = new FilterRegistry();
public static final FilterRegistry instance() {
return INSTANCE;
}
private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();
private FilterRegistry() {
}
public ZuulFilter remove(String key) {
return this.filters.remove(key);
}
public ZuulFilter get(String key) {
return this.filters.get(key);
}
public void put(String key, ZuulFilter filter) {
this.filters.putIfAbsent(key, filter);
}
public int size() {
return this.filters.size();
}
public Collection<ZuulFilter> getAllFilters() {
return this.filters.values();
}
}

2.4 DynamicCodeCompiler

com.netflix.zuul.DynamicCodeCompiler.java,主要是一个接口,定义两种加载编译源码的方法:

1
2
3
4
5
public interface DynamicCodeCompiler {
Class compile(String sCode, String sName) throws Exception;
Class compile(File file) throws Exception;
}

三.参考文章

http://microservices.io/patterns/apigateway.html

如果您觉得文章不错,可以打赏我喝一杯咖啡!