Flex Structure
前言
这两天写了一个研究Flex + Java的例子,供大家参考,这个例子主要是出于以下几点考虑的
1. 系统性能和系统可维护性上的平衡(Value Object lazy load)
2. 开发效率和代码可读性上的平衡(Command and CommandManager)
3. 如何让Flex调用服务端的Service(AMF3, Remote Object)
4. 使用Cache Framework提升我们的性能
花絮:其实做项目和生活,管理等等都是一样,做到最好是不太现实的,但要和谐,什么叫和谐?就是在成本,进度,质量等外在压力下把代码写得最好!所以我下面的例子代码也是一样,追求的是一个平衡J
一. 系统性能和系统可维护性上的平衡(Value Object lazy load)
最佳性能时,系统只在网络上传输必要的数据,如显示用户清单时只传输user name和department name。
而结构最优时,传输的却是规范的数据结构。
这个时候矛盾来了
A. 传输规范的数据结构。这时候必然会带上一些冗余数据,如显示用户清单时传输的UserVO,而UserVO里同时也包含了标志这个用户部门的DepartmentVO,这时就会带来不必要的数据传输,如果显示的用户清单有100条,那么这100个UserVO里面的DepartmentVO必然会带来不小的数据冗余。
B. 只在网络上传输必要的数据。这时有两种方法可以做到,设计一个UserListVO,里面包含user name和department name这两样field,然后在Business Logic里组装这个UserListVO。但这种方法显然有个大的缺点,这个VO或对应的业务逻辑代码不可以共用,因为不同的地方会有不同的业务需求,比如有一个模块中会要显示用户的年龄。另一个方法就是,使用规范的数据结构,但只为这些数据结构中必要的栏位设值,如上面所说的,可以只为userVO.departmentVO.name设值,但其它栏位保持null,显然,这个VO的共用性也不好,因为我没法知道这个VO里面的栏位是否已经被设值了。
综上所说,所以我取上面两种方法的一个中间点来解决这个问题(如下图),即使用完整的数据结构来存储数据,但不是必要的数据不会被加载上来,如果要用时,可以通过Lazy Load的方式加载。如UserVO里有DepartmentVO,但在显示清单时不需要user对应的department信息,在编辑时才需要,所以我们可以在popup出用户编辑窗口的时候才在UserVO的getDepartmentVO()方法中加载相应的DepartmentVO。

class diagram for data model
请参见附件中的class diagram for data model
二. 开发效率和代码可读性上的平衡(Command and CommandManager)
往往在开发的时候,标准的结构会多写很多代码,虽然结构很清晰,但老实说,对于我们的项目,好像不需要这样“清晰”,比如Cairngorm中有command, event, controller等等,这确实是一种清晰的结构,但写起来很麻烦,所以我下面设计了一种简化的结构来实现它(如下图)。

