- 浏览: 414790 次
- 性别:
- 来自: 上海
文章分类
最新评论
-
305954240:
好,好,好文。。。
facebook怎么赚钱?facebook盈利模式解析 -
天外鸭:
你好,我想问一些,那个runsall是哪个版本的命令,我在9. ...
db2常用命令大全 -
tterry:
这个叫热部署的话真是羞煞我等
idea -
Torero:
请求的不是Action的Execute方法, 而是其他方法呢? ...
struts2拦截器实现权限控制 -
fortaotao:
咨询一个问题,<security-constraint& ...
备忘:启用 Tomcat 下的 HTTPS
前面《Appfuse & tapestry 小记》中介绍了Appfuse的基本使用和一些小实例,感觉不过瘾~使用这个第一次让我感觉到“轻量”的J2EE框架,的确有一种爱不释手的感觉~所以就索性另写一篇《Appfuse 源代码分析》把这个“轻量级”的强大框架介绍给大家~少说废话,说来就来~
[Appfuse 源代码分析]
以下我们会以标准的ssh框架来说分析,因为这个骨架基本覆盖了现在最主流的j2EE技术(包括Spring2(Acegi)/Struts2/Hibernate3/Sitemesh/Velocity/XFire/DWR等),下面是建立骨架的命令:
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-basic-struts -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject
按照《Appfuse & tapestry 小记》中介绍的安装步骤安装好源码后,你可以先尝试一下Appfuse的大致功能。Appfuse提供给我们一个最初始的框架,包括用户登录、信息管理、角色管理和一个简单的文件上传功能,以便我们可以更自由的扩展它,当然我建议你在这之前先全面阅读一遍它的代码,这样以后的工作才能更顺手哦~ 下面让我们开始分析代码:
由于我更倾向于用更符合人们接受和思考的顺序记录方式来剖析这个框架,所以下面我拟从配置文件开始,然后进入MVC模式的层次内部,中间穿插介绍事务和安全控制的内容,最后重点分析一些核心代码,希望能有更好的讲解效果~
1. pom.xml & web.xml
既然是Maven管理J2EE项目,首先当然是看看pom.xml和web.xml这两个文件了,关于pom.xml就不多做解释,如果有疑问可以看看之前的文章《Maven2 小记》,一般来说我们需要修改pom.xml尾部的datasource的username和password这两个地方,就可以开始安装,当然如何你想为Appfuse加入一些其他的插件或者扩展库可以在这里控制,我们着重分析一下web.xml(主要介绍filter部分) ...(参考http://blog.csdn.net/halenabc/archive/2005/10/19/509555.aspx)
... ...
<filter>
<filter-name>cacheFilter</filter-name>
<filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class>
</filter>
<filter>
<filter-name>clickstreamFilter</filter-name>
<filter-class>com.opensymphony.clickstream.ClickstreamFilter</filter-class>
</filter>
<!-- 用于区别爬虫和正常的用户流量 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter>
<filter-name>exportFilter</filter-name>
<filter-class>org.displaytag.filter.ResponseOverrideFilter</filter-class>
</filter>
<!-- 用于表格展示/排列/分页等 -->
<filter>
<filter-name>gzipFilter</filter-name>
<filter-class>net.sf.ehcache.constructs.web.filter.GzipFilter</filter-class>
</filter>
<!-- 缓存静态文件,如css/html/js -->
<!--<filter>
<filter-name>lazyLoadingFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>-->
<!-- Use "org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter" if you're using JPA -->
<filter>
<filter-name>localeFilter</filter-name>
<filter-class>com.appfuse.app.webapp.filter.LocaleFilter</filter-class>
</filter>
<!-- 分析获取prefererd locale信息 -->
<filter>
<filter-name>rewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<init-param>
<param-name>logLevel</param-name>
<param-value>log4j</param-value>
</init-param>
</filter>
<!-- Java服务器内部的urlrewrite -->
<filter>
<filter-name>securityFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSecurityFilterChain</param-value>
</init-param>
</filter>
<!-- 整合Spring Security框架 -->
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
</filter>
<filter>
<filter-name>staticFilter</filter-name>
<filter-class>com.appfuse.app.webapp.filter.StaticFilter</filter-class>
<init-param>
<param-name>includes</param-name>
<param-value>/scripts/dojo/*,/dwr/*</param-value>
</init-param>
</filter>
<!-- 允许dojo和dwr的action使用.html作为后缀 -->
<filter>
<filter-name>struts-cleanup</filter-name>
<filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
</filter>
<!-- 管理ActionContext提供与其他框架整合的方案,如sitemesh -->
<filter>
<filter-name>struts</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
... ...
顺便提一下,appfuse的filter都是通过扩展org.springframework.web.filter.OncePerRequestFilter类得来。
另外附上一个请求在Struts2框架中的处理的主要步骤,不是很清楚的朋友可以看一下:
<1> 客户端初始化一个指向Servlet容器(例如Tomcat)的请求
<2> 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)
<3> 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
<4> 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
<5> ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类
<6> ActionProxy创建一个ActionInvocation的实例。
<7> ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
<8> 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper,在上述过程中所有的对象(Action,Results,Interceptors,等)都是通过ObjectFactory来创建的。
2.Startup
这里顺便介绍一下mvn jetty:run的执行过程,从中也可以看到服务器在初始化阶段所进行的一些工作:
compile> 预编译步骤
aspectj:compile
native2ascii:native2ascii
resources:resources
compiler:compile
prepare-data> 准备数据
aspectj:compile
native2ascii:native2ascii
resources:resources
hibernate3:hbm2dll
start-server> 读取配置文件创建规则
jetty:run
确定src目录和web.xml位置并读取以下配置文件(附说明):
/WEB-INF/applicationContext-struts.xml
>>>定义了主要的bean
/WEB-INF/applicationContext.xml
/WEB-INF/decorators.xml
>>>sitemesh的配置文件
/WEB-INF/dwr.xml
/WEB-INF/menu-config.xml
>>>velocity的菜单(权限)配置
/WEB-INF/resin-web.xml
/WEB-INF/security.xml
>>>spring(acegi)security配置
/WEB-INF/sitemesh.xml
/WEB-INF/urlrewrite.xml
>>>重写规则
/WEB-INF/web.xml
/WEB-INF/xfire-servlet.xml
>>>使用xfire做webservice
init-runtime> 最后加载资源/启动监听器
>>>详细如下(参考web.xml中的contextConfigLocation规则):
> classpath:/applicationContext-resources.xml (*.properties/dataSource)
> classpath:/applicationContext-dao.xml (sessionFactory/transactionManager/*Dao)
> classpath:/applicationContext-service.xml (AOP/Transaction/Mail*/velocityEngine/passwordEncoder/*Manager[WebService])
> classpath*:/applicationContext.xml (non-exists)
> /WEB-INF/applicationContext*.xml (loaded)
> /WEB-INF/xfire-servlet.xml (xfire-configs)
> /WEB-INF/security.xml (global-security-configs)
> classpath:/struts.xml (interceptors/actions/admin-actions)
> ... (其他的配置在下面介绍)
>>>END
顺便复习一下servlet的生命周期概念:)
[servlet生命周期:加载实例化>初始化init()>请求处理service()>服务终止destroy()]
3.View-Layer
毫无疑问,Appfuse还是按照MVC规范来设计的,这里我首先想要介绍的是View层,因为我认为对于一个网络系统来说,人们最先看到的就是界面,那么从这里入手应该会比较“人性化”,当然也是因为这部分相对比较简单,循序渐进嘛~
Appfuse使用到的技术包括jstl/sitemesh/velocity/struts/appfuse可以参考(/common/taglibs.jsp)
其中sitemesh用于构造站点界面结构,velocity主要用于一些公用板块,如:菜单、发邮件等
相关的配置文件如下:
/WEB-INF/sitemesh.xml
/WEB-INF/decorators.xml
classpath:/accountCreated.vm
classpath:/cssHorizontalMenu.vm
classpath:/cssVerticalMenu.vm
classpath:/velocity.properties
分析一下相关目录/文件,以下是列表:
/admin/ - 管理页面
/common/ - 公用页面(页头、尾/taglibs)
/decorators/ - sitemesh模板
/images/ - 图片
/scripts/ - js
/styles/ - css
/template/ - freemark模版
/WEB-INF/pages/ - appfuse页面
大家可以参照看一下,由于时间问题,这里没有办法逐个介绍用法了,希望以后有时间补充一下。
4.Model-Layer
抓紧时间我们来看看整个系统的核心部分把,Appfuse使用Hibernate+JPA作为持久层解决方案,更加简洁清晰。
相关的配置文件如下:
classpath:/applicationContext-resources.xml
classpath:/applicationContext-dao.xml
classpath:/applicationContext-service.xml
classpath:/hibernate.cfg.xml
关于JPA可以看看com.appfuse.app.model下面的model实现。
然后DAO包都在com.appfuse.app.dao.hibernate下,使用Spring的HibernateDaoSupport简化代码。
Dao代码本身没有什么值得说的地方,这里主要讲讲appfuse使用spring aop对事务的配置 (值得注意的是这里与传统的通过定义transactionInterceptor的层的区别,个人认为这种方式更加优雅,也更加符合松耦合的设计原则):
看到classpath:/applicationContext-service.xml的xsi:schemaLocation多了如下两行
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
看"AOP: Configuration and Aspects"部分注释
... ...
<!-- =================================================================== -->
<!-- AOP: Configuration and Aspects -->
<!-- =================================================================== -->
<aop:config>
<aop:advisor id="userManagerTx" advice-ref="userManagerTxAdvice" pointcut="execution(* *..service.UserManager.*(..))" order="0"/>
<aop:advisor id="userManagerSecurity" advice-ref="userSecurityAdvice" pointcut="execution(* *..service.UserManager.saveUser(..))" order="1"/>
<aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* *..service.*Manager.*(..))" order="2"/>
</aop:config>
<!-- 配置了三个Transaction规则 -->
<!-- Enable @Transactional support -->
<tx:annotation-driven/>
<!-- 用于开启了事务行为(参考com.appfuse.app.dao.UserDao中使用),这里需要注意的是:Spring团队的建议是你只在具体的类上使用 @Transactional 注解, 而不要注解在接口上。你当然可以在接口(或接口方法)上使用 @Transactional 注解, 但是这只有在你使用基于接口的代理时它才会生效。因为注解是 不能继承 的, 这就意味着如果你正在使用基于类的代理时,事务的设置将不能被基于类的代理所识别,而且对象也不会被事务代理所包装 (这是很糟糕的)。 -->
<!-- Enable @AspectJ support -->
<aop:aspectj-autoproxy/>
<!-- 在启用@AspectJ支持的情况下,在ApplicationContext中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并用于配置在Spring AOP里 -->
<!-- Enable @Configured support -->
<aop:spring-configured/>
<!-- 只要设置 @Configurable 注解中的autowire属性就可以让Spring来自动装配了: @Configurable(autowire=Autowire.BY_TYPE) 或者 @Configurable(autowire=Autowire.BY_NAME,这样就可以按类型或者按名字自动装配了 -->
<tx:advice id="txAdvice">
<tx:attributes>
<!-- Read-only commented out to make things easier for end-users -->
<!-- http://issues.appfuse.org/browse/APF-556 -->
<!--tx:method name="get*" read-only="true"/-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 给所有方法加入通知 -->
<tx:advice id="userManagerTxAdvice">
<tx:attributes>
<tx:method name="save*" rollback-for="UserExistsException"/>
</tx:attributes>
</tx:advice>
<!-- 单独定义save方法,捕获UserExistsException异常 -->
<bean id="userSecurityAdvice" class="com.appfuse.app.service.UserSecurityAdvice"/>
<!-- 单独对service.UserManager.saveUser做限定,只允许administrators修改,改完重新setAuthentication -->
... ...
(参考http://www.redsaga.com/spring_ref/2.0/html/aop.html)
着重介绍一下com.appfuse.app.service.UserSecurityAdvice这个类
... ...
public void before(Method method, Object[] args, Object target) throws Throwable {
// 获得spring-seurity框架的上下文
SecurityContext ctx = SecurityContextHolder.getContext();
// 获得当前的authenticated规则
if (ctx.getAuthentication() != null) {
Authentication auth = ctx.getAuthentication();
boolean administrator = false;
GrantedAuthority[] roles = auth.getAuthorities();
for (GrantedAuthority role1 : roles) {
if (role1.getAuthority().equals(Constants.ADMIN_ROLE)) {
administrator = true;
break;
}
}
// 如果参数args[0]是null或者不是User对象,抛出异常
User user = (User) args[0];
// Makes trust decisions based on whether the passed Authentication is an instance of a defined class.
AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
// allow new users to signup - this is OK b/c Signup doesn't allow setting of roles
boolean signupUser = resolver.isAnonymous(auth);
// 如果是匿名用户则跳过
if (!signupUser) {
User currentUser = getCurrentUser(auth);
// 除了administrator用户,用户没有权限修改其他用户的信息
if (user.getId() != null && !user.getId().equals(currentUser.getId()) && !administrator) {
log.warn("Access Denied: '" + currentUser.getUsername() + "' tried to modify '" + user.getUsername() + "'!");
throw new AccessDeniedException(ACCESS_DENIED);
// 如果你不是administrator用户,又要编辑自己的信息,先要检查你的角色是否符合当前的authenticated规则
} else if (user.getId() != null && user.getId().equals(currentUser.getId()) && !administrator) {
// get the list of roles the user is trying add
Set<String> userRoles = new HashSet<String>();
if (user.getRoles() != null) {
for (Object o : user.getRoles()) {
Role role = (Role) o;
userRoles.add(role.getName());
}
}
// get the list of roles the user currently has
Set<String> authorizedRoles = new HashSet<String>();
for (GrantedAuthority role : roles) {
authorizedRoles.add(role.getAuthority());
}
// if they don't match - access denied
// regular users aren't allowed to change their roles
if (!CollectionUtils.isEqualCollection(userRoles, authorizedRoles)) {
log.warn("Access Denied: '" + currentUser.getUsername() + "' tried to change their role(s)!");
throw new AccessDeniedException(ACCESS_DENIED);
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("Registering new user '" + user.getUsername() + "'");
}
}
}
}
... ...
public void afterReturning(Object returnValue, Method method, Object[] args, Object target)
throws Throwable {
User user = (User) args[0];
// 如果用户存在则判断是否是当前用户,若是则把SecurityContext设置回来
if (user.getVersion() != null) {
// reset the authentication object if current user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
// allow new users to signup - this is OK b/c Signup doesn't allow setting of roles
boolean signupUser = resolver.isAnonymous(auth);
// 如果是匿名用户则跳过
if (auth != null && !signupUser) {
User currentUser = getCurrentUser(auth);
// 如果你编辑过自己的信息,则需要得到最新的权限信息,设置到spring-seurity的上下文中去
if (currentUser.getId().equals(user.getId())) {
auth = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
}
}
... ...
(参考http://www.blogjava.net/DoubleJ/archive/2008/03/04/183796.html)
5.Control-Layer
关于控制层我们放在最后,因为这里可能是最需要添加代码的部分了,和后面的章节可能联系的比较紧。Appfuse使用的是Struts2来作为Action控制器,Struts2我不多说了,实际上更接近于Webwork系统~ 使用起来应该说比老的Struts还是有一定进步的,代码更少,也更多的使用注释器来控制逻辑。
先看看一些关于Action的配置applicationContext-struts.xml:
里面定义了struts.xml需要的bean,最重要的是 com.appfuse.app.webapp.interceptor.UserRoleAuthorizationInterceptor这个是系统 z最主要的权限控制拦截器,appfuse的初始角色有两个:ROLE_ADMIN, ROLE_USER,有关于此的配置文件有/WEB-INF/applicationContext-struts.xml /WEB-INF/menu-config.xml /WEB-INF/security.xml。
所有的Action类都是继承com.opensymphony.xwork2.ActionSupport,拦截器继承com.opensymphony.xwork2.interceptor.Interceptor,都是Struts2的“标配”。
这里涉及到关于Security控制我们重点介绍一下>
A.总体(url)安全控制
最新的Appfuse使用了Spring(Acegi)Security来集中控制整个系统的总体安全策略,更加优雅。看看security.xml的注释(定义见前面的web.xml配置文件,这里主要分析appfuse如何通过识别url,来粗粒度的控制系统安全):
... ...
<http auto-config="true" lowercase-comparisons="false">
<!--intercept-url pattern="/images/*" filters="none"/>
<intercept-url pattern="/styles/*" filters="none"/>
<intercept-url pattern="/scripts/*" filters="none"/-->
<intercept-url pattern="/admin/*" access="ROLE_ADMIN"/>
<intercept-url pattern="/passwordHint.html*" access="ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER"/>
<intercept-url pattern="/signup.html*" access="ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER"/>
<intercept-url pattern="/a4j.res/*.html*" access="ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER"/>
<!-- APF-737, OK to remove line below if you're not using JSF -->
<intercept-url pattern="/**/*.html*" access="ROLE_ADMIN,ROLE_USER"/>
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" login-processing-url="/j_security_check"/>
<remember-me user-service-ref="userDao" key="e37f4b31-0c45-11dd-bd0b-0800200c9a66"/>
</http>
<!-- 配置所有需要拦截过滤的url,登录表单的提交地址和remember-me功能 -->
<authentication-provider user-service-ref="userDao">
<password-encoder ref="passwordEncoder"/>
</authentication-provider>
<!-- 默认使用daoAuthenticationProvider,指明这个 -->
<!-- Override the default password-encoder (SHA) by uncommenting the following and changing the class -->
<!-- <bean id="passwordEncoder" class="org.springframework.security.providers.encoding.ShaPasswordEncoder"/> -->
<!-- 默认使用SHA加密,如果要修改可以在这里设置,Acegi提供了三种加密器,如下:
PlaintextPasswordEncoder—默认,不加密,返回明文.
ShaPasswordEncoder—哈希算法(SHA)加密
Md5PasswordEncoder—消息摘要(MD5)加密
-->
<global-method-security>
<protect-pointcut expression="execution(* *..service.UserManager.getUsers(..))" access="ROLE_ADMIN"/>
<protect-pointcut expression="execution(* *..service.UserManager.removeUser(..))" access="ROLE_ADMIN"/>
</global-method-security>
<!-- 设置更细粒度的方法执行权限 -->
... ...
注意:我们可以取出userDao的实现类(com.appfuse.app.dao.hibernate.UserDaoHibernate)观察代码。可以看到,它实现了org.springframework.security.userdetails.UserDetailsService接口,填充了loadUserByUsername方法,返回一个UserDetails对象,抛出一个 UsernameNotFoundException,这样子daoAuthenticationProvider就可以使用这个pojo来做自己的安全验证了。
B.用户角色控制
在com.appfuse.app.webapp.interceptor.UserRoleAuthorizationInterceptor里面控制,没有权限则返回403。
这是系统里面唯一一个自己定义的拦截器,至于为什么不在上面的Spring(Acegi)Security里面一起配置掉,我想可能是appfuse的设计者想让我们多一点选择吧:)
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/shagoo/archive/2009/04/23/4103937.aspx
[Appfuse 源代码分析]
以下我们会以标准的ssh框架来说分析,因为这个骨架基本覆盖了现在最主流的j2EE技术(包括Spring2(Acegi)/Struts2/Hibernate3/Sitemesh/Velocity/XFire/DWR等),下面是建立骨架的命令:
mvn archetype:create -DarchetypeGroupId=org.appfuse.archetypes -DarchetypeArtifactId=appfuse-basic-struts -DremoteRepositories=http://static.appfuse.org/releases -DarchetypeVersion=2.0.2 -DgroupId=com.mycompany.app -DartifactId=myproject
按照《Appfuse & tapestry 小记》中介绍的安装步骤安装好源码后,你可以先尝试一下Appfuse的大致功能。Appfuse提供给我们一个最初始的框架,包括用户登录、信息管理、角色管理和一个简单的文件上传功能,以便我们可以更自由的扩展它,当然我建议你在这之前先全面阅读一遍它的代码,这样以后的工作才能更顺手哦~ 下面让我们开始分析代码:
由于我更倾向于用更符合人们接受和思考的顺序记录方式来剖析这个框架,所以下面我拟从配置文件开始,然后进入MVC模式的层次内部,中间穿插介绍事务和安全控制的内容,最后重点分析一些核心代码,希望能有更好的讲解效果~
1. pom.xml & web.xml
既然是Maven管理J2EE项目,首先当然是看看pom.xml和web.xml这两个文件了,关于pom.xml就不多做解释,如果有疑问可以看看之前的文章《Maven2 小记》,一般来说我们需要修改pom.xml尾部的datasource的username和password这两个地方,就可以开始安装,当然如何你想为Appfuse加入一些其他的插件或者扩展库可以在这里控制,我们着重分析一下web.xml(主要介绍filter部分) ...(参考http://blog.csdn.net/halenabc/archive/2005/10/19/509555.aspx)
... ...
<filter>
<filter-name>cacheFilter</filter-name>
<filter-class>com.opensymphony.oscache.web.filter.CacheFilter</filter-class>
</filter>
<filter>
<filter-name>clickstreamFilter</filter-name>
<filter-class>com.opensymphony.clickstream.ClickstreamFilter</filter-class>
</filter>
<!-- 用于区别爬虫和正常的用户流量 -->
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter>
<filter-name>exportFilter</filter-name>
<filter-class>org.displaytag.filter.ResponseOverrideFilter</filter-class>
</filter>
<!-- 用于表格展示/排列/分页等 -->
<filter>
<filter-name>gzipFilter</filter-name>
<filter-class>net.sf.ehcache.constructs.web.filter.GzipFilter</filter-class>
</filter>
<!-- 缓存静态文件,如css/html/js -->
<!--<filter>
<filter-name>lazyLoadingFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>-->
<!-- Use "org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter" if you're using JPA -->
<filter>
<filter-name>localeFilter</filter-name>
<filter-class>com.appfuse.app.webapp.filter.LocaleFilter</filter-class>
</filter>
<!-- 分析获取prefererd locale信息 -->
<filter>
<filter-name>rewriteFilter</filter-name>
<filter-class>org.tuckey.web.filters.urlrewrite.UrlRewriteFilter</filter-class>
<init-param>
<param-name>logLevel</param-name>
<param-value>log4j</param-value>
</init-param>
</filter>
<!-- Java服务器内部的urlrewrite -->
<filter>
<filter-name>securityFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
<init-param>
<param-name>targetBeanName</param-name>
<param-value>springSecurityFilterChain</param-value>
</init-param>
</filter>
<!-- 整合Spring Security框架 -->
<filter>
<filter-name>sitemesh</filter-name>
<filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
</filter>
<filter>
<filter-name>staticFilter</filter-name>
<filter-class>com.appfuse.app.webapp.filter.StaticFilter</filter-class>
<init-param>
<param-name>includes</param-name>
<param-value>/scripts/dojo/*,/dwr/*</param-value>
</init-param>
</filter>
<!-- 允许dojo和dwr的action使用.html作为后缀 -->
<filter>
<filter-name>struts-cleanup</filter-name>
<filter-class>org.apache.struts2.dispatcher.ActionContextCleanUp</filter-class>
</filter>
<!-- 管理ActionContext提供与其他框架整合的方案,如sitemesh -->
<filter>
<filter-name>struts</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
</filter>
... ...
顺便提一下,appfuse的filter都是通过扩展org.springframework.web.filter.OncePerRequestFilter类得来。
另外附上一个请求在Struts2框架中的处理的主要步骤,不是很清楚的朋友可以看一下:
<1> 客户端初始化一个指向Servlet容器(例如Tomcat)的请求
<2> 这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin)
<3> 接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请是否需要调用某个Action
<4> 如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy
<5> ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类
<6> ActionProxy创建一个ActionInvocation的实例。
<7> ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
<8> 一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper,在上述过程中所有的对象(Action,Results,Interceptors,等)都是通过ObjectFactory来创建的。
2.Startup
这里顺便介绍一下mvn jetty:run的执行过程,从中也可以看到服务器在初始化阶段所进行的一些工作:
compile> 预编译步骤
aspectj:compile
native2ascii:native2ascii
resources:resources
compiler:compile
prepare-data> 准备数据
aspectj:compile
native2ascii:native2ascii
resources:resources
hibernate3:hbm2dll
start-server> 读取配置文件创建规则
jetty:run
确定src目录和web.xml位置并读取以下配置文件(附说明):
/WEB-INF/applicationContext-struts.xml
>>>定义了主要的bean
/WEB-INF/applicationContext.xml
/WEB-INF/decorators.xml
>>>sitemesh的配置文件
/WEB-INF/dwr.xml
/WEB-INF/menu-config.xml
>>>velocity的菜单(权限)配置
/WEB-INF/resin-web.xml
/WEB-INF/security.xml
>>>spring(acegi)security配置
/WEB-INF/sitemesh.xml
/WEB-INF/urlrewrite.xml
>>>重写规则
/WEB-INF/web.xml
/WEB-INF/xfire-servlet.xml
>>>使用xfire做webservice
init-runtime> 最后加载资源/启动监听器
>>>详细如下(参考web.xml中的contextConfigLocation规则):
> classpath:/applicationContext-resources.xml (*.properties/dataSource)
> classpath:/applicationContext-dao.xml (sessionFactory/transactionManager/*Dao)
> classpath:/applicationContext-service.xml (AOP/Transaction/Mail*/velocityEngine/passwordEncoder/*Manager[WebService])
> classpath*:/applicationContext.xml (non-exists)
> /WEB-INF/applicationContext*.xml (loaded)
> /WEB-INF/xfire-servlet.xml (xfire-configs)
> /WEB-INF/security.xml (global-security-configs)
> classpath:/struts.xml (interceptors/actions/admin-actions)
> ... (其他的配置在下面介绍)
>>>END
顺便复习一下servlet的生命周期概念:)
[servlet生命周期:加载实例化>初始化init()>请求处理service()>服务终止destroy()]
3.View-Layer
毫无疑问,Appfuse还是按照MVC规范来设计的,这里我首先想要介绍的是View层,因为我认为对于一个网络系统来说,人们最先看到的就是界面,那么从这里入手应该会比较“人性化”,当然也是因为这部分相对比较简单,循序渐进嘛~
Appfuse使用到的技术包括jstl/sitemesh/velocity/struts/appfuse可以参考(/common/taglibs.jsp)
其中sitemesh用于构造站点界面结构,velocity主要用于一些公用板块,如:菜单、发邮件等
相关的配置文件如下:
/WEB-INF/sitemesh.xml
/WEB-INF/decorators.xml
classpath:/accountCreated.vm
classpath:/cssHorizontalMenu.vm
classpath:/cssVerticalMenu.vm
classpath:/velocity.properties
分析一下相关目录/文件,以下是列表:
/admin/ - 管理页面
/common/ - 公用页面(页头、尾/taglibs)
/decorators/ - sitemesh模板
/images/ - 图片
/scripts/ - js
/styles/ - css
/template/ - freemark模版
/WEB-INF/pages/ - appfuse页面
大家可以参照看一下,由于时间问题,这里没有办法逐个介绍用法了,希望以后有时间补充一下。
4.Model-Layer
抓紧时间我们来看看整个系统的核心部分把,Appfuse使用Hibernate+JPA作为持久层解决方案,更加简洁清晰。
相关的配置文件如下:
classpath:/applicationContext-resources.xml
classpath:/applicationContext-dao.xml
classpath:/applicationContext-service.xml
classpath:/hibernate.cfg.xml
关于JPA可以看看com.appfuse.app.model下面的model实现。
然后DAO包都在com.appfuse.app.dao.hibernate下,使用Spring的HibernateDaoSupport简化代码。
Dao代码本身没有什么值得说的地方,这里主要讲讲appfuse使用spring aop对事务的配置 (值得注意的是这里与传统的通过定义transactionInterceptor的层的区别,个人认为这种方式更加优雅,也更加符合松耦合的设计原则):
看到classpath:/applicationContext-service.xml的xsi:schemaLocation多了如下两行
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
看"AOP: Configuration and Aspects"部分注释
... ...
<!-- =================================================================== -->
<!-- AOP: Configuration and Aspects -->
<!-- =================================================================== -->
<aop:config>
<aop:advisor id="userManagerTx" advice-ref="userManagerTxAdvice" pointcut="execution(* *..service.UserManager.*(..))" order="0"/>
<aop:advisor id="userManagerSecurity" advice-ref="userSecurityAdvice" pointcut="execution(* *..service.UserManager.saveUser(..))" order="1"/>
<aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* *..service.*Manager.*(..))" order="2"/>
</aop:config>
<!-- 配置了三个Transaction规则 -->
<!-- Enable @Transactional support -->
<tx:annotation-driven/>
<!-- 用于开启了事务行为(参考com.appfuse.app.dao.UserDao中使用),这里需要注意的是:Spring团队的建议是你只在具体的类上使用 @Transactional 注解, 而不要注解在接口上。你当然可以在接口(或接口方法)上使用 @Transactional 注解, 但是这只有在你使用基于接口的代理时它才会生效。因为注解是 不能继承 的, 这就意味着如果你正在使用基于类的代理时,事务的设置将不能被基于类的代理所识别,而且对象也不会被事务代理所包装 (这是很糟糕的)。 -->
<!-- Enable @AspectJ support -->
<aop:aspectj-autoproxy/>
<!-- 在启用@AspectJ支持的情况下,在ApplicationContext中定义的任意带有一个@Aspect切面(拥有@Aspect注解)的bean都将被Spring自动识别并用于配置在Spring AOP里 -->
<!-- Enable @Configured support -->
<aop:spring-configured/>
<!-- 只要设置 @Configurable 注解中的autowire属性就可以让Spring来自动装配了: @Configurable(autowire=Autowire.BY_TYPE) 或者 @Configurable(autowire=Autowire.BY_NAME,这样就可以按类型或者按名字自动装配了 -->
<tx:advice id="txAdvice">
<tx:attributes>
<!-- Read-only commented out to make things easier for end-users -->
<!-- http://issues.appfuse.org/browse/APF-556 -->
<!--tx:method name="get*" read-only="true"/-->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- 给所有方法加入通知 -->
<tx:advice id="userManagerTxAdvice">
<tx:attributes>
<tx:method name="save*" rollback-for="UserExistsException"/>
</tx:attributes>
</tx:advice>
<!-- 单独定义save方法,捕获UserExistsException异常 -->
<bean id="userSecurityAdvice" class="com.appfuse.app.service.UserSecurityAdvice"/>
<!-- 单独对service.UserManager.saveUser做限定,只允许administrators修改,改完重新setAuthentication -->
... ...
(参考http://www.redsaga.com/spring_ref/2.0/html/aop.html)
着重介绍一下com.appfuse.app.service.UserSecurityAdvice这个类
... ...
public void before(Method method, Object[] args, Object target) throws Throwable {
// 获得spring-seurity框架的上下文
SecurityContext ctx = SecurityContextHolder.getContext();
// 获得当前的authenticated规则
if (ctx.getAuthentication() != null) {
Authentication auth = ctx.getAuthentication();
boolean administrator = false;
GrantedAuthority[] roles = auth.getAuthorities();
for (GrantedAuthority role1 : roles) {
if (role1.getAuthority().equals(Constants.ADMIN_ROLE)) {
administrator = true;
break;
}
}
// 如果参数args[0]是null或者不是User对象,抛出异常
User user = (User) args[0];
// Makes trust decisions based on whether the passed Authentication is an instance of a defined class.
AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
// allow new users to signup - this is OK b/c Signup doesn't allow setting of roles
boolean signupUser = resolver.isAnonymous(auth);
// 如果是匿名用户则跳过
if (!signupUser) {
User currentUser = getCurrentUser(auth);
// 除了administrator用户,用户没有权限修改其他用户的信息
if (user.getId() != null && !user.getId().equals(currentUser.getId()) && !administrator) {
log.warn("Access Denied: '" + currentUser.getUsername() + "' tried to modify '" + user.getUsername() + "'!");
throw new AccessDeniedException(ACCESS_DENIED);
// 如果你不是administrator用户,又要编辑自己的信息,先要检查你的角色是否符合当前的authenticated规则
} else if (user.getId() != null && user.getId().equals(currentUser.getId()) && !administrator) {
// get the list of roles the user is trying add
Set<String> userRoles = new HashSet<String>();
if (user.getRoles() != null) {
for (Object o : user.getRoles()) {
Role role = (Role) o;
userRoles.add(role.getName());
}
}
// get the list of roles the user currently has
Set<String> authorizedRoles = new HashSet<String>();
for (GrantedAuthority role : roles) {
authorizedRoles.add(role.getAuthority());
}
// if they don't match - access denied
// regular users aren't allowed to change their roles
if (!CollectionUtils.isEqualCollection(userRoles, authorizedRoles)) {
log.warn("Access Denied: '" + currentUser.getUsername() + "' tried to change their role(s)!");
throw new AccessDeniedException(ACCESS_DENIED);
}
}
} else {
if (log.isDebugEnabled()) {
log.debug("Registering new user '" + user.getUsername() + "'");
}
}
}
}
... ...
public void afterReturning(Object returnValue, Method method, Object[] args, Object target)
throws Throwable {
User user = (User) args[0];
// 如果用户存在则判断是否是当前用户,若是则把SecurityContext设置回来
if (user.getVersion() != null) {
// reset the authentication object if current user
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
AuthenticationTrustResolver resolver = new AuthenticationTrustResolverImpl();
// allow new users to signup - this is OK b/c Signup doesn't allow setting of roles
boolean signupUser = resolver.isAnonymous(auth);
// 如果是匿名用户则跳过
if (auth != null && !signupUser) {
User currentUser = getCurrentUser(auth);
// 如果你编辑过自己的信息,则需要得到最新的权限信息,设置到spring-seurity的上下文中去
if (currentUser.getId().equals(user.getId())) {
auth = new UsernamePasswordAuthenticationToken(user, user.getPassword(), user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(auth);
}
}
}
}
... ...
(参考http://www.blogjava.net/DoubleJ/archive/2008/03/04/183796.html)
5.Control-Layer
关于控制层我们放在最后,因为这里可能是最需要添加代码的部分了,和后面的章节可能联系的比较紧。Appfuse使用的是Struts2来作为Action控制器,Struts2我不多说了,实际上更接近于Webwork系统~ 使用起来应该说比老的Struts还是有一定进步的,代码更少,也更多的使用注释器来控制逻辑。
先看看一些关于Action的配置applicationContext-struts.xml:
里面定义了struts.xml需要的bean,最重要的是 com.appfuse.app.webapp.interceptor.UserRoleAuthorizationInterceptor这个是系统 z最主要的权限控制拦截器,appfuse的初始角色有两个:ROLE_ADMIN, ROLE_USER,有关于此的配置文件有/WEB-INF/applicationContext-struts.xml /WEB-INF/menu-config.xml /WEB-INF/security.xml。
所有的Action类都是继承com.opensymphony.xwork2.ActionSupport,拦截器继承com.opensymphony.xwork2.interceptor.Interceptor,都是Struts2的“标配”。
这里涉及到关于Security控制我们重点介绍一下>
A.总体(url)安全控制
最新的Appfuse使用了Spring(Acegi)Security来集中控制整个系统的总体安全策略,更加优雅。看看security.xml的注释(定义见前面的web.xml配置文件,这里主要分析appfuse如何通过识别url,来粗粒度的控制系统安全):
... ...
<http auto-config="true" lowercase-comparisons="false">
<!--intercept-url pattern="/images/*" filters="none"/>
<intercept-url pattern="/styles/*" filters="none"/>
<intercept-url pattern="/scripts/*" filters="none"/-->
<intercept-url pattern="/admin/*" access="ROLE_ADMIN"/>
<intercept-url pattern="/passwordHint.html*" access="ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER"/>
<intercept-url pattern="/signup.html*" access="ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER"/>
<intercept-url pattern="/a4j.res/*.html*" access="ROLE_ANONYMOUS,ROLE_ADMIN,ROLE_USER"/>
<!-- APF-737, OK to remove line below if you're not using JSF -->
<intercept-url pattern="/**/*.html*" access="ROLE_ADMIN,ROLE_USER"/>
<form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=true" login-processing-url="/j_security_check"/>
<remember-me user-service-ref="userDao" key="e37f4b31-0c45-11dd-bd0b-0800200c9a66"/>
</http>
<!-- 配置所有需要拦截过滤的url,登录表单的提交地址和remember-me功能 -->
<authentication-provider user-service-ref="userDao">
<password-encoder ref="passwordEncoder"/>
</authentication-provider>
<!-- 默认使用daoAuthenticationProvider,指明这个 -->
<!-- Override the default password-encoder (SHA) by uncommenting the following and changing the class -->
<!-- <bean id="passwordEncoder" class="org.springframework.security.providers.encoding.ShaPasswordEncoder"/> -->
<!-- 默认使用SHA加密,如果要修改可以在这里设置,Acegi提供了三种加密器,如下:
PlaintextPasswordEncoder—默认,不加密,返回明文.
ShaPasswordEncoder—哈希算法(SHA)加密
Md5PasswordEncoder—消息摘要(MD5)加密
-->
<global-method-security>
<protect-pointcut expression="execution(* *..service.UserManager.getUsers(..))" access="ROLE_ADMIN"/>
<protect-pointcut expression="execution(* *..service.UserManager.removeUser(..))" access="ROLE_ADMIN"/>
</global-method-security>
<!-- 设置更细粒度的方法执行权限 -->
... ...
注意:我们可以取出userDao的实现类(com.appfuse.app.dao.hibernate.UserDaoHibernate)观察代码。可以看到,它实现了org.springframework.security.userdetails.UserDetailsService接口,填充了loadUserByUsername方法,返回一个UserDetails对象,抛出一个 UsernameNotFoundException,这样子daoAuthenticationProvider就可以使用这个pojo来做自己的安全验证了。
B.用户角色控制
在com.appfuse.app.webapp.interceptor.UserRoleAuthorizationInterceptor里面控制,没有权限则返回403。
这是系统里面唯一一个自己定义的拦截器,至于为什么不在上面的Spring(Acegi)Security里面一起配置掉,我想可能是appfuse的设计者想让我们多一点选择吧:)
本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/shagoo/archive/2009/04/23/4103937.aspx
相关推荐
【Appfuse 源代码分析】Appfuse 是一个基于 Maven 的 Java 开发框架,它集成了多种主流的 J2EE 技术,如 Spring、Struts、Hibernate、Sitemesh、Velocity、XFire 和 DWR 等。通过提供一个基础架构,Appfuse 使得...
AppFuse是一个集成了众多当前最流行开源框架与工具(包括Hibernate、ibatis、Struts、Spring、DBUnit、Maven、Log4J、Struts Menu、Xdoclet、SiteMesh、OSCache、JUnit、JSTL等(现在还有lucene的,无敌了))于一身的...
AppFuse 是一个开源项目,旨在简化Java Web应用程序的开发过程。它提供了一个基础框架,集成了许多流行的开源库,如Spring、Hibernate、Struts或Spring Boot等,帮助开发者快速搭建应用骨架。AppFuse 可以根据选定的...
项目中的 `src/test/java` 目录下包含了各种测试类,你可以根据需要添加或修改这些测试,确保代码的质量。 **8. 部署到生产环境** 当开发完成后,你可以打包应用并部署到生产环境。使用 Maven 的 package 目标生成 ...
- 使用AppFuse提供的脚本或工具,可以自动为数据库表生成相应的Java对象(POJO)以及增删改查(CRUD)操作所需的代码。 - 通过这种方式,开发者可以大大减少重复性的编码工作,更加专注于业务逻辑的实现。 4. **...
AppFuse 是一个基于Java平台的开源项目,旨在加速和简化Web应用程序的开发。它通过集成各种流行框架,如Struts、Spring、Hibernate等,提供了一个项目骨架,使得开发者能够快速搭建新项目的结构。AppFuse分为1.x和...
### Appfuse 学习笔记 #### 一、Appfuse 简介 Appfuse 是一个开源框架,旨在帮助开发者高效地构建企业级应用。通过提供一套完善的架构模板、最佳实践和技术栈组合,使得开发者能够专注于业务逻辑的实现,而不是...
由于appfuse主页的mvn自动生成项目架构代码无法显示,个中原因,你懂的~被墙了。所以在此制作了自动生成代码包,进入之后点击页面可以出现生成代码。
06年时的appfuse,学习SSH架构的经典入门框架。相对比较老的资料,可以欣赏一下当时的架构,向牛人致敬
AppFuse 是一个开源项目,它提供了快速开发Java Web应用程序...同时,记得将项目纳入源代码控制系统,如Subversion,以确保代码的安全和版本管理。对于初学者,AppFuse 提供的快速启动指南和详尽文档是极好的学习资源。
现在,你已经有了一个基本的AppFuse应用,可以根据需要添加功能、修改代码,Maven会帮助你管理依赖并自动化构建过程。 在这个过程中,你将学到如何使用Maven的生命周期和插件,以及如何通过POM文件来管理项目配置。...
8. JUnit和Mockito:单元测试框架和模拟对象库,用于测试代码。 这些库和组件共同构成了AppFuse 2.1的基础架构,使开发者能够快速搭建一个具备基本功能的Web应用,并且可以根据需求进行扩展和定制。使用AppFuse可以...
AppFuse 是一个由 Matt Raible 创建的开源项目,旨在为初学者提供一个快速入门的 J2EE 框架模板。它集成了多种流行的技术,包括 Spring、Hibernate、iBatis、Struts、Xdoclet 和 JUnit,同时也支持 Taperstry 和 JSF...
14. **源代码仓库和赞助商**:提供AppFuse的源代码访问链接和项目赞助信息,鼓励开源贡献。 15. **参考指南**:涵盖AppFuse使用的所有细节,包括配置、模板和最佳实践。 16. **CSS框架和数据库配置**:指导如何...
**Appfuse开发教程** Appfuse 是一个开源项目,它提供了一种快速开发Web应用程序的方式,尤其在使用Java技术栈时。本教程将深入探讨如何利用Appfuse创建数据访问对象(DAO)和简单Java对象(POJO),并进行数据库...
"CertsManSys"可能是一个实际的AppFuse应用示例,包含了完整的源代码,你可以运行这个例子来进一步了解AppFuse的实际应用。 总之,AppFuse是一个强大的工具,可以帮助Java开发者高效地创建MVC Web应用。它将复杂的...
Ant通过XML格式的构建文件(build.xml)定义了各种构建任务,如编译源代码、打包、测试、部署等。 Appfuse的Ant任务列表是开发者进行日常开发、构建和部署的重要参考文档,下面将详细介绍其中的一些关键任务: 1. ...