您的位置:首页 > 编程语言 > Java开发

playframework拦截器和热加载 源码浅析

2013-12-24 10:47 351 查看
继上一篇
playframework拦截器,这次来看看play怎么样实现拦截行为,同时看看play magic——hot swap的实现原理。(本文基于play1.2版本)

要想实现http拦截行为,需要在拿到request信息后在路由分发到具体实现类之前做点文章,我们把目光锁定到PlayHandler的messageReceived方法下,发现最终会执行这行代码:

Invoker.invoke(new NettyInvocation(request, response, ctx, nettyRequest, messageEvent));


NettyInvocation类实现了Runable接口,它是带有所有用户请求信息的一个线程。

跟进到invoke方法 :

/**
* Run the code in a new thread took from a thread pool.
* @param invocation The code to run
* @return The future object, to know when the task is completed
*/
public static Future<?> invoke(final Invocation invocation) {
Monitor monitor = MonitorFactory.getMonitor("Invoker queue size", "elmts.");
monitor.add(executor.getQueue().size());
invocation.waitInQueue = MonitorFactory.start("Waiting for execution");
return executor.submit(invocation);
}
上面的方法把带有完整信息的线程加入到executor线程池,线程池会决定是否立即执行该线程。

当执行submit(invocation)时会回调NettyInvocation的run方法:

@Override
public void run() {
try {
if (Logger.isTraceEnabled()) {
Logger.trace("run: begin");
}
super.run();
} catch (Exception e) {
serve500(e, ctx, nettyRequest);
}
if (Logger.isTraceEnabled()) {
Logger.trace("run: end");
}
}


进入 super.run(),看注释就知道,高潮来了:

/**
* It's time to execute.
*/
public void run() {
if (waitInQueue != null) {
waitInQueue.stop();
}
try {
preInit();
if (init()) {
before();
execute();
after();
onSuccess();
}
} catch (Suspend e) {
suspend(e);
after();
} catch (Throwable e) {
onException(e);
} finally {
_finally();
}
}
}
preInit()清理当前的线程池。

before() 和 after() 为自定义的classLoader和加载plugin做准备和善后工作

追踪execute()方法,发现最终会执行ActionInvoker的invoke方法

