【转载】ArcGIS10时态GIS使用体验之五

<esri:renderer>

<esri:ClassBreaksRenderer attribute=”FIRE_TYPE”>

<esri:ClassBreakInfo maxValue=”5″>

<esri:symbol>

<esri:SimpleMarkerSymbol alpha=”0.8″ color=”0xFF0000″ size=”8″ style=”triangle”>

<esri:SimpleLineSymbol color=”0xAA0000″/>

</esri:SimpleMarkerSymbol>

</esri:symbol>

</esri:ClassBreakInfo>

<esri:ClassBreakInfo minValue=”10″ maxValue=”14″>

<esri:symbol>

<esri:SimpleMarkerSymbol alpha=”0.8″ color=”0xFF0000″ size=”12″ style=”triangle”>

<esri:SimpleLineSymbol color=”0xAA0000″/>

</esri:SimpleMarkerSymbol>

</esri:symbol>

</esri:ClassBreakInfo>

<esri:ClassBreakInfo minValue=”15″>

<esri:symbol>

<esri:SimpleMarkerSymbol alpha=”0.8″ color=”0xFF0000″ size=”25″ style=”triangle”>

<esri:SimpleLineSymbol color=”0xAA0000″/>

</esri:SimpleMarkerSymbol>

</esri:symbol>

</esri:ClassBreakInfo>

</esri:ClassBreaksRenderer>

</esri:renderer>

</esri:FeatureLayer>

</esri:Map>

<esri:TimeSlider id=”myTimeSlider” enabled=”{fLayer.loaded}”/>

<mx:Label text=”{myDateFormatter.format(myTimeSlider.timeExtent.endTime)}” fontSize=”14″ fontWeight=”bold”/>

<mx:Text width=”100%” text=”使用Point按时间序列来渲染绘制火灾,使用FeatureLayer。”/>

</mx:Application>

使用FeatureLayer的方式其实是在客户端新建了一个FeatureLayer,跟之前的GraphicLayer类似,默认绘画要素的方式是累积的,这个不像前面的ArcGISDynamicMapServiceLayer的方式,这个需要注意一下,不过也有更多的可操作性,比如统计绘画个数、MapTip 等Graphic的操作。

【转载】ArcGIS10时态GIS使用体验之四

2. 采用FeatureLayer的方式:

<?xml version=”1.0″ encoding=”utf-8″?>

<mx:Application

xmlns:fx=”http://ns.adobe.com/mxml/2009&#8243;

xmlns:mx=”library://ns.adobe.com/flex/mx”

xmlns:esri=”http://www.esri.com/2008/ags&#8221;

xmlns:s=”library://ns.adobe.com/flex/spark”

pageTitle=”1985-2008之间的火灾_FeatureLayer”

styleName=”plain”

horizontalAlign=”center”>

<fx:Style>

@namespace mx “library://ns.adobe.com/flex/mx”;

@namespace esri “http://www.esri.com/2008/ags&#8221;;

@namespace s “library://ns.adobe.com/flex/spark”;

/* for the slider tooltip that shows the date as you move the slider */

mx|ToolTip {

font-size: 14;

}

</fx:Style>

<fx:Script>

<![CDATA[

import com.esri.ags.events.LayerEvent;

import com.esri.ags.layers.supportClasses.TimeInfo;

protected function fLayer_loadHandler(event:LayerEvent):void

{

var timeInfo:TimeInfo = fLayer.layerDetails.timeInfo;

myTimeSlider.createTimeStopsByCount(timeInfo.timeExtent,20);

//myTimeSlider.play(); // start playing automatically

}

]]>

</fx:Script>

<fx:Declarations>

<mx:DateFormatter id=”myDateFormatter” formatString=”MMMM YYYY”/>

</fx:Declarations>

<mx:Label text=”1985-2008之间的火灾” fontSize=”20″/>

<mx:Label text=”总共火灾 次数{fLayer.numGraphics} .” fontSize=”13″ visible=”{fLayer.numGraphics > 0}”/>

<esri:Map timeSlider=”{myTimeSlider}”>

<esri:ArcGISDynamicMapServiceLayer url=”http://china-2065ed03d/ArcGIS/rest/services/test/MapServer”&gt;

<esri:visibleLayers>

<mx:ArrayCollection>

<fx:Number>0</fx:Number>

<fx:Number>2</fx:Number>

<fx:Number>3</fx:Number>

<fx:Number>4</fx:Number>

<fx:Number>5</fx:Number>

<fx:Number>6</fx:Number>

<fx:Number>7</fx:Number>

</mx:ArrayCollection>

</esri:visibleLayers>

</esri:ArcGISDynamicMapServiceLayer>

<esri:FeatureLayer id=”fLayer”

load=”fLayer_loadHandler(event)”

mode=”snapshot”

outFields=”[FIRE_TYPE]”

url=”http://china-2065ed03d/ArcGIS/rest/services/test/MapServer/0″&gt;

【转载】ArcGIS10时态GIS使用体验之三

在server端:WebAPI

将带有时态数据图层的地图发布为服务(跟普通的一样),尽量不要用layer group的方式,我在Prerelease版本下试好像是不行, 不知道正式版如何。下面的示例是用flash API的,别的Web API也一样。

1. 采用ArcGISDynamicMapServiceLayer的方式:

<?xml version="1.0" encoding="utf-8"?>

<mx:Application

xmlns:fx="http://ns.adobe.com/mxml/2009&quot;

xmlns:mx="library://ns.adobe.com/flex/mx"

xmlns:esri="http://www.esri.com/2008/ags&quot;