class diagram for command
Class Diagram
请参见附件中的class diagram for command
Cache
sequence diagram for command pattern
Sequence Diagram
请参见附件中的sequence diagram for command pattern
关于Command Pattern,请参考以下的链接
http://www.javaworld.com/javaworld/jw-06-2002/jw-0628-designpatterns.html
这里,CommandManager就是那个Invoker。而com.novem.farc.command.UserSaveCommand.datagrid就是那个receiver。
Why not Cairngorm Event or Command?
我们以查找一个user为例,来看看Cairngorm是怎么调用一个Command并返回结果的。
1. 创建一个CairngormEvent,并在这个Event里要有一个userId:Number的field。
2. 创建一个Command,这个Command要实现两个接口,ICommand和IResponder。
3. 创建一个FrontController来建立Event和Command的关连。
然后,在客户端调用的时候,书写如下的代码:
var event: EventFindUser = new EventFindUser ();
event.userId = userVO.id;
CairngormEventDispatcher.getInstance().dispatchEvent( event );
我们现在新的结构是这样实现的:
var command:CommandFindUser = new CommandFindUser();
command.userId = userVO.id;
NovemCommandManager.execute(command);
可以看出来,Cairngorm通过注册Event,并通过Event来传递输入参数,而我们自己的结构是将参数直接传递给Command,所以Cairngorm并没有给我们提供特别的方便,反而增加了不少麻烦的Event,而它提供的这种解耦,也并不实在。
Why not Cairngorm Model Locator?
Cairngorm Model Locator提供的其实是一种静态全局变量。
那么,谁都可以来改变这个Model Locator中的值,这显然是一个很危险的事。
如果大家也和我一样认为Cairngorm Model Locator就是一种静态全局变量的话,我想我在这里不用说得太多,只要去查一下静态全局变量的好处坏处就可以了。
三. 如何让Flex调用服务端的Service(AMF3, Remote Object)
暂且假定,我们的项目使用的Remote Object方式去访问服务端
Why not Cairngorm Delegate?
老规矩,我们先来看看Cairngorm是怎么来调用服务端的
1. 在service.xml里添加配置项
2. 创建Delegate.as,并为RemoteObject添加对应的方法(这里需要为每个服务端对象都创建对应的Delegate和方法,工作量不但不小,而且很烦哦)
再来看看我们的写法吧:
1.在ServiceFactory里添加需要调用的Service和method的名字常量
2.调用方法
ServiceFactory.getService(ServiceFactory.USER_BIZ)
.callService(ServiceFactory.USER_BIZ_Insert, [newVO], this.result);
四. 使用Cache Framework提升我们的性能
有空再做哦……
但主要的思路是使用第三方的Cache工具在业务层做
如何在业务层管理你的Cache
上次初步研究了一下前台与后台的关系,但还遗留了一个Server端的Cache问题。
前言
在看过很多的Cache的文章和讨论后,我是这样使用Cache的
1. 在Session的生命周期内使用Hibernate的First Level Cache来缓存对象(数据访问层,细粒度缓存)
2. 使用EHCache对Value Object在业务层做缓存(粗粒度缓存,写代码实现)
为什么我不想使用Hibernate的二级缓存呢?主要有以下几点思考
为了提高它的性能,我们把Cache和持久层关连起来,值得吗?
有必要所有的地方都做Cache吗?这些性能的提升是客户想要的吗?
哪些地方需要做Cache不是只有业务层才知道吗?
关于Hibernate二级缓存详细的介绍,大家还是看看下面几篇文章吧,讲得很好
分析Hibernate的缓存机制
http://www.enet.com.cn/article/2008/0115/A20080115110243.shtml
hibernate二级缓存攻略
http://www.javaeye.com/topic/18904
Speed Up Your Hibernate Applications with Second-Level Caching
http://www.devx.com/dbzone/Article/29685/1954?pf=true
在现实生活中,在业务层做Cache又会有一些问题
在业务层需要做Cache的方法里要加上添加Cache或清除Cache的代码,这样不但做起来很麻烦,而且把Cache代码和业务逻辑混杂在一起。
在执行一个方法时,哪些关连的Cache需要被清除。如执行了UserBiz.update(userVO)后,需要清除findAll产生的所有Cache,同时也应该把id相同的findById产生的Cache清除。
下面的文章和代码也就是着重解决上面提到的问题
如附图所示,Spring会为所有的Biz方法加上MethodCacheInterceptor.java和 MethodCacheAfterAdvice.java,当方法执行之前,Interceptor会对照Annotation的配置去看此方法的结果需不需要和有没有被Cache,然后决定是否直接从Cache中获得结果(如findAll方法)。而After Advice是在方法执行后决定是否要做一些Cache的清理工作(如update方法)。
具体的Annotation配置方法请参照后面的UserBiz.java
废话和理论还是少说点,上代码才是硬道理
ApplicationContext.xml
Java代码 复制代码
1.
2.
3.
5.
7.
8.
9.
11.
14.
16.
17.
18.
20.
22.
23.
24.
26.
28.
29.
30.
32.
35.
38.
36.
37.
MethodCacheInterceptor.java
Java代码 复制代码
1. package com.novem.common.cache.ehcache;
2.
3. import java.io.Serializable;
4.
5. import net.sf.ehcache.Cache;
6. import net.sf.ehcache.Element;
7.
8. import org.aopalliance.intercept.MethodInterceptor;
9. import org.aopalliance.intercept.MethodInvocation;
10. import org.springframework.beans.factory.InitializingBean;
11. import org.springframework.util.Assert;
12.
13. import com.novem.common.cache.annotation.MethodCache;
14.
15. public class MethodCacheInterceptor implements MethodInterceptor,
16. InitializingBean
17. {
18. private Cache cache;
19.
20. /**
21. * sets cache name to be used
22. */
23. public void setCache(Cache cache)
24. {
25. this.cache = cache;
26. }
27.
28. /**
29. * Checks if required attributes are provided.
30. */
31. public void afterPropertiesSet() throws Exception
32. {
33. Assert.notNull(cache,
34. “A cache is required. Use setCache(Cache) to provide one.”);
35. }
36.
37. /**
38. * main method caches method result if method is configured for caching
39. * method results must be serializable
40. */
41. public Object invoke(MethodInvocation invocation) throws Throwable
42. {
43. // do not need to cache
44. if(!invocation.getMethod().isAnnotationPresent(MethodCache.class)
45. || MethodCache.FALSE.equals(invocation.getMethod().getAnnotation(MethodCache.class).isToCache()))
46. {
47. return invocation.proceed();
48. }
49.
50. String targetName = invocation.getThis().getClass().getName();
51. String methodName = invocation.getMethod().getName();
52. Object[] arguments = invocation.getArguments();
53. Object result;
54.
55. String cacheKey = getCacheKey(targetName, methodName, arguments);
56. Element element = cache.get(cacheKey);
57. if (element == null)
58. {
59. // call target/sub-interceptor
60. result = invocation.proceed();
61.
62. // cache method result
63. element = new Element(cacheKey, (Serializable) result);
64. cache.put(element);
65. }
66. return element.getValue();
67. }
68.
69. /**
70. * creates cache key: targetName.methodName.argument0.argument1…
71. */
72. private String getCacheKey(String targetName, String methodName,
73. Object[] arguments)
74. {
75. StringBuffer sb = new StringBuffer();
76. sb.append(targetName).append(“.”).append(methodName);
77. if ((arguments != null) && (arguments.length != 0))
78. {
79. for (int i = 0; i < arguments.length; i++)
80. {
81. sb.append(".").append(arguments[i]);
82. }
83. }
84.
85. return sb.toString();
86. }
87. }
package com.novem.common.cache.ehcache;
import java.io.Serializable;
import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import com.novem.common.cache.annotation.MethodCache;
public class MethodCacheInterceptor implements MethodInterceptor,
InitializingBean
{
private Cache cache;
/**
* sets cache name to be used
*/
public void setCache(Cache cache)
{
this.cache = cache;
}
/**
* Checks if required attributes are provided.
*/
public void afterPropertiesSet() throws Exception
{
Assert.notNull(cache,
"A cache is required. Use setCache(Cache) to provide one.");
}
/**
* main method caches method result if method is configured for caching
* method results must be serializable
*/
public Object invoke(MethodInvocation invocation) throws Throwable
{
// do not need to cache
if(!invocation.getMethod().isAnnotationPresent(MethodCache.class)
|| MethodCache.FALSE.equals(invocation.getMethod().getAnnotation(MethodCache.class).isToCache()))
{
return invocation.proceed();
}
String targetName = invocation.getThis().getClass().getName();
String methodName = invocation.getMethod().getName();
Object[] arguments = invocation.getArguments();
Object result;
String cacheKey = getCacheKey(targetName, methodName, arguments);
Element element = cache.get(cacheKey);
if (element == null)
{
// call target/sub-interceptor
result = invocation.proceed();
// cache method result
element = new Element(cacheKey, (Serializable) result);
cache.put(element);
}
return element.getValue();
}
/**
* creates cache key: targetName.methodName.argument0.argument1...
*/
private String getCacheKey(String targetName, String methodName,
Object[] arguments)
{
StringBuffer sb = new StringBuffer();
sb.append(targetName).append(".").append(methodName);
if ((arguments != null) && (arguments.length != 0))
{
for (int i = 0; i < arguments.length; i++)
{
sb.append(".").append(arguments[i]);
}
}
return sb.toString();
}
}
MethodCacheAfterAdvice.java
Java代码 复制代码
1. package com.novem.common.cache.ehcache;
2.
3. import java.lang.reflect.Method;
4. import java.util.List;
5.
6. import net.sf.ehcache.Cache;
7.
8. import org.springframework.aop.AfterReturningAdvice;
9. import org.springframework.beans.factory.InitializingBean;
10. import org.springframework.util.Assert;
11.
12. import com.novem.common.cache.annotation.CacheCleanMethod;
13. import com.novem.common.cache.annotation.MethodCache;
14.
15. public class MethodCacheAfterAdvice implements AfterReturningAdvice,
16. InitializingBean
17. {
18. private Cache cache;
19.
20. public void setCache(Cache cache)
21. {
22. this.cache = cache;
23. }
24.
25. public MethodCacheAfterAdvice()
26. {
27. super();
28. }
29.
30. public void afterReturning(Object returnValue, Method method,
31. Object[] args, Object target) throws Throwable
32. {
33. // do not need to remove cache
34. if (!method.isAnnotationPresent(MethodCache.class)
35. || method.getAnnotation(MethodCache.class).cacheCleanMethods().length == 0)
36. {
37. return;
38. }
39. else
40. {
41. String targetName = target.getClass().getName();
42.
43. CacheCleanMethod[] cleanMethods = method.getAnnotation(
44. MethodCache.class).cacheCleanMethods();
45. List list = cache.getKeys();
46. for (int i = 0; i < list.size(); i++)
47. {
48. for (int j = 0; j < cleanMethods.length; j++)
49. {
50. String cacheKey = String.valueOf(list.get(i));
51.
52. StringBuffer tempKey = new StringBuffer();
53. tempKey.append(targetName);
54. tempKey.append(".");
55. tempKey.append(cleanMethods[j].methodName());
56.
57. if (CacheCleanMethod.CLEAN_BY_ID.equals(cleanMethods[j].cleanType()))
58. {
59. tempKey.append(".");
60. tempKey.append(getIdValue(target, method, args[0]));
61. }
62.
63. if (cacheKey.startsWith(tempKey.toString()))
64. {
65. cache.remove(cacheKey);
66. }
67. }
68. }
69. }
70. }
71.
72. private String getIdValue(Object target, Method method, Object idContainer)
73. {
74. String targetName = target.getClass().getName();
75.
76. // get id value
77. String idValue = null;
78. if (MethodCache.TRUE.equals(method.getAnnotation(MethodCache.class)
79. .firstArgIsIdContainer()))
80. {
81. if (idContainer == null)
82. {
83. throw new RuntimeException(
84. "Id container cannot be null for method "
85. + method.getName() + " of " + targetName);
86. }
87.
88. Object id = null;
89. try
90. {
91. Method getIdMethod = idContainer.getClass().getMethod("getId");
92. id = getIdMethod.invoke(idContainer);
93. }
94. catch (Exception e)
95. {
96. throw new RuntimeException("There is no getId method for "
97. + idContainer.getClass().getName());
98. }
99.
100. if (id == null)
101. {
102. throw new RuntimeException("Id cannot be null for method "
103. + method.getName() + " of " + targetName);
104. }
105. idValue = id.toString();
106. }
107. else if (MethodCache.TRUE.equals(method
108. .getAnnotation(MethodCache.class).firstArgIsId()))
109. {
110. if (idContainer == null)
111. {
112. throw new RuntimeException("Id cannot be null for method "
113. + method.getName() + " of " + targetName);
114. }
115. idValue = idContainer.toString();
116. }
117.
118. return idValue;
119. }
120.
121. public void afterPropertiesSet() throws Exception
122. {
123. Assert.notNull(cache,
124. "Need a cache. Please use setCache(Cache) create it.");
125. }
126.
127. }
package com.novem.common.cache.ehcache;
import java.lang.reflect.Method;
import java.util.List;
import net.sf.ehcache.Cache;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import com.novem.common.cache.annotation.CacheCleanMethod;
import com.novem.common.cache.annotation.MethodCache;
public class MethodCacheAfterAdvice implements AfterReturningAdvice,
InitializingBean
{
private Cache cache;
public void setCache(Cache cache)
{
this.cache = cache;
}
public MethodCacheAfterAdvice()
{
super();
}
public void afterReturning(Object returnValue, Method method,
Object[] args, Object target) throws Throwable
{
// do not need to remove cache
if (!method.isAnnotationPresent(MethodCache.class)
|| method.getAnnotation(MethodCache.class).cacheCleanMethods().length == 0)
{
return;
}
else
{
String targetName = target.getClass().getName();
CacheCleanMethod[] cleanMethods = method.getAnnotation(
MethodCache.class).cacheCleanMethods();
List list = cache.getKeys();
for (int i = 0; i < list.size(); i++)
{
for (int j = 0; j < cleanMethods.length; j++)
{
String cacheKey = String.valueOf(list.get(i));
StringBuffer tempKey = new StringBuffer();
tempKey.append(targetName);
tempKey.append(".");
tempKey.append(cleanMethods[j].methodName());
if (CacheCleanMethod.CLEAN_BY_ID.equals(cleanMethods[j].cleanType()))
{
tempKey.append(".");
tempKey.append(getIdValue(target, method, args[0]));
}
if (cacheKey.startsWith(tempKey.toString()))
{
cache.remove(cacheKey);
}
}
}
}
}
private String getIdValue(Object target, Method method, Object idContainer)
{
String targetName = target.getClass().getName();
// get id value
String idValue = null;
if (MethodCache.TRUE.equals(method.getAnnotation(MethodCache.class)
.firstArgIsIdContainer()))
{
if (idContainer == null)
{
throw new RuntimeException(
"Id container cannot be null for method "
+ method.getName() + " of " + targetName);
}
Object id = null;
try
{
Method getIdMethod = idContainer.getClass().getMethod("getId");
id = getIdMethod.invoke(idContainer);
}
catch (Exception e)
{
throw new RuntimeException("There is no getId method for "
+ idContainer.getClass().getName());
}
if (id == null)
{
throw new RuntimeException("Id cannot be null for method "
+ method.getName() + " of " + targetName);
}
idValue = id.toString();
}
else if (MethodCache.TRUE.equals(method
.getAnnotation(MethodCache.class).firstArgIsId()))
{
if (idContainer == null)
{
throw new RuntimeException("Id cannot be null for method "
+ method.getName() + " of " + targetName);
}
idValue = idContainer.toString();
}
return idValue;
}
public void afterPropertiesSet() throws Exception
{
Assert.notNull(cache,
"Need a cache. Please use setCache(Cache) create it.");
}
}
MethodCache.java
Java代码 复制代码
1. package com.novem.common.cache.annotation;
2.
3. import java.lang.annotation.Retention;
4. import java.lang.annotation.RetentionPolicy;
5.
6. @Retention(RetentionPolicy.RUNTIME)
7. public @interface MethodCache
8. {
9. String TO_CACHE = "TO_CACHE";
10. String NOT_TO_CACHE = "NOT_TO_CACHE";
11.
12. String TRUE = "TRUE";
13. String FALSE = "FALSE";
14.
15. public String isToCache() default TO_CACHE;
16.
17. public String firstArgIsId() default FALSE;
18.
19. public String firstArgIsIdContainer() default FALSE;
20.
21. public CacheCleanMethod[] cacheCleanMethods() default {};
22. }
package com.novem.common.cache.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodCache
{
String TO_CACHE = "TO_CACHE";
String NOT_TO_CACHE = "NOT_TO_CACHE";
String TRUE = "TRUE";
String FALSE = "FALSE";
public String isToCache() default TO_CACHE;
public String firstArgIsId() default FALSE;
public String firstArgIsIdContainer() default FALSE;
public CacheCleanMethod[] cacheCleanMethods() default {};
}
CacheCleanMethod.java
Java代码 复制代码
1. package com.novem.common.cache.annotation;
2.
3. import java.lang.annotation.Retention;
4. import java.lang.annotation.RetentionPolicy;
5.
6. @Retention(RetentionPolicy.RUNTIME)
7. public @interface CacheCleanMethod
8. {
9. String CLEAN_ALL = "CLEAN_ALL";
10. String CLEAN_BY_ID = "CLEAN_BY_ID";
11.
12. public String methodName();
13.
14. public String cleanType() default CLEAN_ALL;
15. }
package com.novem.common.cache.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheCleanMethod
{
String CLEAN_ALL = "CLEAN_ALL";
String CLEAN_BY_ID = "CLEAN_BY_ID";
public String methodName();
public String cleanType() default CLEAN_ALL;
}
UserBiz.java
Java代码 复制代码
1. package com.novem.farc.biz;
2.
3. import java.util.List;
4.
5. import com.novem.common.cache.annotation.CacheCleanMethod;
6. import com.novem.common.cache.annotation.MethodCache;
7. import com.novem.farc.vo.UserVO;
8.
9. public interface UserBiz
10. {
11. @MethodCache()
12. public UserVO findById(Long id);
13.
14. @MethodCache()
15. public List findAll(int firstResult, int maxResults);
16.
17. @MethodCache(
18. isToCache = MethodCache.FALSE,
19. firstArgIsIdContainer = MethodCache.TRUE,
20. cacheCleanMethods = {@CacheCleanMethod(methodName="findById", cleanType = CacheCleanMethod.CLEAN_BY_ID),
21. @CacheCleanMethod(methodName="findAll")}
22. )
23. public void update(UserVO vo);
24.
25. @MethodCache(
26. isToCache = MethodCache.FALSE,
27. firstArgIsIdContainer = MethodCache.TRUE,
28. cacheCleanMethods = {@CacheCleanMethod(methodName="findAll")}
29. )
30. public Long insert(UserVO vo);
31.
32. @MethodCache(
33. isToCache = MethodCache.FALSE,
34. firstArgIsId = MethodCache.TRUE,
35. cacheCleanMethods = {@CacheCleanMethod(methodName="findById", cleanType = CacheCleanMethod.CLEAN_BY_ID),
36. @CacheCleanMethod(methodName="findAll")}
37. )
38. public void remove(Long id);
39. }
package com.novem.farc.biz;
import java.util.List;
import com.novem.common.cache.annotation.CacheCleanMethod;
import com.novem.common.cache.annotation.MethodCache;
import com.novem.farc.vo.UserVO;
public interface UserBiz
{
@MethodCache()
public UserVO findById(Long id);
@MethodCache()
public List findAll(int firstResult, int maxResults);
@MethodCache(
isToCache = MethodCache.FALSE,
firstArgIsIdContainer = MethodCache.TRUE,
cacheCleanMethods = {@CacheCleanMethod(methodName="findById", cleanType = CacheCleanMethod.CLEAN_BY_ID),
@CacheCleanMethod(methodName="findAll")}
)
public void update(UserVO vo);
@MethodCache(
isToCache = MethodCache.FALSE,
firstArgIsIdContainer = MethodCache.TRUE,
cacheCleanMethods = {@CacheCleanMethod(methodName="findAll")}
)
public Long insert(UserVO vo);
@MethodCache(
isToCache = MethodCache.FALSE,
firstArgIsId = MethodCache.TRUE,
cacheCleanMethods = {@CacheCleanMethod(methodName="findById", cleanType = CacheCleanMethod.CLEAN_BY_ID),
@CacheCleanMethod(methodName="findAll")}
)
public void remove(Long id);
}
注意:如果@CacheCleanMethod的cleanType = CacheCleanMethod.CLEAN_BY_ID,则此方法的第一个参数一定要是对象的ID(userId)或ID container(UserVO, 并且此对象中要有getId方法)。之所以要有这样的限制,我是觉得在企业开发中,大家follow这样的规则就好,没必要为了能灵活地取出ID再多搞出一些配置项出来。
为什么我不使用XML来配置Cache呢?
下面是我最早的时候写的一个配置XML,但后来发现,使用这种方法,就得为每一个Biz配置一个XML,就和早期的xxx.hbm.xml一样,管理起来比较麻烦,不如Annotation简洁
UserBiz.cache.xml
Java代码 复制代码
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.