public static void invoke(Http.Request request, Http.Response response) {
Monitor monitor = null;

try {

resolve(request, response);
Method actionMethod = request.invokedMethod;

// 1. Prepare request params
Scope.Params.current().__mergeWith(request.routeArgs);

// add parameters from the URI query string
String encoding = Http.Request.current().encoding;
Scope.Params.current()._mergeWith(UrlEncodedParser.parseQueryString(new ByteArrayInputStream(request.querystring.getBytes(encoding))));

// 2. Easy debugging ...
if (Play.mode == Play.Mode.DEV) {
Controller.class.getDeclaredField("params").set(null, Scope.Params.current());
Controller.class.getDeclaredField("request").set(null, Http.Request.current());
Controller.class.getDeclaredField("response").set(null, Http.Response.current());
Controller.class.getDeclaredField("session").set(null, Scope.Session.current());
Controller.class.getDeclaredField("flash").set(null, Scope.Flash.current());
Controller.class.getDeclaredField("renderArgs").set(null, Scope.RenderArgs.current());
Controller.class.getDeclaredField("routeArgs").set(null, Scope.RouteArgs.current());
Controller.class.getDeclaredField("validation").set(null, Validation.current());
}

ControllerInstrumentation.stopActionCall();
Play.pluginCollection.beforeActionInvocation(actionMethod);

// Monitoring
monitor = MonitorFactory.start(request.action + "()");

// 3. Invoke the action
try {
// @Before
handleBefores(request);

// Action

Result actionResult = null;
String cacheKey = null;

// Check the cache (only for GET or HEAD)
if ((request.method.equals("GET") || request.method.equals("HEAD")) && actionMethod.isAnnotationPresent(CacheFor.class)) {
cacheKey = actionMethod.getAnnotation(CacheFor.class).id();
if ("".equals(cacheKey)) {
cacheKey = "urlcache:" + request.url + request.querystring;
}
actionResult = (Result) play.cache.Cache.get(cacheKey);
}

if (actionResult == null) {
ControllerInstrumentation.initActionCall();
try {
inferResult(invokeControllerMethod(actionMethod));
} catch(Result result) {
actionResult = result;
// Cache it if needed
if (cacheKey != null) {
play.cache.Cache.set(cacheKey, actionResult, actionMethod.getAnnotation(CacheFor.class).value());
}
} catch (InvocationTargetException ex) {
// It's a Result ? (expected)
if (ex.getTargetException() instanceof Result) {
actionResult = (Result) ex.getTargetException();
// Cache it if needed
if (cacheKey != null) {
play.cache.Cache.set(cacheKey, actionResult, actionMethod.getAnnotation(CacheFor.class).value());
}

} else {
// @Catch
Object[] args = new Object[]{ex.getTargetException()};
List<Method> catches = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Catch.class);
Collections.sort(catches, new Comparator<Method>() {

public int compare(Method m1, Method m2) {
Catch catch1 = m1.getAnnotation(Catch.class);
Catch catch2 = m2.getAnnotation(Catch.class);
return catch1.priority() - catch2.priority();
}
});
ControllerInstrumentation.stopActionCall();
for (Method mCatch : catches) {
Class[] exceptions = mCatch.getAnnotation(Catch.class).value();
if (exceptions.length == 0) {
exceptions = new Class[]{Exception.class};
}
for (Class exception : exceptions) {
if (exception.isInstance(args[0])) {
mCatch.setAccessible(true);
inferResult(invokeControllerMethod(mCatch, args));
break;
}
}
}

throw ex;
}
}
}

// @After
handleAfters(request);

monitor.stop();
monitor = null;

// OK, re-throw the original action result
if (actionResult != null) {
throw actionResult;
}

throw new NoResult();

} catch (IllegalAccessException ex) {
throw ex;
} catch (IllegalArgumentException ex) {
throw ex;
} catch (InvocationTargetException ex) {
// It's a Result ? (expected)
if (ex.getTargetException() instanceof Result) {
throw (Result) ex.getTargetException();
}
// Re-throw the enclosed exception
if (ex.getTargetException() instanceof PlayException) {
throw (PlayException) ex.getTargetException();
}
StackTraceElement element = PlayException.getInterestingStrackTraceElement(ex.getTargetException());
if (element != null) {
throw new JavaExecutionException(Play.classes.getApplicationClass(element.getClassName()), element.getLineNumber(), ex.getTargetException());
}
throw new JavaExecutionException(Http.Request.current().action, ex);
}

} catch (Result result) {

Play.pluginCollection.onActionInvocationResult(result);

// OK there is a result to apply
// Save session & flash scope now

Scope.Session.current().save();
Scope.Flash.current().save();

result.apply(request, response);

Play.pluginCollection.afterActionInvocation();

// @Finally
handleFinallies(request, null);

} catch (PlayException e) {
handleFinallies(request, e);
throw e;
} catch (Throwable e) {
handleFinallies(request, e);
throw new UnexpectedException(e);
} finally {
if (monitor != null) {
monitor.stop();
}
}
}
第37、74、105、152行分别定义了play的各种拦截顺序和行为,方法体中定义了自己具体的拦截规则。

private static void handleBefores(Http.Request request) throws Exception {
List<Method> befores = Java.findAllAnnotatedMethods(Controller.getControllerClass(), Before.class);
Collections.sort(befores, new Comparator<Method>() {

public int compare(Method m1, Method m2) {
Before before1 = m1.getAnnotation(Before.class);
Before before2 = m2.getAnnotation(Before.class);
return before1.priority() - before2.priority();
}
});
ControllerInstrumentation.stopActionCall();
for (Method before : befores) {
String[] unless = before.getAnnotation(Before.class).unless();
String[] only = before.getAnnotation(Before.class).only();
boolean skip = false;
for (String un : only) {
if (!un.contains(".")) {
un = before.getDeclaringClass().getName().substring(12).replace("$", "") + "." + un;
}
if (un.equals(request.action)) {
skip = false;
break;
} else {
skip = true;
}
}
for (String un : unless) {
if (!un.contains(".")) {
un = before.getDeclaringClass().getName().substring(12).replace("$", "") + "." + un;
}
if (un.equals(request.action)) {
skip = true;
break;
}
}
if (!skip) {
before.setAccessible(true);
inferResult(invokeControllerMethod(before));
}
}
}


第56行会真正的执行用户请求。在这里我们可以非常清晰的看到 所有httpRequest过来后都会先检查是否有@Before注解的方法需要优先执行。用户请求执行完毕后,又会检查是否有@After 和@Finally 注解的方法需要执行。当用户请求执行抛出异常后这里会catch住,同时检查是否有@Catch注解的方法需要执行,这就是play的filter chain。