xmlns:s="library://ns.adobe.com/flex/spark"

pageTitle="1985-2008之间的火灾"

styleName="plain"

horizontalAlign="center" xmlns:layers="com.esri.ags.layers.*">

<fx:Style>

@namespace mx "library://ns.adobe.com/flex/mx";

@namespace esri "http://www.esri.com/2008/ags&quot;;

@namespace s "library://ns.adobe.com/flex/spark";

/* for the slider tooltip that shows the date as you move the slider */

mx|ToolTip {

font-size: 14;

}

</fx:Style>

<fx:Script>

<![CDATA[

import com.esri.ags.events.LayerEvent;

import com.esri.ags.layers.supportClasses.TimeInfo;

protected function fLayer_loadHandler(event:LayerEvent):void

{

var timeInfo:TimeInfo = myTimeLayer.timeInfo;

myTimeSlider.createTimeStopsByCount(myTimeLayer.timeInfo.timeExtent); // defaults to 10 intervals

//myTimeSlider.createTimeStopsByTimeInterval(timeInfo.timeExtent, timeInfo.timeInterval, timeInfo.timeIntervalUnits);

//myTimeSlider.play(); // start playing automatically

}

]]>

</fx:Script>

<fx:Declarations>

<mx:DateFormatter id="myDateFormatter" formatString="YYYY"/>

</fx:Declarations>

<mx:Label text="1985-2008之间的火灾" fontSize="20"/>

<esri:Map timeSlider="{myTimeSlider}">

<esri:ArcGISDynamicMapServiceLayer id="myTimeLayer" url="http://china-2065ed03d/ArcGIS/rest/services/test/MapServer&quot; load="fLayer_loadHandler(event)" >

</esri:ArcGISDynamicMapServiceLayer>

</esri:Map>

<esri:TimeSlider id="myTimeSlider" />

<mx:Label text="{myDateFormatter.format(myTimeSlider.timeExtent.endTime)}" fontSize="14" fontWeight="bold"/>

<mx:Text width="100%" text="使用Polygon来按时间序列绘制火灾,使用ArcGISDynamicMapServiceLayer。"/>

</mx:Application>

效果:

【转载】ArcGIS10时态GIS使用体验之二

ArcGIS10.0中使用时态数据

ArcMap中通过设置图层属性的时间选项 卡,可以将该图层作为时态数据来使用,一旦启用了时态数据图层,你就可以通过时间滑动工具(Time Slider)来观看,或者你也可以其通过ArcGIS Server发布出来,通过各 种客户端API来 进行基于时间序列的查询和动态展示。

图层属性的时间选项卡