Spring Rich Client Project
官方介绍:
What is spring-richclient?
Spring-RCP's mission is to provide an elegant way to build highly-configurable, GUI-standards-following rich-client applications faster by leveraging the Spring Framework, and a rich library of UI factories and support classes.
官方网站:
http://spring-rich-c.sourceforge.net/1.0.0/
使用 Django 和 Python 开发 Web 站点
文档选项
将此页作为电子邮件发送
将此页作为电子邮件发送
级别: 中级
Ian Maurer (ian@itmaurer.com), 资深顾问, Brulant, Inc.
2006 年 7 月 03 日
本系列文章一共有两篇,本文是其中的第一篇。在这一篇文章中,我们将展示 Django 的用法,Django 是 Python 编程语言驱动的一个开源模型-视图-控制器(MVC)风格的 Web 应用程序框架。使用 Django,我们在几分钟之内就可以创建高品质、易维护、数据库驱动的应用程序。
Django 项目是一个定制框架,它源自一个在线新闻 Web 站点,于 2005 年以开源的形式被释放出来。Django 框架的核心组件有:
* 用于创建模型的对象关系映射
* 为最终用户设计的完美管理界面
* 一流的 URL 设计
* 设计者友好的模板语言
* 缓存系统
本文是有关 Python Web 框架的由两篇文章组成的系列文章的第一篇。第二篇文章将向您介绍 TurboGears 框架。
要使用并理解本文中提供的代码,则需要安装 Python,并了解在初学者的水平上如何使用 Python。要查看是否安装了 Python 以及 Python 的版本号,可以输入 python -V。Django 至少需要 2.3.5 版本的 Python,可以从 Python Web 站点上下载它(关于链接请参阅本文后面 参考资料 部分)。我们至少还应该顺便熟悉一下 MVC 架构。
安装 Django
本文使用了 Django 的开发版本,以便能够利用 Django 框架的最新改进。建议您在 0.95 版正式发布之前使用这个版本。关于最新发行版本,请参阅 Django 的 Web 站点(再次请您参阅 参考资料 来获得链接)。
按照以下步骤下载并安装 Django:
清单 1. 下载并安装 Django
~/downloads# svn co http://code.djangoproject.com/svn/django/trunk/ django_src
~/downloads# cd django_src
~/downloads# python setup.py install
回页首
Django 管理工具
在安装 Django 之后,您现在应该已经有了可用的管理工具 django-admin.py。清单 2 给出了这个管理工具中可以使用的一些命令:
清单 2. 使用 Django 管理工具
~/dev$ django-admin.py
usage: django-admin.py action [options]
actions:
adminindex [modelmodule …]
Prints the admin-index template snippet for the given model
module name(s).
… snip …
startapp [appname]
Creates a Django app directory structure for the given app name
in the current directory.
startproject [projectname]
Creates a Django project directory structure for the given
project name in the current directory.
validate
Validates all installed models.
options:
-h, –help show this help message and exit
–settings=SETTINGS Python path to settings module, e.g.
“myproject.settings.main”. If this isn't
provided, the DJANGO_SETTINGS_MODULE
environment variable will be used.
–pythonpath=PYTHONPATH
Lets you manually add a directory the Python
path, e.g. “/home/djangoprojects/myproject”.
回页首
Django 项目和应用程序
要启动 Django 项,请使用 django-admin startproject 命令,如下所示:
清单 3. 启动项目
~/dev$ django-admin.py startproject djproject
上面这个命令会创建一个 djproject 目录,其中包含了运行 Django 项目所需要的基本配置文件:
清单 4. djproject 目录的内容
__init__.py
manage.py
settings.py
urls.py
对于这个项目来说,我们要构建一个职位公告板应用程序 “jobs”。要创建应用程序,可以使用 manage.py 脚本,这是一个特定于项目的 django-admin.py 脚本,其中 settings.py 文件可以自动提供:
清单 5. 使用 manage.py startapp
~/dev$ cd djproject
~/dev/djproject$ python manage.py startapp jobs
这将创建一个应用程序骨架,其中模型有一个 Python 模块,视图有另外一个 Python 模块。jobs 目录中包含以下文件:
清单 6. jobs 应用程序目录中的内容
__init__.py
models.py
views.py
提供应用程序在项目中的位置纯粹是为新 Django 开发人员建立的一种惯例,并不是必需的。一旦开始在几个项目中混合使用应用程序,就可以将应用程序放到自己的命名空间中,并使用设置和主 URL 文件将它们绑定在一起。现在,请按照下面给出的步骤执行操作。
为了使 Django 认识到新应用程序的存在,还需要向 settings.py 文件中的 INSTALLED_APPS 添加一个条目。对于这个职位公告板应用程序来说,我们必须添加字符串 djproject.jobs:
清单 7. 向 settings.py 中添加一个条目
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'djproject.jobs',
)
回页首
创建一个模型
Django 提供了自己的对象关系型数据映射组件(object-relational mapper,ORM)库,它可以通过 Python 对象接口支持动态数据库访问。这个 Python 接口非常有用,功能十分强大,但如果需要,也可以灵活地不使用这个接口,而是直接使用 SQL。
orM 目前提供了对 PostgreSQL、MySQL、SQLite 和 Microsoft® SQL 数据库的支持。
这个例子使用 SQLite 作为后台数据库。SQLite 是一个轻量级数据库,它不需要进行任何配置,自身能够以一个简单文件的形式存在于磁盘上。要使用 SQLite,可以简单地使用 setuptools 来安装 pysqlite(有关 setuptools 的更多资料,尤其是有关 easy_install 工具(需要单独安装)的资料,请参阅 参考资料):
easy_install pysqlite
在使用这个模型之前,需要在设置文件中对数据库进行配置。SQLite 只需要指定数据库引擎和数据库名即可。
清单 8. 在 settings.py 中配置数据库
DATABASE_ENGINE = 'sqlite3'
DATABASE_NAME = '/path/to/dev/djproject/database.db'
DATABASE_USER = ''
DATABASE_PASSWORD = ''
DATABASE_HOST = ''
DATABASE_PORT = ''
这个职位公告板应用程序有两种类型的对象:Location 和 Job。Location 包含 city、state(可选)和 country 字段。Job 包含 location、title、description 和 publish date 字段。
清单 9. jobs/models.py 模块
from django.db import models
class Location(models.Model):
city = models.CharField(maxlength=50)
state = models.CharField(maxlength=50, null=True, blank=True)
country = models.CharField(maxlength=50)
def __str__(self):
if self.state:
return “%s, %s, %s” % (self.city, self.state, self.country)
else:
return “%s, %s” % (self.city, self.country)
class Job(models.Model):
pub_date = models.DateField()
job_title = models.CharField(maxlength=50)
job_description = models.TextField()
location = models.ForeignKey(Location)
def __str__(self):
return “%s (%s)” % (self.job_title, self.location)
__str__ 方法是 Python 中的一个特殊类,它返回对象的字符串表示。Django 在 Admin 工具中显示对象时广泛地使用了这个方法。
要设置这个模型的模式,请返回 manage.py 的 sql 命令。此时模式尚未确定。
清单 10. 使用 manage.py sql 命令查看数据库模式
~/dev/djproject$ python manage.py sql jobs
BEGIN;
Create TABLE “jobs_job” (
“id” integer NOT NULL PRIMARY KEY,
“pub_date” date NOT NULL,
“job_title” varchar(50) NOT NULL,
“job_description” text NOT NULL,
“location_id” integer NOT NULL
);
Create TABLE “jobs_location” (
“id” integer NOT NULL PRIMARY KEY,
“city” varchar(50) NOT NULL,
“state” varchar(50) NULL,
“country” varchar(50) NOT NULL
);
COMMIT;
为了初始化并安装这个模型,请运行数据库命令 syncdb:
~/dev/djproject$ python manage.py syncdb
注意,syncdb 命令要求我们创建一个超级用户帐号。这是因为 django.contrib.auth 应用程序(提供基本的用户身份验证功能)默认情况下是在 INSTALLED_APPS 设置中提供的。超级用户名和密码用来登录将在下一节介绍的管理工具。记住,这是 Django 的超级用户,而不是系统的超级用户。
回页首
查询集
Django 模型通过默认的 Manager 类 objects 来访问数据库。例如,要打印所有 Job 的列表,则应该使用 objects 管理器的 all 方法:
清单 11. 打印所有的职位
>>> from jobs.models import Job
>>> for job in Job.objects.all():
… print job
Manager 类还有两个过滤方法:一个是 filter,另外一个是 exclude。过滤方法可以接受满足某个条件的所有方法,但是排除不满足这个条件的其他方法。下面的查询应该可以给出相同的结果(“gte” 表示 “大于或等于”,而 “lt” 表示 “小于”)。
清单 12. 排除和过滤职位
>>> from jobs.models import Job
>>> from datetime import datetime
>>> q1 = Job.objects.filter(pub_date__gte=datetime(2006, 1, 1))
>>> q2 = Job.objects.exclude(pub_date__lt=datetime(2006, 1, 1))
filter 和 exclude 方法返回一些 QuerySet 对象,这些对象可以链接在一起,甚至可以执行连接操作。下面的 q4 查询会查找从 2006 年 1 月 1 日开始在俄亥俄州的 Cleveland 张贴的职位:
清单 13. 对职位进行更多的排除和过滤
>>> from jobs.models import Job
>>> from datetime import datetime
>>> q3 = Job.objects.filter(pub_date__gte=datetime(2006, 1, 1))
>>> q4 = q3.filter(location__city__exact=”Cleveland”,
… location__state__exact=”Ohio”)
QuerySets 是惰性的,这一点非常不错。这意味着只在对数据库进行求值之后才会对它们执行查询,这会比立即执行查询的速度更快。
这种惰性利用了 Python 的分片(slicing)功能。下面的代码并没有先请求所有的记录,然后对所需要的记录进行分片,而是在实际的查询中使用了 5 作为 OFFSET、10 作为 LIMIT,这可以极大地提高性能。
清单 14. Python 分片
>>> from jobs.models import Job
>>> for job in Job.objects.all()[5:15]
… print job
注意:使用 count 方法可以确定一个 QuerySet 中有多少记录。Python 的 len 方法会进行全面的计算,然后统计那些以记录形式返回的行数,而 count 方法执行的则是真正的 SQL COUNT 操作,其速度更快。我们这样做,数据库管理员会感激我们的。
清单 15. 统计记录数
>>> from jobs.models import Job
>>> print “Count = “, Job.objects.count() # GOOD!
>>> print “Count = “, len(Job.objects.all()) # BAD!
有关的更多信息,请参阅 参考资料 部分给出的 Django “Database API reference” 的链接。
回页首
管理员工具
Django 的最大卖点之一是其一流的管理界面。这个工具是按照最终用户的思路设计的。它为我们的项目提供了很多数据输入工具。
管理工具是 Django 提供的一个应用程序。与 jobs 应用程序一样,在使用之前也必须进行安装。第一个步骤是将应用程序的模块(django.contrib.admin)添加到 INSTALLED_APPS 设置中:
清单 16. 修改 settings.py
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'djproject.jobs',
'django.contrib.admin',
)
要让该管理工具可以通过 /admin URL 使用,只需要简单地取消项目的 urls.py 文件中提供的对应行的内容即可。下一节将详细介绍 URL 的配置。
清单 17. 使管理工具可以通过 urls.py 使用
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls.admin')),
)
这个管理应用程序有自己的数据库模型,但也需要进行安装。我们可以再次使用 syncdb 命令来完成这个过程:
python manage.py syncdb
要查看这个管理工具,可以使用 Django 提供的测试服务器。
清单 18. 使用测试服务器来查看管理工具
~/dev/djproject$ python manage.py runserver
Validating models…
0 errors found.
Django version 0.95 (post-magic-removal), using settings 'djproject.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C (Unix) or CTRL-BREAK (Windows).
现在可以使用 http://localhost:8000/admin 启动管理工具,并使用前面创建的超级用户帐号进行登录。我们注意到现在还没有可用的模块。
要让一个类可以通过管理工具进行访问,我们需要为其创建一个 Admin 子类。然后可以通过为这个子类添加类属性来定制如何对每个类进行管理。清单 19 展示了如何将 Location 类添加到这个管理工具中。
清单 19. 使用管理工具添加 Location 类
class Location(meta.Model):
…
class Admin:
list_display = (“city”, “state”, “country”)
现在就可以通过管理界面来创建、更新和删除 Location 记录了。
图 1. 使用管理工具编辑位置
使用管理工具编辑位置
可以按照 list_display 类的属性指定的城市、州和国家来列出记录并对它们进行排序。
图 2. 使用管理工具显示位置
使用管理工具显示位置
管理工具有无数用来管理每种模块类的选项。清单 20 给出了几个适用于 Job 类的例子:
清单 20. 管理模块类的选项
class Job(meta.Model):
…
class Admin:
list_display = (“job_title”, “location”, “pub_date”)
ordering = [“-pub_date”]
search_fields = (“job_title”, “job_description”)
list_filter = (“location”,)
根据以上设置,职位的标题、位置和发布日期都会在显示职位记录时用到。职位可以按照发布时间进行排序,最开始是最近发布的职位(减号表示降序)。用户可以按照标题和说明来查找职位,管理员可以根据位置对记录进行过滤。
图 3. 使用管理工具显示职位
使用管理工具显示职位
回页首
设计 URL 方案
Django URL 分发系统使用了正则表达式配置模块,它可以将 URL 字符串模式映射为 Python 方法 views。这个系统允许 URL 与底层代码完全脱节,从而实现最大的控制和灵活性。
urls.py 模块被创建和定义成 URL 配置的默认起点(通过 settings.py 模块中的 ROOT_URLCONF 值)。URL 配置文件的惟一要求是必须包含一个定义模式 urlpatterns 的对象。
这个职位公告板应用程序会在启动时打开一个索引和一个详细视图,它们可以通过以下的 URL 映射进行访问:
* /jobs 索引视图:显示最近的 10 个职位
* /jobs/1 详细视图:显示 ID 为 1 的职位信息
这两个视图(索引视图和详细视图)都是在这个 jobs 应用程序的 views.py 模块中实现的。在项目的 urls.py 文件中实现这种配置看起来如下所示:
清单 21. 在 djproject/urls.py 中实现视图的配置
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls.admin')),
(r'^jobs/$', 'djproject.jobs.views.index'),
(r'^jobs/(?P
)
注意
最佳实践是提取出应用程序特有的 URL 模式,并将它们放入应用程序自身中。这样可以取消应用程序与项目的耦合限制,从而更好地实现重用。jobs 使用的应用程序级的 URL 配置文件如下所示:
清单 22. 应用程序级的 URL 配置文件 urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^$', 'djproject.jobs.views.index'),
(r'^(?P
)
由于 view 方法现在都是来自同一个模块,因此第一个参数可以使用这个模块的根名称来指定 djproject.jobs.views,Django 会使用它来查找 index 方法和 detail 方法:
清单 23. jobs/urls.py:查找 index 和 detail 方法
from django.conf.urls.defaults import *
urlpatterns = patterns('djproject.jobs.views',
(r'^$', 'index'),
(r'^(?P
)
尝试上面的 jobs URL 会返回到这个项目中,因为它们是使用 include 函数将其作为一个整体来实现的。应用程序级的 URL 被绑定到下面的 /jobs 部分:
清单 24. djproject/urls.py:将 URL 送回该项目
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^admin/', include('django.contrib.admin.urls.admin')),
(r'^jobs/', include('djproject.jobs.urls')),
)
如果现在尝试使用测试服务器来访问索引页(http://localhost:8000/jobs),会得到一个错误,因为正在调用的视图(djproject.jobs.views.index)不存在。
回页首
实现视图
视图是一个简单的 Python 方法,它接受一个请求对象,负责实现:
* 任何业务逻辑(直接或间接)
* 上下文字典,它包含模板数据
* 使用一个上下文来表示模板
* 响应对象,它将所表示的结果返回到这个框架中
在 Django 中,当一个 URL 被请求时,所调用的 Python 方法称为一个视图(view),这个视图所加载并呈现的页面称为模板(template)。由于这个原因,Django 小组将 Django 称为一个 MVT(model-view-template)框架。另一方面,TurboGears 把自己的方法称作控制器(controller),将所呈现的模板称为视图(view),因此缩写也是 MVC。其区别在于广义的语义,因为它们所实现的内容是相同的。
最简单的视图可能会返回一个使用字符串初始化过的 HttpResponse 对象。创建下面的方法,并生成一个 /jobs HTTP 请求,以确保 urls.py 和 views.py 文件都已经正确设置。
清单 25. jobs/views.py (v1)
from django.utils.httpwrappers import HttpResponse
def index(request):
return HttpResponse(“Job Index View”)
下面的代码将获取最近的 10 个职位,并通过一个模板呈现出来,然后返回响应。没有 下一节 中的模板文件,这段代码就无法 正常工作。
清单 26. jobs/views.py (v2)
from django.template import Context, loader
from django.http import HttpResponse
from jobs.models import Job
from django.template import Context, loader
from django.http import HttpResponse
from jobs.models import Job
def index(request):
object_list = Job.objects.order_by('-pub_date')[:10]
t = loader.get_template('jobs/job_list.html')
c = Context({
'object_list': object_list,
})
return HttpResponse(t.render(c))
在上面的代码中,模板是由 jobs/job_list.html 字符串进行命名的。该模板是使用名为 object_list 的职位列表的上下文呈现的。所呈现的模板字符串随后被传递到 HTTPResponse 构造器中,后者通过这个框架被发送回请求客户机那里。
加载模板、创建内容以及返回新响应对象的步骤在下面都被 render_to_response 方法取代了。新增内容是详细视图方法使用了一个 get_object_or_404 方法,通过该方法使用所提供的参数获取一个 Job 对象。如果没有找到这个对象,就会触发 404 异常。这两个方法减少了很多 Web 应用程序中的样板代码。
清单 27. jobs/views.py (v3)
from django.shortcuts import get_object_or_404, render_to_response
from jobs.models import Job
def index(request):
object_list = Job.objects.order_by('-pub_date')[:10]
return render_to_response('jobs/job_list.html',
{'object_list': object_list})
def detail(request, object_id):
job = get_object_or_404(Job, pk=object_id)
return render_to_response('jobs/job_detail.html',
{'object': job})
注意,detail 使用 object_id 作为一个参数。这是前面提到过的 jobs urls.py 文件中 /jobs/ URL 路径后面的数字。它以后会作为主键(pk)传递给 get_object_or_404 方法。
上面的视图仍然会失败,因为它们所加载和呈现的模板(jobs/job_list.html and jobs/job_detail.html)不存在。
回页首
创建模板
Django 提供了一种模板语言,该语言被设计为能够快速呈现且易于使用。Django 模板是利用 {{ variables }} 和 {% tags %} 中嵌入的文本创建的。变量会使用它们表示的值进行计算和替换。标记用来实现基本的控制逻辑。模板可以用来生成任何基于文本的格式,包括 HTML、XML、CSV 和纯文本。
第一个步骤是定义将模板加载到什么地方。为了简便起见,我们需要在 djproject 下面创建一个 templates 目录,并将这个路径添加到 settings.py 的 TEMPLATE_DIRS 条目中:
清单 28. 在 settings.py 中创建一个 templates 目录
TEMPLATE_DIRS = (
'/path/to/devdir/djproject/templates/',
)
Django 模板支持称为模板继承(template inheritance)的概念,它允许站点设计人员创建一个统一的外表,而不用替换每个模板的内容。我们可以通过使用块标记定义骨干文档或基础文档来使用继承。这些块标记都是使用一些包含内容的页面模板来填充的。这个例子给出了一个包含称为 title、extrahead 和 content 的块的 HTML 骨干:
清单 29. 骨干文档 templates/base.html
{% block extrahead %}{% endblock %}
{% block content %}{% endblock %}
为了取消应用程序与项目之间的耦合,我们使用了一个中间基本文件作为 Job 应用程序所有页面文件的基础。对于这个例子来说,为了简便起见,我们将应用程序的 CSS 放到这个基本文件中。在实际的应用程序中,需要有一个正确配置的 Web 服务器,将这个 CSS 提取出来,并将其放到 Web 服务器所服务的静态文件中。
清单 30. 中间基础文件 templates/jobs/base.html
{% extends “base.html” %}
{% block extrahead %}
{% endblock %}
默认情况下,Django 测试服务器并不会为静态文件提供服务,因为这是 Web 服务器的工作。但是在开发过程中,如果您希望 Django 可以提供图像、样式表等,那么请参阅 参考资料 中有关如何激活这个特性的链接。
现在我们要创建视图所加载并呈现的两个页面模板。jobs/job_list.html 模板简单地循环遍历 object_list,它通过索引视图遍历其内容,并显示一个到每条记录的详细页面的链接。
清单 31. templates/jobs/job_list.html 模板
{% extends “jobs/base.html” %}
{% block title %}Job List{% endblock %}
{% block content %}
Job List
-
{% for job in object_list %}
- {{ job.job_title }}
{% endfor %}
{% endblock %}
jobs/job_detail.html 页面会显示一条称为 job 的记录:
清单 32. templates/jobs/job_detail.html 页面
{% extends “jobs/base” %}
{% block title %}Job Detail{% endblock %}
{% block content %}
Job Detail
–
{{ job.location }}
{% endblock %}
Django 模板语言已经被设计为只能实现有限的功能。这种限制可以为非程序员保持模板的简单性,同时还可以让程序员不会将业务逻辑放到不属于自己的地方,即表示层。请参阅 参考资料 中模板语言文档的链接。
回页首
通用视图
Django 提供了 4 种通用视图(generic view),它们可以让开发人员创建遵循典型模式的应用程序:
* 页面列表/详细页面(与上面的例子类似)
* 基于数据的记录分类(对于新闻或 blog 站点非常有用)
* 对象的创建、更新和删除(CRUD)
* 简单直接的模板表示或简单地对 HTTP 重新进行定向
我们没有创建样板视图方法,而是将所有的业务逻辑都放入了 urls.py 文件中,它们都由 Django 提供的通用视图进行处理。
清单 33. jobs/urls.py 中的通用视图
from django.conf.urls.defaults import *
from jobs.models import Job
info_dict = {
'queryset': Job.objects.all(),
}
urlpatterns = patterns('django.views.generic.list_detail',
(r'^$', 'object_list', info_dict),
(r'^(?P
)
这个 urls.py 文件中的 3 个主要变化如下:
* info_dict 映射对象会为要访问的 Job 提供一个查询集。
* 它使用了 django.views.generic.list_detail,而不是 djproject.jobs.views。
* 真正的视图调用是 object_list 和 object_detail。
这个项目需要遵循一些要求才能让通用视图自动工作:
* 通用详细视图期望获得一个 object_id 参数。
* 模板遵循下面的命名模式:app_label/model_name_list.html (jobs/job_list.html) app_label/model_name_detail.html (jobs/job_detail.html)
* 列表模板处理一个名为 object_list 的列表。
* 详细模板处理一个名为 object 的对象。
更多选项可以通过 info_dict 来传递,其中包括指定每个页面中对象个数的 paginate_by 值。
回页首
结束语
本系列的下一篇文章将介绍 TurboGears,这是另外一个 Python Web 框架;并且会将该框架与 Django 进行比较。
参考资料
学习
* 您可以参阅本文在 developerWorks 全球站点上的 英文原文 。
* 请参阅 Wikipedia 上的 overview of the MVC architecture。
* “使用 Ruby on Rails 快速开发 Web 应用程序”(developerWorks,2005 年 6 月)展示了如何使用 Ruby on Rails 创建基于 Web 的应用程序。
* Python.org 是 Python 编程语言的大本营,在那里可以找到下载 Python 解释器和标准库的链接。
* Python 教程 可以帮助我们快速入门 Python。
* DjangoProject.com 是 Django 框架的主页。其中的文档包括:
o How to install Django,展示了如何在一台开发机器上设置 Django
o Database API reference,使用 Django orM 库的指南
o Django template language,为模板作者准备的一份简单指南
o How to serve static files,如何在开发过程中通过设置 Django 来提供静态文件服务的介绍(不要在产品环境中这样做)
o How to use Django with mod_python,这是一份有关利用 mod_python 组合使用 Django 和 Apache 的指南
o Generic views,展示了如何使用 Django 的通用视图更快地实现通用的 Web 应用程序模式
* Building and Distributing Packages with setuptools 展示了如何安装 setuptools 和 easy_install(Python Eggs 包的一部分)
* Django performance tips 展示了如何使用 Django 处理大量通信
* 在 developerWorks Linux 专区 中可以找到为 Linux 开发人员准备的更多资源。
* 随时关注 developerWorks 技术事件和网络广播。
获得产品和技术
* 订购免费的 SEK for Linux,它有两张 DVD,其中包括最新的 IBM for Linux 的试用版软件,这些软件包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。
* 可以使用 IBM 试用版软件 改进您的下一个开发项目,从 developerWorks 可以直接下载该软件。
讨论
* 通过参与 developerWorks blogs 加入 developerWorks 社区。
关于作者
作者照片:Ian Maurer
Ian Maurer 是 Brulant, Inc. 的一名资深顾问,他擅长使用开源软件和 IBM WebSphere 技术为各个行业开发集成的电子商务解决方案,其中包括消费品和零售行业。Ian 居住在东北部的俄亥俄州,他是 Cleveland Area Python Interest Group 的成员。
分页SQL
表中主键必须为标识列,[ID] int IDENTITY (1,1)
1.分页方案一:(利用Not In和Select TOP分页)
语句形式:
Select TOP 10 *
FROM TestTable
Where (ID NOT IN
(Select TOP 20 id
FROM TestTable
orDER BY id))
orDER BY ID
Select TOP 页大小 *
FROM TestTable
Where (ID NOT IN
(Select TOP 页大小*页数 id
FROM 表
orDER BY id))
orDER BY ID
2.分页方案二:(利用ID大于多少和Select TOP分页)
语句形式:
Select TOP 10 *
FROM TestTable
Where (ID >
(Select MAX(id)
FROM (Select TOP 20 id
FROM TestTable
orDER BY id) AS T))
orDER BY ID
Select TOP 页大小 *
FROM TestTable
Where (ID >
(Select MAX(id)
FROM (Select TOP 页大小*页数 id
FROM 表
orDER BY id) AS T))
orDER BY ID
3.分页方案三:(利用SQL的游标存储过程分页)
create procedure SQLPager
@SQLstr nvarchar(4000), –查询字符串
@currentpage int, –第N页
@pagesize int –每页行数
as
set nocount on
declare @P1 int, –P1是游标的id
@rowcount int
exec sp_cursoropen @P1 output,@sqlstr,@scrollopt=1,@ccopt=1, @rowcount=@rowcount output
select ceiling(1.0*@rowcount/@pagesize) as 总页数–,@rowcount as 总行数,@currentpage as 当前页
set @currentpage=(@currentpage-1)*@pagesize+1
exec sp_cursorfetch @P1,16,@currentpage,@pagesize
exec sp_cursorclose @P1
set nocount off
其它的方案:如果没有主键,可以用临时表,也可以用方案三做,但是效率会低。
建议优化的时候,加上主键和索引,查询效率会提高。
通过SQL 查询分析器,显示比较:我的结论是:
分页方案二:(利用ID大于多少和Select TOP分页)效率最高,需要拼接SQL语句
分页方案一:(利用Not In和Select TOP分页) 效率次之,需要拼接SQL语句
分页方案三:(利用SQL的游标存储过程分页) 效率最差,但是最为通用
我们在编写MIS系统和Web应用程序等系统时,都涉及到与数据库的交互,如果数据库中数据量很大的话,一次检索所有的记录,会占用系统很大的资源,因此我们常常采用,需要多少数据就只从数据库中取多少条记录,即采用分页语句。根据自己使用过的内容,把常见数据库Sql Server,Oracle和My sql的分页语句,从数据库表中的第M条数据开始取N条记录的语句总结如下:
SQL Server
从数据库表中的第M条记录开始取N条记录,利用Top关键字:注意如果Select语句中既有top,又有order by,则是从排序好的结果集中选择:
Select *
FROM ( Select Top N *
FROM (Select Top (M + N – 1) * FROM 表名称 order by 主键 desc) t1 ) t2
order by 主键 asc
例如从表Sys_option(主键为sys_id)中从10条记录还是检索20条记录,语句如下:
Select *
FROM ( Select TOP 20 *
FROM (Select TOP 29 * FROM Sys_option order by sys_id desc) t1) t2
order by sys_id asc
oralce数据库
从数据库表中第M条记录开始检索N条记录
Select *
FROM (Select ROWNUM r,t1.* From 表名称 t1 where rownum < M + N) t2
where t2.r >= M
例如从表Sys_option(主键为sys_id)中从10条记录还是检索20条记录,语句如下:
Select *
FROM (Select ROWNUM R,t1.* From Sys_option where rownum < 30 ) t2
Where t2.R >= 10
My sql数据库
My sql数据库最简单,是利用mysql的LIMIT函数,LIMIT [offset,] rows从数据库表中M条记录开始检索N条记录的语句为:
Select * FROM 表名称 LIMIT M,N
例如从表Sys_option(主键为sys_id)中从10条记录还是检索20条记录,语句如下:
select * from sys_option limit 10,20
Spring.net
第一章. 介绍
1.1. 概览
Spring.NET 是一个关注于.NET企业应用开发的应用程序框架。它能够提供宽广范围的功能,例如依赖注入、面向方面编程(AOP)、数据访问抽象, 以及ASP.NET集成等。基于java的spring框架的核心概念和价值已被应用到.NET。Spring.NET 1.0 包含一个完全功能的依赖注入容器和AOP库。后续的发布将包含对ASP.NET、Remoting和数据访问的支持。下图展现出了 Spring .NET的各个模块。具有黑色阴影的模块包含在1.0版本中,其他模块计划在将来的发布中推出。在很多情况下,你可以在我们的下载网站中发现可以工作的计划模块的实现。

Spring.Core 库是框架的基础, 提供依赖注入功能。Spring.NET中大多数类库依赖或扩展了Spring.Core的功能。IObjectFactory接口提供了一个简单而优雅的工厂模式,移除了对单例和一些服务定位stub写程序的必要。允许你将真正的程序逻辑的配置和依赖的详细情况解耦。作为对IObjectFactory的扩展,IApplicationContext接口也在Spring.Core库中,并且添加了许多企业应用为中心的功能,例如利用资源文件进行文本本地化、事件传播、资源加载等等。
Spring.Aop 库提供对业务对象的面向方面编程(AOP) 的支持。Spring.Aop 库是对Spring.Core库的补充,可为声明性地建立企业应用和为业务对象提供服务提供坚实的基础。
Spring.Web 库扩展了ASP.NET,添加了一些功能,如对ASP.NET页面的依赖注入,双向数据绑定,针对 ASP.NET 1.1的Master pages以及改进的本地化支持。
Spring.Services库可让你将任何“一般”对象(即没有从其他特殊的服务基类继承的对象)暴露为企业服务或远程对象,使得.NET Web services 获得依赖注入的支持,并覆盖属性元数据。此外还提供了对Windows Service的集成。
Spring.Data 库提供了数据访问层的抽象,可以被多个数据访问提供者(从ADO.NET 到多个ORM 提供者)应用。它还包含一个对ADO.NET的抽象层,移除了为ADO.NET编写可怕的编码和声明性的事务管理的必要。
Spring.ORM库提供了对常见对象关系映射库的的集成,提供了一些功能,比如对声明性事务管理的支持。
官方网站:http://www.springframework.net/
官方文档:[urlhttp://www.springframework.net/documentation.html#Reference_Manual_and_API]document
[/url]
官方下载:点击下载
xFire下载及实例
http://xfire.codehaus.org/Download
终于,使用Java完成了一个WebService的例子,其中的一个非常小的问题,折腾了我将近一天的时间。下面给出步骤,说明在Java平台上如何开发WebService。
采用的工具:Eclipse3.1.2 + Tomcat5.5 + XFire1.1 。使用XFire开发WebService应该说非常的容易,只需要按照下面例子的步骤来做:
(1)在Eclipse中新建一个dynamic Web Project ,假设名为XFireZhuweiTest。
(2)导入XFire用户库。该库中应包含xfire-1.1目录下的xfire-all-1.1.jar文件,以及xfire-1.1\lib目录下的所有文件。
(3)将上述的XFire用户库中的所有文件拷贝到XFireZhuweiTest项目的WebContent\WEB-INF\lib目录下。
(4)修改WebContent\WEB-INF\web.xml配置文件的内容,下面是修改后web.xml:
XFireZhuweiTest
org.codehaus.xfire.transport.http.XFireConfigurableServlet
web.xml中添加的servlet映射表明,所有匹配“/services/*”的url请求全部交给org.codehaus.xfire.transport.http.XFireConfigurableServlet来处理。
(5)编写需要发布为WebService的Java类,这个例子中是一个非常简单的MathService.java。
package com.zhuweisky.xfireDemo;
public class MathService
{
public int Add(int a ,int b)
{
return a+b ;
}
}
(6)在WebContent\META-INF目录下新建xfire文件夹,然后在xfire目录下添加一个XFire使用的配置文件services.xml,该配置文件中的内容反映了要将哪些java类发布为web服务。本例中的services.xml内容如下:
XFire会借助Spring来解析services.xml,从中提取需要发布为WebService的配置信息。
很多文章介绍到这里就完了,然而当我按照他们所说的启动WebService ,然后通过http://localhost:8080/XFireZhuweiTest/services/MathService?wsdl 来访问服务描述时,却抛出了异常,说services.xml文件不存在--
“org.springframework.beans.factory.BeanDefinitionStoreException: IOException parsing XML document from class path resource [META-INF/xfire/services.xml]; nested exception is java.io.FileNotFoundException: class path resource [META-INF/xfire/services.xml] cannot be opened because it does not exist”。
(7)非常关键的一点,就是这个小难题花费了我将近一天的时间。
在WebContent\WEB-INF目录下新建classes文件夹,然后需要将WebContent下的整个META-INF文件夹剪切到新建的classes文件夹下。
到这里,项目的完整目录结构如下:
(8)在Package Explorer中选中XFireZhuweiTest项目,右键->Run As ->Run On Server,关联到你机器上的TomCat,然后会启动Tomcat,以启动web服务。(注意,在进行此步骤之前,请先停止TomCat)
(9)在IE中输入 http://localhost:8080/XFireZhuweiTest/services/MathService?wsdl 会得到正确的web服务描述文档。
(10)测试刚发布的webService。我使用C#动态调用Web服务:
//C#
string url = “http://localhost:8080/XFireZhuweiTest/services/MathService” ;
object[] args ={1,2} ;
object result = ESFramework.WebService.WebServiceHelper.InvokeWebService(url ,”Add” ,args) ;
MessageBox.Show(result.ToString());
(关于C#动态调用Web服务,请参见这里)
执行后,弹出对话框,显示结果是3。
spring+xfire+webservice+helloworld
由于我们网站的sso系统要实行夸语言,跨服务器的特点,按照领导的指示
要用webService 我就google找了一下关于webService的资料,发现webService还非常复杂,自然我只能最简单的办法来搞了,找了近一个小时,终于在一个老外的网站上看到了关于xfire插件支持java,最重要的是支持spring框架而且非常简单就实现了功能,好在简单提示英语只有初中水平就可以看懂,要不以我这我英语水平是根本看不懂的,我把例子download下来后查看代码,发现老外还是非常友好的代码在ecilpse下打包后直接在 tomcat发布就ok了,和想像的结果一样
我就不这webService的例子拿出来和大家分享了。
Echo.java
package org.codehaus.xfire.spring.example;
public interface Echo{
String echo(String in);
}
EchoImpl.java
package org.codehaus.xfire.spring.example;
public class EchoImpl implements Echo{
public String echo(String in){
return in;
}
}
applicationContext.xml
这上面的就最简单的spring IoC 下helloworld的例子
这文件xfire-servlet.xml配置是关键:
xfire-servlet.xml
接下来是客户端的test就更简单了
客户端
applicationContext-client.xml
package test;
import java.util.*;
import junit.framework.TestCase;
import org.codehaus.xfire.spring.example.*;
public class WebServiceClientTest extends TestCase {
Echo echo=null;
static {
ApplicationContextFactory.init(/test/applicationContext-client.xml);
}
public static void main(String[] args) {
junit.swingui.TestRunner.run(PlayContextDaoTest.class);
}
protected void setUp() throws Exception {
echo=(Echo)ApplicationContextFactory.getApplicationContext().getBean(testWebService);
super.setUp();
}
protected void tearDown() throws Exception {
super.tearDown();
}
public void testCilient(){
System.out.print(echo.echo(haoha i haohao));
}
}
XmlSchema-1.0.3.jar activation-1.1.jar
bcprov-jdk15-133.jar
commons-attributes-api-2.1.jar
commons-beanutils-1.7.0.jar
commons-codec-1.3.jar
commons-discovery-0.2.jar
commons-httpclient-3.0.jar
commons-logging-1.0.4.jar
jaxb-api-1.0.jar
jaxb-api-2.0EA3.jar
jaxb-impl-1.0.5.jar
jaxb-impl-2.0EA3.jar
jaxb-xjc-2.0EA3.jar
jaxen-1.1-beta-8.jar
jdom-1.0.jar
jmock-1.0.1.jar
junit-3.8.1.jar
log4j-1.2.6.jar
mail-1.4.jar
opensaml-1.0.1.jar
org.mortbay.jetty-5.1.3.jar
relaxngDatatype-20050913.jar
spring-1.2.6.jar
stax-api-1.0.1.jar
stax-utils-snapshot-20040917.jar
sun-jaxws-api-2.0-ea3.jar
sun-saaj-api-2.0-ea3.jar
sun-saaj-impl-2.0-ea3.jar
wsdl4j-1.5.2.jar
wss4j-1.5.0.jar
wstx-asl-2.9.3.jar
xbean-2.1.0.jar
xbean-spring-2.3.jar
xercesImpl-2.6.2.jar
xfire-aegis-1.1.jar
xfire-annotations-1.1.jar
xfire-core-1.1.jar
xfire-distribution-1.1.jar
xfire-generator-1.1.jar
xfire-java5-1.1.jar
xfire-jaxb-1.1.jar
xfire-jaxb2-1.1.jar
xfire-jaxws-1.1.jar
xfire-jsr181-api-1.0-M1.jar
xfire-spring-1.1.jar
xfire-ws-security-1.1.jar
xfire-xmlbeans-1.1.jar
xml-apis-1.0.b2.jar
xmlsec-1.3.0.jar
xsdlib-20050913.jar
Flex程序运行原理
http://www.fluidea.cn/blog/index.php/2007/11/12/36/
此文是我翻译自OReilly出版的 Programming Flex 2 中部分章节,略有修改.
从某种程度上说,即使一点也不了解Flex程序的运行机制,也并不妨碍我们开发Flex程序。不过,弄清楚那些基本的结构还是非常有好处的,起码可以知道它的内部是怎么工作的。这会帮助我们实现一些有趣的功能,比如个性化预加载条,在运行期间加载其它的Flex程序,管理运行期间库元件的加载和卸载等等。更远的方面,理解Flex程序的运行可以帮助完善程序,因为我们将知道如何去优化代码。比如,如果想确定哪些代码是在预加载期间执行的,就得知道如何去捕捉这些事件。
每个Flex程序都是从SystemManager开始的,它是flash.display.MovieClip的子类,一个可视的数据类型, MovieClip支持时间轴。SystemManager有两桢。Flex程序的第一桢用来显示一个下载进度条。这一桢应该尽量使用少量的元素,避免文件尺寸过大,使得可以被快速下载,这时候,Flex框架还有大部分没有加载,只有一些核心的类库被加载,加载结束后,进入第二桢。直到第二桢,程序才完全加载进来,包括程序中使用的Flex框架中的大部分类库。如果我们要个性化预加载条,就得先了解SystemManager的工作流程。
下图演示了程序的初始化流程:

SystemManager的初始化流程
一旦程序中SystemManager的实例进入第二桢,它将创建主程序类的一个实例。SystemManager实例有个属性 “application”,初识值为null,直到此时才被指向主程序实例。然后,主程序的实例开始自身的初始化。这时,依次触发了主程序的内部事件:
preinitialize
表示程序被初始化了但还没有开始创建任何子对象.
initialize
程序创建了子对象但这些子对象还没有调整好布局
creationComplete
程序完成了初始化,并绘制好了所有子对象。
主程序完成了这一切后,抛出applicationComplete 事件,并告诉SystemManager :一切准备完毕,可以开始运行了.
SystemManager也负责管理所有位于程序显示层次最顶层的对象,包括所有的pop up(弹出窗口),cursors(鼠标样式),和tool tips(鼠标提示)都位于SystemManager实例中。
SystemManager的属性“topLevelSystemManager”,代表当前运行在Flash Player中的根元素。在Flex程序中,这个属性总是指向SystemManager实例自身。当一个Flex程序被加载到另一个Flex程序中时,被加载的程序也会拥有自己的SystemManager,但这时候它的topLevelSystemManager属性就会指向主程序的 SystemManager对象。
一般情况下,我们并不需要去使用SystemManager对象,但如果想使用它,也是非常容易的,所有UIComponents的子类(包括Application)都有一个“systemManager”属性,指向当前的SystemManager对象。
在实际开发中,我们可能会使用SystemManager去侦听程序中任何一个可视对象的事件,因为事件冒泡机制告诉我们,冒泡阶段的最后一个对象总是SystemManager。
接下来的内容是:加载另一Flex程序,待续
Flex学习曲线
转自:http://www.51as.com/flex/2007-11-6/ZenMeXueXi-flex.html
原来有人问我:怎样学好Flash?我的回答一般就是:仔细看帮助、多做练习、多看优秀的源码、多上专业论坛参加讨论。
可是Flex来了,于是又有人问:怎样学好Flex?
我不知如何回答,因为我也是Flex新手,也在“仔细看帮助、做练习、看源码、上论坛……”。现在d.CAT的这篇优秀的文章,详细的回答了这个问题。
下面的文章转自d.CAT RIA Blog,由于原文是繁体中文的,所以转载过来的时候我对文章的繁体字部分进行了替换,对一些词语进行了修改以符合简体中文语言习惯,对一些术语进行了注释。
最后,文中所有第一人称处所指的都是原文作者而不是“我”,有麻烦可以找他 :em61:
以下为转载:
==================================================================
*Flex 的基础架构
关于 flex 基本上常被问到的不外乎就是“如何可以学好它?”,要了解这个问题的答案基本上只要看懂下面这个图就OK了。
Flex 的基础架构
*Actionscript 该学的重点
从最底层看起,最下面的 actionscript 3是一切的基础,它是 flash/flex 编程使用的唯一程式语言,因此任何人想学好 flex 第一件事绝对是先摸熟 actionscript 这个语言,包含:
1. 它的基本语法与结构(array, hash, loop, if else…)
2. DisplayList (DisplayObject, DisplayObjectContainer)与 Event system(bubbling, propagating…)
3. Sound, Video, NetConnection 与 Graphics class
掌握 as3 的精华后,接下来就可以进入 flex framework。
*Flex framework 的重点
基本上 flex framework 就是用 actionscript 写成的框架,因此也可以把它看成是 as3的最好示范,看着 framework source 学 actionscript 也是挺不错的,只是路会变很长。
Flex Framework 整个体系非常博大精深,通常一般人不太可能完整把它学完,只需要针对最常用到的部份熟悉就好,图中列出的那三块(component, managers, style/skin)就是我个人认为所有初学者最优先该学会的。
*Component 该学些什么
Component 是整个 flex framework 的基础,几乎80% 的元素都是由 UIComponent 继承而来,例如最根本的它本身就是一个 UIComponent,因此,熟悉 component 就成为学好 flex framework 最根本也最重要的基本功
Flex 内建了 二十几个 UI controls, 例如 Button, DataGrid, HBox等,以种类来分,这些 components 可以概分为三大类:
* Controls: Button, DateChooser, Slider…
* Containers: Box, DividedBox, Panel…
* List: DataGrid, Tree, TileList…
初学者第一步至少该学会怎么用这些元件,了解每个元件的 properties, events, styles, effects…,知道怎么在手册里查它的 API 文件,以及何时该用何种元件。
进一步,则是学会怎么修改这些元件,例如继承一个 Button 下来加上不同的功能,或是写不同的 skin border 来改变它的外观。
再进一步,则是开始研究元件的生命周期,了解每个元件是何时初始化,元件内部有那些关键指令与它们个别的功能,然后可以试着建立自已的 custom component。
这一关看起来容易但实际上最困难,因为 flex 的 component framework 写的非常庞大,虽然乱中有序但要在混沌中看出隐藏的架构然后抓住重点整串提起,就非得有人带着指引正确的途径才比较可能完成。
*manager 是什么
图中最上方的第二块就是 manager。
flex 里有很多的 managers,负责做各种不同的工作(废话…),几个比较重要的包含:
* SystemManager:
它是每个 flex 程序的根源,最先被下载,也最早启动,由它进行一连串的 app boot流程
* StyleManager:
它负责整支app 的 css style 套用与 skin 生成,如果想玩动态 css 载换也靠它
* DragManager:
Flex 最大的卖点就是 drag and drop(拖放),这个 manager 就是背后的英雄,初学者至少要学会怎么处理 drag 行为的五个事件,以及如何在不同元件间做拖放;进阶的玩家则应该深入研究这支 manager 是怎么写成的,详细阅读它的 source 会得到意想不到的无穷乐趣(如果你读完却没有这种感觉,呃,那代表你该再多读几次,如果还是没有,那请私下联络我 😀 )
* ModuleManager:
使用 Flex 开发大型应用程式时,往往会将程式切割成许多小的 module, 这个 manager 就是负责载入并管理所有的 module (包含它的 class partition),初心者或许用不到,但有志深入的玩家一定要很熟。
* CursorManager:
这个用到的时机不是很多,但偶尔要换一下 cursor 时还是会用到,初学者至少要知道怎么用指定的图案去换掉系统cursor。
*Style/Skin 的重点
CSS style 与 skinning 是 Flex 最大的卖点之一,也是开发过程中较为麻烦也最耗时的部份。
初学者应该要彻底了解如何使用 CSS style 来打点一支 flex app 的外观,换颜色、素材,使用外部 assets 修饰介面。
中阶玩家则应该了解 skinning 的系统,包含 programmatic skinning 与 graphical skin,它们俩的差别?使用时机?如何客制化(zrong注1)?
更高阶的玩家则应该熟悉整个 Styling system 的运作模式,外加如何动态载入 css 在 runtime 换掉整个介面。
简而言之,flex app 写的好不好,外行人其实看不太出来,但一支 app UI 美不美则是一翻两瞪眼,比较漂亮的那就先加十分
(当然,有一种情况是刻意用心去美化了介面结果弄巧成拙搞的怨声载道人人喊打,但那种比较不多见,也不是每家公司都会搞到这步田地,就先不讨论)
*学完基本功后下一步
在我的标准里,当一个 developer 对上图内每一块都有中等程度的了解后,就算是完成 flex 养成教育,可以迈向下一个阶段。
也就是开始熟悉 application 的制作手法,这包含
* 了解至少一种以上的开发框架,例如 Cairngorm,老实说我对这个框架没什么好感(因为手法太复杂,只适合超复杂登月计画或火星探勘时使用),但它结构设计良好,又是业界公认的圣杯,等于是专家们共通的语言,因此至少要先了解它在做什么,将来在专业场合才好沟通(俗话说 know the rules so you know what you are breaking, 就是指这情况)
* 接着可以看看比较简单的手法,像 Riawave, Model-Glue:Flex, PureMVC…等,基本上这些框架设计方式都大同小异,每个都有不同的应用场合,可以挑一个喜欢的再自行修改。
* 了解基本的概念,例如 Value Object, DAO, MVC 等,它们在大部份的程式框架里都会出现,早点学会日子比较轻松。
* 接着就是开始实际 coding,写一个中小型规模的app,不论是单纯的 CRUD (zrong注2)程序,或是留言版、电话簿、进销存管理都可以,籍由多写来强化编程的概念,然后透过大量的 peer code review 来找出可改进的地方。
*结论
结论还是老话一句:要入门 flex 超级简单,只要不是白痴应该一小时就行,但要成为可独当一面的专业开发者,路就很长,如果没有走对方向很容易就迷失甚至最后放弃。
换句话说,要能成为职场上真正需要的 professional developer,并不如表面上想象的容易(其实我想每种技术领域跟产业都一样吧),这也是我过去半年来协助很多公司做 recruiting 后的感想。
zrong注1:按客人要求不同定义
zrong注2:CRUD是指在做计算处理时的增加、查询(重新得到数据)、更新和删除(create, retrieve, update, and delete)几个单词的首字母简写。主要被用在描述软件系统中数据库或者持久层的基本操作功能
Flex学习站
今天把收藏夹共享出来,希望对学习Flex的人有所帮助。
一、国外站点
1.资源类
Adobe Flex 2 Component Explorer: 官方的,展示了各种组件的用法,入门必看。
CFlex:很好的一个Flex资源站点,包括教程,新闻,资源站点…… 只是页面有点杂乱,大家一般看右边那一栏就行了。
FlexBox:一个收集了网上很多开源组件的站点,是进阶学习的好帮手。
FlexLib:也是一个开源Flex组件站点,不过与FlexBox不同的是,这个是原创,而FlexBox只是收集。
Flex Developer Center:Adobe Flex开发者中心,经常会有一些好的教程出现。
Adobe Labs:这个不用我说了吧。
Flex.org:http://www.flex.org/ 官方的,基本上应有尽有。
2. Explorers
Flex 2 Style Explorer:用来设计程序样式风格的工具,很好用,现在源代码已经可以下载。
Flex 2 Primitive Explorer:用来调节各种Primitive图形的组件,非官方的,源代码提供下载。
Flex 2 Filter Explorer:用来调节各种滤镜(filter),非官方的,源代码提供下载。
3. Blogs
MXNA:这个不用我说了吧,虽说这不是一个Blog,但是它聚合了所有优秀的Blog,所以把它放在Blog一栏,下面所有的Blog都在这个聚合中。
Alex Uhlmann:http://weblogs.macromedia.com/auhlmann/
Christophe Coenraets:http://coenraets.org/ 特别推荐
Code Slinger:http://blogs.digitalprimates.net/codeSlinger/
Deitte:http://www.deitte.com/
Doug mccune:http://dougmccune.com/blog/ 特别推荐
Flex Doc Team:http://blogs.adobe.com/flexdoc/
Kuwamoto:http://kuwamoto.org/ 特别推荐
Macromedia Consulting:http://weblogs.macromedia.com/mc/
Matt Chotin:http://weblogs.macromedia.com/mchotin/ 特别推荐
Peter Ent:http://weblogs.macromedia.com/pent/ 特别推荐
Quietly Scheming:http://www.quietlyscheming.com/blog/ 特别推荐
ScaleNine Blog:http://www.scalenine.com/blog/index.php 特别推荐
Steven Webster:http://weblogs.macromedia.com/swebster/
EverythingFlex:http://blog.everythingflex.com/ 特别推荐
Alex’s Flex Closet:http://blogs.adobe.com/aharui/ 特别推荐
4. 邮件列表
FlexCoders:http://tech.groups.yahoo.com/group/flexcoders/
Flex Components:http://www.adobe.com/go/flexcomponents 非高级开发者最好别加入
上面是两个比较有名的邮件列表,建议大家提问之前先搜索一下邮件存档,一般都能找到答案,找不到再提问。更多邮件列表请看这里:http://flex.org/community/
5.Cairngorm 相关
Cairngorm Documentation Group 这个里面收集了基本上所有关于Cairngorm的资料
二、国内站点
1.论坛
RIACHINA:前身是RIACN,国内最好的Flex论坛之一。我最初知道Flex从这里开始,对这个站挺有感情,饮水思源,把它排第一。
AnyFlex:国内最好的Flex论坛之一,成立的时间比较早,而且论坛FTP中有很多好的资料。
RIADev:Google网上论坛,d.CAT前辈主持的,一般小问题都能解决。
FlexCoders.cn:刚起步的论坛,不过看域名觉得挺有前途,呵呵。
2.Blogs
Dreamer's Blog:就是本站。我翻译了国外Flex Blog上的大量优秀文章,自认为是国内中文资料最多的站点之一。
Y.X.Shawn:对Flex研究很深入,自己写一些开源的组件。
d.CAT:高级开发者,台湾的,为数不多的华语高级开发者,他还做过一个类似Caringorm的架构。
Kenshin:很早就开始研究Flex了,自己开发过很多东西。
3.Cairngorm
没有。不过我翻译过一个关于Cairngorm 小文档,大概30页左右,或许对你有帮助。您可以在AnyFlex 论坛下载到
友情提示:上面这些站点中,资源类的更新不快,不用天天看;Blog和MXNA值得天天看,当然您也可以关注本站,因为我会把MXNA上的关于Flex的内容整理过来;有问题请先去邮件列表或者论坛中搜索,基本上都能搜索到。
writed by bonashen
Mikel