然后我们再来谈谈 play的热部署机制

所谓的热加载是指无需重启JVM就可以加载修改过的类,更新运行时的class行为。

play做到了只需刷新页面就可以实现play的热加载

因为load class是在JVM 里面的native方法执行的,要想执行热加载有两条路:

1.修改JVM源码。这个就像给自己挖个大坑,随时都存在性能隐患,各种专家强烈不建议,play也没走这条路。

2.实现自己的classLoader,并且创建对象的行为,指定为用自定义的classLoader加载的class,play就是这么干的。

在上面的run()方法中 执行 execute()前有一个init()方法

/**
* Init the call (especially usefull in DEV mode to detect changes)
*/
public boolean init() {
Thread.currentThread().setContextClassLoader(Play.classloader);
Play.detectChanges();
if (!Play.started) {
if (Play.mode == Mode.PROD) {
throw new UnexpectedException("Application is not started");
}
Play.start();
}
InvocationContext.current.set(getInvocationContext());
return true;
}
我们可以发现
Play.detectChanges()


里面有这么一句

classloader.detectChanges();
这就是play自己的classLoader了

/**
* Detect Java changes
*/
public void detectChanges() {
// Now check for file modification
List<ApplicationClass> modifieds = new ArrayList<ApplicationClass>();
for (ApplicationClass applicationClass : Play.classes.all()) {
if (applicationClass.timestamp < applicationClass.javaFile.lastModified()) {
applicationClass.refresh();
modifieds.add(applicationClass);
}
}
Set<ApplicationClass> modifiedWithDependencies = new HashSet<ApplicationClass>();
modifiedWithDependencies.addAll(modifieds);
if (modifieds.size() > 0) {
modifiedWithDependencies.addAll(Play.pluginCollection.onClassesChange(modifieds));
}
List<ClassDefinition> newDefinitions = new ArrayList<ClassDefinition>();
boolean dirtySig = false;
for (ApplicationClass applicationClass : modifiedWithDependencies) {
if (applicationClass.compile() == null) {
Play.classes.classes.remove(applicationClass.name);
currentState = new ApplicationClassloaderState();//show others that we have changed..
} else {
int sigChecksum = applicationClass.sigChecksum;
applicationClass.enhance();
if (sigChecksum != applicationClass.sigChecksum) {
dirtySig = true;
}
BytecodeCache.cacheBytecode(applicationClass.enhancedByteCode, applicationClass.name, applicationClass.javaSource);
newDefinitions.add(new ClassDefinition(applicationClass.javaClass, applicationClass.enhancedByteCode));
currentState = new ApplicationClassloaderState();//show others that we have changed..
}
}
if (newDefinitions.size() > 0) {
Cache.clear();
if (HotswapAgent.enabled) {
try {
HotswapAgent.reload(newDefinitions.toArray(new ClassDefinition[newDefinitions.size()]));
} catch (Throwable e) {
throw new RuntimeException("Need reload");
}
} else {
throw new RuntimeException("Need reload");
}
}
// Check signature (variable name & annotations aware !)
if (dirtySig) {
throw new RuntimeException("Signature change !");
}

// Now check if there is new classes or removed classes
int hash = computePathHash();
if (hash != this.pathHash) {
// Remove class for deleted files !!
for (ApplicationClass applicationClass : Play.classes.all()) {
if (!applicationClass.javaFile.exists()) {
Play.classes.classes.remove(applicationClass.name);
currentState = new ApplicationClassloaderState();//show others that we have changed..
}
if (applicationClass.name.contains("$")) {
Play.classes.classes.remove(applicationClass.name);
currentState = new ApplicationClassloaderState();//show others that we have changed..
// Ok we have to remove all classes from the same file ...
VirtualFile vf = applicationClass.javaFile;
for (ApplicationClass ac : Play.classes.all()) {
if (ac.javaFile.equals(vf)) {
Play.classes.classes.remove(ac.name);
}
}
}
}
throw new RuntimeException("Path has changed");
}
}


这个方法会遍历所有发生改变的class然后重现加载之,因为是放在用户请求的过程中,所以我们的直观感受就是刷新页面就热加载了一切,这就是play的 hotswap magic!

关于自定义classLoader的思路给个传送门

http://www.ibm.com/developerworks/cn/java/j-lo-hotdeploy/index.html?ca=drs-
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  playframework java