时 间滑动工具(Time Slider


Server端:Time-Aware map service layers

ArcGIS Services Directory中的Time Info


 

 

ArcGIS10.0支持的时态空间数据存 储方式:

时间信息可以保存在一个属性字段里面如feature classes, mosaic datasets, raster catalogs等数据,也可以保存在数据内部如netCDF data, Tracking layers等。

Ø  Feature class Layers:

1.       属性值随时间 序列变化(相同的shape,不同的时间序列值)

2.       空间位置大小 随时间序列变化(不同shape,不同时间序列值)

不管事哪种Feature class时间变化类型,在ArcMap中该图层属性Time属性卡中,layer Time有两中选择1.Each feature has a single time field2.Each feature has a start and end time field。前者指的是由单个字 段确定的时间序列,比如2010年;后者代表的是该事件是在严格的一个时间区间内,比如2010/2/32010/4/27

除此外还有时间格式的设置、时间间隔和时间范围的设置,更高级的还有时区和偏移量的设置。还可以按 时序以累计的方式来观看地图。

ArcMap中的表现:

【转载】ArcGIS10时态GIS使用体验之一

什么是时态数据:

时态数据是代表了某一个时间某种状态的简单 数据,如2009年 上海的土地利用现状,或者是2010-04-02这天在云南省的降雨总量等。收集时态数据用来分析气候变化或其他环境变量,监管交通条件,研究人口成 长趋势等。时态数据具有很多数据源,通过使用观测传感器得到的数据,或从业务模型产生的数据。

存储时态数据

ArcGIS你可以存储或管理很多格式的时态 数据如:feature classes, mosaic datasets, raster catalogs, table,etc,如何选择使用哪种格式取决于时 态数据的属性和你想要表现的方式。

时态数据格式选择:

动态要素:观测海洋生物漂流位置

用颜色渲染要素:研究一种疾病致死率的升高 原因

用大小渲染要素:一个城市的人口数量变化情 况

使用raster catalogs or netCDF data观测变化:观测海洋水温变化或气候变化

绘图的变换:在不同区域检测臭氧水平或水压

存储时间序列值

1.       时态数据存储 的时间序列内的值可以用相关或不相关时间间隔内某一个取样时间点来代表。这些数据将存储在单个属性字段中,并且可以在时间序列上某一个特别时间点来观测时 态数据。如河流的流量和水位值记录在相关的时间间隔内,地震数据收集取决于地震的发生,记录与非相关时间间隔内。

2.       时态数据存储 的时间序列内的值也可以用一个时间段来代表。在这种情况下,一个事件发生于一段时间内,时间序列值将存储在两个字段内。一个代表起始时间值,一个代表终止 时间值。例如,用面要素来代表一个消防队任务的起始和终止时间取决于一场大火的起始和终止时间。

这些时间序列值将以datastringnumeric字段保存。详细看如下help上的说明:

Supported string field formats

 

YYYY

YYYYMM

YYYY/MM

YYYYMMDD

YYYY/MM/DD

YYYY-MM-DD

YYYYMMDDhhmmss

YYYY/MM/DD hh:mm:ss

YYYY-MM-DDhhmmss

YYYYMMDDhhmmss.s

YYYY/MM/DDhh:mm:ss.s

Supported numeric field formats

 

YYYY

YYYYMM

YYYYMMDD

YYYYMMDDhhmmss

where

 

YYYY = four-digit year

MM = two-digit month (01=January)

DD = two-digit day of month (01 through 31)

hh = two digits of hour (00 through 23) (a.m./p.m. not allowed)

mm = two digits of minute (00 through 59)

ss = two digits of second (00 through 59)

s = one digit of millisecond (0 through 9)

 

Geowebcache切片个数与占用空间估计

=== Estimating the number of tiles and size on disk ===
Please download one of the attached spreadsheets (Open Document or Excel 97).

The green areas in the document are numbers you should change to match your data. When you specify the bounds, use the extent of your actual data, what is known as “dataBounds” in GWC 1.1.x or as gridSubset in GWC 1.2.x. Using the world bounds represents the worst case scenario.

Note that the numbers are for each zoomlevel, but the error margins are so large that this is not significant.

=== Preview ===
To cover the entire planet is quite resource intensive. Assumptions:
  * Bounding box: -180,-90,180,90  (EPSG:4326)
  * Average tile size: 20 kbyte (typical for PNG with some antialiasing, or JPEG of raster)
  * Response time from WMS for 768×768 GetMap: 0.5
  * Number of cores on WMS server: 2
  * Tile dimensions: 256×256 pixels
Please note that these numbers below in many ways represent the worst case scenario. Speak to an expert before using the attached spreadsheet as a decision tool.

|| Zoomlevel || Number of tiles || Disk space (megabytes) || Time to render (hours) || Note ||
|| 0 ||  2 ||  0 ||  0|| ||
|| 1 ||  8 ||  0 ||  0|| ||
|| 2 ||  32 ||  1 ||  0|| ||
|| 3 ||  128 ||  3 ||  0|| ||
|| 4 ||  512 ||  10 ||  0|| ||
|| 5 ||  2,048 ||  40 ||  0|| ||
|| 6 ||  8,192 ||  160 ||  0|| ||
|| 7 ||  32,768 ||  640 ||  0|| ||
|| 8 ||  131,072 ||  2,560 ||  1|| ||
|| 9 ||  524,288 ||  10,240 ||  4|| ||
|| 10 ||  2,097,152 ||  40,960 ||  16|| ||
|| 11 ||  8,388,608 ||  163,840 ||  65|| ||
|| 12 ||  33,554,432 ||  655,360 ||  259|| This is 11 days of rendering ||
|| 13 ||  134,217,728 ||  2,621,440 ||  1,036|| This is 2 terabytes ||
|| 14 ||  536,870,912 ||  10,485,760 ||  4,143|| ||
|| 15 ||  2,147,483,648 ||  41,943,040 ||  16,570|| This is two years of rendering ||
|| 16 ||  8,589,934,592 ||  167,772,160 ||  66,280|| ||
|| 17 ||  34,359,738,368 ||  671,088,640 ||  265,121|| ||
|| 18 ||  137,438,953,472 ||  2,684,354,560 ||  1,060,486 || This is 2.7 petabytes ||
|| 19 ||  549,755,813,888 ||  10,737,418,240 ||  4,241,943 || ||
|| 20 ||  2,199,023,255,552 ||  42,949,672,960 ||  16,967,772 || ||

【转载】IDEA 9 TOMCAT 整合开发,修改类不重启 热部署 热加载

下边是个简单的工程

f7b07d49-a0f7-3ba5-aa58-b6103a86b54b

1.我们先来设置编译的目录. modules settings ->

4482d4ee-c2d4-3a60-bb86-4a06643f02e6

2.设置工程所需要的jar.
a07ddcc0-95a2-3307-88dd-2948f772d1c8

3.添加一个web faces.

a1fd516f-88a3-3ad5-80eb-21809a093969

4.设置web  faces.主要设置 web root 路径和web.xml的位置.
2d397f3e-55b6-3a23-9942-ca8fd4102de7
5.添加一个Artifacts.

324c78ae-f144-3e16-845e-3aca931d8966

6.设置Artifacts.与工程相关的配置。

b9587811-240e-32dd-a1ab-b68f7e62bd42

7.设置Artifacts.输出目录.

28e1f176-85e4-3bab-ac01-09829b177a3d

8.添加一个tomcat运行环境.

4c79fb69-80a0-39a7-85ef-93613dc295d8

9.添加一个工程相对的虚拟路径.补充一下。如果Application context 啥都不填,就说是应用的根目录,等于访问路径是http://localhost:8080/.
4be7135f-f0da-305c-a0c7-3d46ea9b9d25

10.ok配置已经好了。可以运行或者编译. 说明一下在debug模式下,修改了类后要按[下图第一个按钮]才能重新加载修改后的类,但服务器不需要重启编译的.

2f3f63bd-6fea-3404-89e5-26975dea9539

【转载】Google Guice 入门教程06 &ndash; Web 和 Servlet

3 Web 和 Servlet

3.1 快速开始

我们从一个例子开始Guice Web的开发。

image

首先准备我们的环境,由于是web开发,因此我们需要guice-servlet的jar包。log4j不是必须的,只是为了方便日志记录而已(Guice内部是使用jdk内部的logging包来完成日志记录的)。

必可避免的要在web.xml中都一些手脚,这里先配置一个filter吧。

<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

GuiceFilter中并没有帮我们完成Guice的初始化工作,因此我们必须手动完成Guice的模块注入。

public class MyGuiceServletContextListener extends GuiceServletContextListener {

@Override
protected Injector getInjector() {
return Guice.createInjector(new ServletModule());
}
}

继续在web.xml中添加东西。

<listener>
<listener-class>cn.imxylz.study.guice.web.MyGuiceServletContextListener</listener-class>
</listener>

显然我们需要将某个PATH映射到一个Servlet上,于是需要在ServletModule上做点事情。

public class MyGuiceServletContextListener extends GuiceServletContextListener {

@Override
protected Injector getInjector() {
return Guice.createInjector(new ServletModule() {
protected void configureServlets() {
serve(”/helloworld”).with(HelloWorldServlet.class);
}
});
}
}

这里将/helloworld这个地址映射到HelloWorldServlet上。好吧,先写个 “HelloWorld”的Servlet吧。

@Singleton
public class HelloWorldServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
resp.getWriter().append(”Hello, guice! “+new Date());
}
}

注意,根据Guice的Servlet要求,每一个Servlet必须是单例的,因此这里强制加上@Singleton。

好了,我们来看看输出。

image

3.2 注入服务

当然了,既然是一个IOC的容器,那么在Guice中也是可以注入服务的。

首先定义一个服务。

@ImplementedBy(HelloWorldImpl.class)
public interface HelloWorld {

void execute() throws IOException;
}

接着是服务的实现,注意在我们的服务中需要request和response对象,并且我们的服务假定是与request绑定的,采用@RequestScoped标签来标识。

@RequestScoped
public class HelloWorldImpl implements HelloWorld {

private HttpServletRequest request;
private HttpServletResponse response;
@Inject
public HelloWorldImpl(HttpServletRequest request, HttpServletResponse response) {
super();
this.request = request;
this.response = response;
}

public void execute() throws IOException{
String name=request.getParameter(”user”);
if(name==null||name.length()<1)name=”Guest”;
response.getWriter().append(String.format(”Hello, %s. %s -> sessionId=%s,hashCode=%d \n”, name,new Date(),request.getSession().getId(),hashCode()));
}

}

然后在我们的Servlet中可以采用如下实现。

@Singleton
public class HelloWorldServlet extends HttpServlet{
private static final long serialVersionUID = 1L;
@Inject
private Injector inj;
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException,
IOException {
inj.getInstance(HelloWorld.class).execute();
inj.getInstance(HelloWorld.class).execute();
}
}

这里我们自动注入Injector对象,然后通过Inject对象获取我们的服务,注意我们没有将HttpServletRequest和HttpServletResponse手动注入到我们的服务中。

好了,我们再来看看输出。可以看到我们的对象是与request绑定,同一个request使用的同一个HelloWorld服务,不同的request那么是同一个session获取的服务也是不一样的。

image_thumb4

老实说,Guice关于WEB的东西其实大概就这么多。其它的关于多规则匹配顺序,正则表达式匹配等等其实不谈也罢,都很弱,用处不大。

3.3 整合Struts 2

Guice可以与Struts 2整合,当然了理论上可以与其它MVC框架整合,只是Guice官方提供了一个Struts 2的插件。

image_thumb5

首先看看依赖的jar包,我们尽可能的少用jar包。

aopalliance-1.0.jar是guice-servlet依赖的,因此guice需要aopalliance/guice/guice-servlet/guice-struts2-plugin等包,struts2启动依赖commons-logging/freemarker/ognl/struts2-core/xwork等jar包。lo4j只是为了记录日志方便而已。

首先定义一个服务,服务很简单输出服务器的状态。

public interface Service {

String getStatus();
}

public class ServiceImpl implements Service {

public String getStatus() {
return “I’am running.”;
}
}

然后写一个Module绑定服务及其实现,当然如果偷懒可以使用@ImplementedBy,这里为了说明如果在配置文件中配置Module,所以单写一个Module。

public class ServiceModule implements Module {
@Override
public void configure(Binder binder) {
binder.bind(Service.class).to(ServiceImpl.class);
}
}

然后写一个SessionScope级别的对象绑定访问次数。

@SessionScoped
public class AccessCounter {

private AtomicInteger count = new AtomicInteger(0);

public int visit() {
return count.incrementAndGet();
}

@Override
public String toString() {
return String.format(”AccessCounter#%s:%d”, this.hashCode(), count.get());
}
}

好了,我们的Servlet出场了。

package cn.imxylz.study.guice.web.struts2;

import com.google.inject.Inject;

public class AccessStatusAction {

final AccessCounter counter;
final Service service;
String message;

@Inject
public AccessStatusAction(AccessCounter counter, Service service) {
this.counter = counter;
this.service = service;
}

public String execute() {
return “success”;
}

public int getCount() {
return counter.visit();
}

public String getStatus() {
return service.getStatus();
}

public String getMessage() {
return message;
}

public void setMessage(String message) {
this.message = message;
}
}

可以看到我们很简单的服务又被Guice给入侵了,所以说Guice对我们业务逻辑的侵入是很大,估计这也是很多程序员不愿意推广Guice的一个原因吧。

要写的Java代码就这么多,剩下的就是一堆的配置了。

首先web.xml中配置struts2的fi
lter,注意这里我们没必要再配置一个guice的listener了,因为在guice的struts2的插件中已经配置一个ServletModule了。

<?xml version=”1.0″ encoding=”UTF-8″?>
<web-app xmlns=”http://java.sun.com/xml/ns/javaee”xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd”
version=”2.5″>

<display-name>guice</display-name>
<description>xylz study project – guice</description>

<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher
</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!– listener>
<listener-class>cn.imxylz.study.guice.web.MyGuiceServletContextListener</listener-class>
</listener –>
</web-app>

下面该配置struts.xml了。

<!DOCTYPE struts PUBLIC
“-//Apache Software Foundation//DTD Struts Configuration 2.0//EN”
http://struts.apache.org/dtds/struts-2.0.dtd”>

<struts>

<constant name=”guice.module” value=”cn.imxylz.study.guice.web.struts2.ServiceModule”/>

<package name=”default” extends=”struts-default”>
<action name=”access-status”
class=”cn.imxylz.study.guice.web.struts2.AccessStatusAction”>
<result>access-status.jsp</result>
</action>
</package>

</struts>

在这里先配置我们的Module,我们的Module就是完成Guice的注入过程。在guice的Struts2插件中类com.google.inject.struts2.GuiceObjectFactory有以下逻辑:

@Inject(value = “guice.module”, required = false)
void setModule(String moduleClassName) {
try {
// Instantiate user’s module.
@SuppressWarnings({”unchecked”})
Class<? extends Module> moduleClass =
(Class<? extends Module>) Class.forName(moduleClassName);
this.module = moduleClass.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}

这段逻辑就是完成我们Module的注入过程。

当然了我们需要配置一个Struts2的action类cn.imxylz.study.guice.web.struts2.AccessStatusAction,访问地址access-status.action正确的时候渲染access-status.jsp页面。

<%@ taglib prefix=”s” uri=”/struts-tags” %>

<html>
<body>
<h1>Access Status</h1>
<h3><b>Access in this session:</b>
<s:property value=”count”/></h3>

<h3><b>Status:</b>
<s:property value=”status”/></h3>

<h3><b>Message:</b>
<s:property value=”message”/></h3>
<h4><%=”sessionId=”+session.getId()%></h4>
</body>
</html>

所有的工作就完成了,我们看看浏览器中的渲染结果。

image_thumb6

即使如此,Guice整合Struts 2还是比较弱的。

【转载】Google Guice 入门教程05 &ndash; AOP(面向切面编程)

2 AOP 面向切面编程

2.1 AOP入门

在前面的章节主要讲Guice的依赖注入,有了依赖注入的基础后我们再来看Guice的AOP。我们先从一个例子入手,深入浅出的去理解Guice的AOP的原理和实现。

首先我们定义服务Service,这个服务有一个简单的方法sayHello,当然了我们有一个服务的默认实现ServiceImpl,然后使用@ImplementedBy将服务和默认实现关联起来,同时将服务的实现标注为单例模式。

@ImplementedBy(ServiceImpl.class)
public interface Service {
    void sayHello();
}

在服务的实现ServiceImpl中,我们sayHello方法就是输出一行信息,这行信息包含服务的类名,hashCode以及方法名称和执行的时间。

@Singleton
public class ServiceImpl implements Service {

    @Override
    @Named(“log”)
    public void sayHello() {
        System.out.println(String.format(“[%s#%d] execute %s at %d”, this.getClass().getSimpleName(),hashCode(),”sayHello”,System.nanoTime()));
    }

}

接下来定义一个AOP的实现。在Aopalliance中(大家都认可的AOP联盟)实现我们的方法拦截器。这个拦截器LoggerMethodInterceptor 也没有做什么特别的事情,只是记录些执行的时间,当然了由于执行时间比较短我们用纳秒来描述(尽管不是那么精确)。

在MethodInvocation中我们一定要调用proceed()方法,这样我们的服务才能被执行。当然了如果为了做某些控制我们就能决定是否调用服务代码了。

import static java.lang.System.out;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

public class LoggerMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        String methodName = invocation.getMethod().getName();
        long startTime=System.nanoTime();
        out.println(String.format(“before method[%s] at %s”, methodName, startTime));
        Object ret = null;
        try {
            ret = invocation.proceed();
        } finally {
            long endTime=System.nanoTime();
            out.println(String.format(” after method[%s] at %s, cost(ns):%d”, methodName, endTime,(endTime-startTime)));
        }
        return ret;
    }
}

最后才是我们的客户端程序,注意在这里我们需要绑定一个拦截器,这个拦截器匹配任何类的带有log注解的方法。所以这就是为什么我们服务的实现方法需要用log标注的原因了。

public class AopDemo {
    @Inject
    private Service service;

    public static void main(String[] args) {
        Injector inj = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                binder.bindInterceptor(Matchers.any(),//
                        Matchers.annotatedWith(Names.named(“log”)),//
                        new LoggerMethodInterceptor());
            }
        });
        inj.getInstance(AopDemo.class).service.sayHello();
        inj.getInstance(AopDemo.class).service.sayHello();
        inj.getInstance(AopDemo.class).service.sayHello();
    }
}

我们的程序输出了我们期望的结果。

before method[sayHello] at 7811306067456
[ServiceImpl$$EnhancerByGuice$$96717882#33353934] execute sayHello at 7811321912287
after method[sayHello] at 7811322140825, cost(ns):16073369
before method[sayHello] at 7811322315064
[ServiceImpl$$EnhancerByGuice$$96717882#33353934] execute sayHello at 7811322425280
after method[sayHello] at 7811322561835, cost(ns):246771
before method[sayHello] at 7811322710141
[ServiceImpl$$EnhancerByGuice$$96717882#33353934] execute sayHello at 7811322817521
after method[sayHello] at 7811322952455, cost(ns):242314

关于此结果有几点说明。

(1)由于使用了AOP我们的服务得到的不再是我们写的服务实现类了,而是一个继承的子类,这个子类应该是在内存中完成的。

(2)除了第一次调用比较耗时外(可能guice内部做了比较多的处理),其它调用事件为0毫秒(我们的服务本身也没做什么事)。

(3)确实完成了我们期待的AOP功能。

我们的例子暂且说到这里,来看看AOP的相关概念。

2.2 AOP相关概念

老实说AOP有一套完整的体系,光是概念就有一大堆,而且都不容易理解。这里我们结合例子和一些场景来大致了解下这些概念。

通知(Advice)

所谓通知就是我们切面需要完成的功能。比如2.1例子中通知就是记录方式执行的耗时,这个功能我们就称之为一个通知。

比如说在很多系统中我们都会将操作者的操作过程记录下来,但是这个记录过程又不想对服务侵入太多,这样就可以使用AOP来完成,而我们记录日志的这个功能就是一个通知。通知除了描述切面要完成的工作外还需要描述何时执行这个工作,比如是在方法的之前、之后、之前和之后还是只在有异常抛出时。

连接点(Joinpoint)

连接点描述的是我们的通知在程序执行中的时机,这个时机可以用一个“点”来描述,也就是瞬态。通常我们这个瞬态有以下几种:方法运行前,方法运行后,抛出异常时或者读取修改一个属性等等。总是我们的通知(功能)就是插入这些点来完成我们额外的功能或者控制我们的执行流程。比如说2.1中的例子,我们的通知(时间消耗)不仅在方法执行前记录执行时间,在方法的执行后也输出了时间的消耗,那么我们的连接点就有两个,一个是在方法运行前,还有一个是在方法运行后。

切入点(Pointcut)

切入点描述的是通知的执行范围。如果通知描述的是“什么时候”做“什么事”,连接点描述有哪些“时候”,那么切入点可以理解为“什么地方”。比如在2.1例子中我们切入点是所有Guice容器管理的服务的带有@Named(“log”)注解的方法。这样我们的通知就限制在这些地方,这些地方就是所谓的切入点。

切面(Aspect)

切面就是通知和切入点的结合。就是说切面包括通知和切入点两部分,由此可见我们所说的切面就是通知和切入点。通俗的讲就是在什么时候在什么地方做什么事。

引入(Introduction)

引入是指允许我们向现有的类添加新的方法和属性。个人觉
这个特性尽管很强大,但是大部分情况下没有多大作用,因为如果一个类需要切面来增加新的方法或者属性的话那么我们可以有很多更优美的方式绕过此问题,而是在绕不过的时候可能就不是很在乎这个功能了。

目标(Target)

目标是被通知的对象,比如我们2.1例子中的ServiceImpl 对象。

代理(Proxy)

代理是目标对象被通知引用后创建出来新的对象。比如在2.1例子中我们拿到的Service对象都不是ServiceImpl本身,而是其包装的子类ServiceImpl$$EnhancerByGuice$$96717882。

织入(Weaving)

所谓织入就是把切面应用到目标对象来创建新的代理对象的过程。通常情况下我们有几种实际来完成织入过程:

编译时:就是在Java源文件编程成class时完成织入过程。AspectJ就存在一个编译器,运行在编译时将切面的字节码编译到目标字节码中。

类加载时:切面在目标类加载到JVM虚拟机中时织入。由于是在类装载过程发生的,因此就需要一个特殊的类装载器(ClassLoader),AspectJ就支持这种特性。

运行时:切面在目标类的某个运行时刻被织入。一般情况下AOP的容器会建立一个新的代理对象来完成目标对象的功能。事实上在2.1例子中Guice就是使用的此方式。

Guice支持AOP的条件是:

  • 类必须是public或者package (default)
  • 类不能是final类型的
  • 方法必须是public,package或者protected
  • 方法不能使final类型的
  • 实例必须通过Guice的@Inject注入或者有一个无参数的构造函数

2.3 切面注入依赖

如果一个切面(拦截器)也需要注入一些依赖怎么办?没关系,Guice允许在关联切面之前将切面的依赖都注入。比如看下面的例子。

我们有一个前置服务,就是将所有调用的方法名称输出。

@ImplementedBy(BeforeServiceImpl.class)
public interface BeforeService {

    void before(MethodInvocation invocation);
}

public class BeforeServiceImpl implements BeforeService {

    @Override
    public void before(MethodInvocation invocation) {
        System.out.println(“before method “+invocation.getMethod().getName());
    }
}

然后有一个切面,这个切面依赖前置服务,然后输出一条方法调用结束语句。

public class AfterMethodInterceptor implements MethodInterceptor {
   @Inject
    private BeforeService beforeService;
    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        beforeService.before(invocation);
        Object ret = null;
        try {
            ret = invocation.proceed();
        } finally {
            System.out.println(“after “+invocation.getMethod().getName());
        }
        return ret;
    }
}

在AopDemo2中演示了如何注入切面的依赖。在第9行,AfterMethodInterceptor 请求Guice注入其依赖。

public class AopDemo2 {
    @Inject
    private Service service;
    public static void main(String[] args) {
        Injector inj = Guice.createInjector(new Module() {
            @Override
            public void configure(Binder binder) {
                AfterMethodInterceptor after= new AfterMethodInterceptor();
                binder.requestInjection(after);
                binder.bindInterceptor(Matchers.any(),//
                        Matchers.annotatedWith(Names.named(“log”)),//
                        after);
            }
        });
        AopDemo2 demo=inj.getInstance(AopDemo2.class);
        demo.service.sayHello();
    }
}

尽管切面允许注入其依赖,但是这里需要注意的是,如果切面依赖仍然走切面的话那么程序就陷入了死循环,很久就会堆溢出。

2.4 Matcher

Binder绑定一个切面的API是

com.google.inject.Binder.bindInterceptor(Matcher<? super Class<?>>, Matcher<? super Method>, MethodInterceptor…)

第一个参数是匹配类,第二个参数是匹配方法,第三个数组参数是方法拦截器。也就是说目前为止Guice只能拦截到方法,然后才做一些切面工作。

对于Matcher有如下API:

  • com.google.inject.matcher.Matcher.matches(T)
  • com.google.inject.matcher.Matcher.and(Matcher<? super T>)
  • com.google.inject.matcher.Matcher.or(Matcher<? super T>)

其中第2、3个方法我没有发现有什么用,好像Guice不适用它们,目前没有整明白。

对于第一个方法,如果是匹配Class那么这里T就是一个Class<?>的类型,如果是匹配Method就是一个Method对象。不好理解吧。看一个例子。

static class ServiceClassMatcher implements Matcher<Class<?>>{
    @Override
    public Matcher<Class<?>> and(Matcher<? super Class<?>> other) {
        return null;
    }
    @Override
    public boolean matches(Class<?> t) {
        return t==ServiceImpl.class;
    }
    @Override
    public Matcher<Class<?>> or(Matcher<? super Class<?>> other) {
        return null;
    }
}

在前面的例子中我们是使用的Matchers.any()对象匹配所有类而通过标注来识别方法,这里可以只匹配ServiceImpl类来控制服务运行流程。

事实上Guice里面有一个Matcher的抽象类com.google.inject.matcher.AbstractMatcher<T>,我们只需要覆盖其中的matches方法即可。

大多数情况下我们只需要使用Matchers提供的默认类即可。Matchers中有如下API:

  • com.google.inject.matcher.Matchers.any():任意类或者方法
  • com.google.inject.matcher.Matchers.not(Matcher<? super T>):不满足此条件的类或者方法
  • com.google.inject.matcher.Matchers.annotatedWith(Class<? extends Annotation>):带有此注解的类或者方法
  • com.google.inject.matcher.Matchers.annotatedWith(Annotation):带有此注解的类或者方法
  • com.google.inject.matcher.Matchers.subclassesOf(Class<?>):匹配此类的子类型(包括本身类型)
  • com.google.inject.matcher.Matchers.only(Object):与指定类型相等的类或者方法(这里是指equals方法返回true)
  • com.google.inject.matcher.Matchers.identicalTo(Object):与指定类型相同的类或者方法(这里是指同
    个对象)

  • com.google.inject.matcher.Matchers.inPackage(Package):包相同的类
  • com.google.inject.matcher.Matchers.inSubpackage(String):子包中的类(包括此包)
  • com.google.inject.matcher.Matchers.returns(Matcher<? super Class<?>>):返回值为指定类型的方法

通常只需要使用上面的方法或者组合方法就能满足我们的需求。

通过上面的学习可以看出,Guice的AOP还是很弱的,目前仅仅支持方法级别上的,另外灵活性也不是很高。

【转载】Google Guice 入门教程04 &ndash; 依赖注入

本章节继续讨论依赖注入的其他话题,包括作用域(scope,这里有一个与线程绑定的作用域例子)、立即初始化(Eagerly Loading Bindings)、运行阶段(Stage)、选项注入(Optional Injection)等等。

1.3.5 Scope(作用域)

在1.1章节中我们初步了解了对象的单例模式,在Guice中提供了一些常见的作用域,比如对于单例模式有下面两个作用域。

com.google.inject.Scopes.SINGLETON
com.google.inject.Scopes.NO_SCOPE

在使用上,可以使用Module的bind来实现,看下面的例子。

1 public class ScopeDemo {
2 public static void main(String[] args) {
3
4             Service service = Guice.createInjector(new Module() {
5                 @Override
6 public void configure(Binder binder) {
7                     binder.bind(Service.class).to(WwwService.class).in(Scopes.SINGLETON);
8                 }
9             }).getInstance(Service.class);
10             service.execute();
11         }
12     }
13
14

当然单例模式还可以似乎用@Singleton注解。

在com.google.inject.binder.ScopedBindingBuilder.in(Scope)方法中,一个Scope除了可以使上面的SINGLETION和NO_SCOPE外,还可以是自己定义的Scope。下面的例子演示了一个与线程绑定的Scope例子。

1 /**
2  * $Id: ThreadScopeDemo.java 90 2009-12-25 08:12:21Z xylz $
3  * xylz study project (www.imxylz.info)
4 */
5 package cn.imxylz.study.guice.inject.more;
6
7 import com.google.inject.Binder;
8 import com.google.inject.Guice;
9 import com.google.inject.Injector;
10 import com.google.inject.Key;
11 import com.google.inject.Module;
12 import com.google.inject.Provider;
13 import com.google.inject.Scope;
14
15 /** a demo with thread-scope
16  * @author xylz (www.imxylz.info)
17  * @version $Rev: 90 $
18 */
19 public class ThreadScopeDemo {
20
21 static class ThreadServiceScope implements Scope {
22
23 static ThreadLocal<Object> threadLocal = new ThreadLocal<Object>();
24
25         @Override
26 public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
27 return new Provider<T>() {
28                 @Override
29 public T get() {
30                     T instance = (T) threadLocal.get();
31 if (instance == null) {
32                         instance = unscoped.get();
33                         threadLocal.set(instance);
34                     }
35 return instance;
36                 }
37             };
38         }
39
40         @Override
41 public String toString() {
42 return “Scopes.ThreadServiceScope”;
43         }
44     }
45
46 public static void main(String[] args) {
47 final Injector inj=Guice.createInjector(new Module() {
48             @Override
49 public void configure(Binder binder) {
50                 binder.bind(Service.class).to(WwwService.class).in(new ThreadServiceScope());
51             }
52         });
53 for(int i=0;i<3;i++) {
54 new Thread(”Thread-”+i) {
55 public void run() {
56 for(int m=0;m<3;m++) {
57                         System.out.println(String.format(”%s-%d:%d”,//
58                                 getName()//
59                                 ,m//
60                                 ,inj.getInstance(Service.class).hashCode()));
61 try {
62                             Thread.sleep(50L);
63                         } catch (Exception e) {
64                         }
65                     }
66                 }
67             }.start();
68         }
69     }
70 }
71

注意,这里用到了《Google Guice 入门教程03 – 依赖注入》的中的两个类Service和WwwService。在本例中ThreadServiceScope 类是一个与线程绑定的作用域(利用ThreadLocal特性),当当前线程中没有构造一个对象的时候先构造一个出来,然后放入线程上下文中,以后每次都从线程中获取对象。第50行是将WwwService服务以ThreadServiceScope的作用域绑定到Service服务上。第57-60行输出当前对象的hashCode,如果此类是同一对象的话就应该输出相同的hashCode。为了看到效果,我们使用3个线程,每个线程输出三次来看结果。

Thread-0-0:18303751
Thread-1-0:23473608
Thread-2-0:21480956
Thread-1-1:23473608
Thread-0-1:18303751
Thread-2-1:21480956
Thread-1-2:23473608
Thread-2-2:21480956
Thread-0-2:18303751

我们看到对于同一个线程(比如说Thread-0)的三次都输出了相同的对象(hashCode为18303751),而与线程2和线程3的hashCode不同。

(特别说明:如果两个线程输出了同一个hashCode不必惊慌,那是因为可能前一个线程生成的对象的地址空间被GC释放了,结果下一个线程使用了上一个线程的相同空间,所以这里使用Thread.sleep来降低这种可
能性)

事实上在guice-servlet-2.0.jar中有与request和session绑定的scope。

com.google.inject.servlet.ServletScopes.REQUEST
com.google.inject.servlet.ServletScopes.SESSION

1.3.6 Eagerly Loading Bindings (立即初始化)

除了可以绑定scope外,对象默认在第一次调用时被创建,也即所谓的延时加载,Guice也允许对象在注入到Guice容器中时就被创建出来(显然这是针对单例模式才有效)。

1 public class EagerSingletonDemo {
2
3 public EagerSingletonDemo() {
4         System.out.println(” constuctor:”+System.nanoTime());
5     }
6 void doit() {
7         System.out.println(”       doit:”+System.nanoTime());
8     }
9 public static void main(String[] args) throws Exception{
10         Injector inj = Guice.createInjector(new Module() {
11             @Override
12 public void configure(Binder binder) {
13                 binder.bind(EagerSingletonDemo.class).asEagerSingleton();
14             }
15         });
16         System.out.println(”before call:”+System.nanoTime());
17         Thread.sleep(100L);
18         inj.getInstance(EagerSingletonDemo.class).doit();
19     }
20 }

结果输出如下:

constuctor:26996967388652
before call:26996967713635
doit:26997069993702

可以看到我们的对象在调用getInstance之前就已经被构造出来了。

1.3.7 Stages (运行阶段)

Guice还有一个特效,可以指定Guice运行模式来控制Guice的加载速度。在com.google.inject.Stage枚举中提供了TOOL,DEVELOPMENT,PRODUCTION三种模式。

TOOL描述的是带有IDE等插件的运行模式;DEVELOPMENT是指在开发阶段只加载自己需要的功能(对于非立即初始化单例对象采用延后加载),这样来降低加载不需要功能的时间;而PRODUCTION模式是指完全加载所有功能(对于单例对象采用立即加载方式),这样可以更早的发现问题,免得等需要某些功能的时候才发现问题(要知道我们某些功能可能需要特定的条件才能触发)。

其实只有比较多的单例对象,并且单例对象构造比较耗时的情况下才能有用。大部分情况下这点性能可能都忽略不计了。

默认情况下Guice采用DEVELOPMENT模式。

1.3.8 Optional Injection (选项注入 )

选项注入描述的是如果不能从Guice容器中注入一个对象,那么可以使用一个默认的对象。看下面的例子。

1 public class OptionalInjectionDemo {
2     @Inject(optional=true)
3     Service service = new WwwService();
4 public static void main(String[] args) {
5         Guice.createInjector(new Module() {
6 public void configure(Binder binder) {
7 //binder.bind(Service.class).to(HomeService.class);
8             }
9         }).getInstance(OptionalInjectionDemo.class).service.execute();
10     }
11 }

上述例子中第2行描述的是选项注入,如果不能从Guice容器中获取一个Service服务那么就使用默认的WwwService,否则就是用获取的服务。如果将第7行注释去掉我们就可以看到实际上调用的是HomeService服务了。

到此为止,Guice依赖注入的基本教程就学习完了,下面的章节我们进入经典的AOP教程学习。