security

来源:互联网 发布:mac双系统开机切换 编辑:程序博客网 时间:2024/06/10 01:43
1.Acegi认证与授权
1.1在Web.xml中添加
    <!--acegi 的filter链代理-->
    <filter>
        <filter-name>Acegi Filter Chain Proxy</filter-name>
        <filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
        <init-param>
            <param-name>targetClass</param-name>
            <param-value>org.acegisecurity.util.FilterChainProxy</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>Acegi Filter Chain Proxy</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
1.2bookstore对每个Component都有单独的配置。这里也参照它的结构,把Acegi的配置文件放在bookstoredemo/src/java/org/springside/bookstoredemo/components/acegi目录下,取名applicationContext-acegi-security.xml,并在bookstoredemo/src/resources/spring目录下新建applicationContext-components.xml文件,内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/spring-beans-2.0.dtd">
<beans>
    <!-- Acegi -->
    <import resource="classpath:org/springside/bookstoredemo/components/acegi/applicationContext-acegi-security.xml"/>

</beans>


1.3在applicationContext-acegi-security.xml中添加刚才在Web.xml文件中声明的org.acegisecurity.util.FilterChainPrxy:
    <bean id="filterChainProxy"
        class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /**=httpSessionContextIntegrationFilter,logoutFilter,authenticationProcessingFilter,securityContextHolderAwareRequestFilter,rememberMeProcessingFilter,anonymousProcessingFilter,exceptionTranslationFilter,filterInvocationInterceptor
            </value>
        </property>
    </bean>
这个bean它主要是装载filterInvocationDefinitionSource指定的filter类,并顺序调用它们的doFilter方法,进行安全服务处理。这个bean总共声明了8个filter。

1.4顺序添加httpSessionContextIntegrationFilter:
    <bean id="httpSessionContextIntegrationFilter"
        class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
这个bean负责每次请求从HttpSession中获取Authentication对象,然后把Authentication存于一个新的ContextHolder对象(其实质上只是一个ThreadLocal对象)中,则让该次请求过程中的任何Filter都可以通过ContextHolder来共享Authentication,而不需要从HttpSession中取,减少传HttpRequest参数的麻烦.在请求完后把Authentication对象保存到HttpSession中供下次请求使用,最后把刚才生成的ContextHolder对象销毁.这样就达到了让Authentication对象跨越多个请求的目的.注意此filter须在调用其他Acegi filter前使用
1.5顺序添加logoutFilter:
    <!-- 注销处理filter -->
    <bean id="logoutFilter"
        class="org.acegisecurity.ui.logout.LogoutFilter">
        <constructor-arg value="/shop/index.do"/>
        <!-- URL redirected to after logout -->
        <constructor-arg>
            <list>
                <ref bean="rememberMeServices"/>
                <bean
                        class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
            </list>
        </constructor-arg>
    </bean>
这个bean负责处理退出登录后所需要的清理工作。它会把session销毁,把ContextHolder清空, 把rememberMeServices从cookies中清除掉,然后重定向到指定的退出登陆页面。

1.6顺序添加authenticationProcessingFilter
    <!-- 表单认证处理filter -->
    <bean id="authenticationProcessingFilter"
        class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
        <property name="authenticationManager"
                ref="authenticationManager"/>
        <property name="authenticationFailureUrl"
                value="/acegilogin.jsp?login_error=1"/>
        <property name="defaultTargetUrl" value="/admin/index.jsp"/>
        <property name="filterProcessesUrl"
                value="/j_acegi_security_check"/>
        <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>
这个bean处理一个认证表单,登陆用的表单必须提交用户名和密码这两个参数给这个filter.由用户名和密码构造一个UsernamePasswordAuthenticationToken,将传给AuthenticationManager的authenticate方法进行认证处理。该filter默认处理filterProcessesUrl属性指定的URL,认证失败会转到authenticationFailureUrl,认证成功会转到defaultTargetUrl页面。

1.7顺序添加securityContextHolderAwareRequestFilter
    <bean id="securityContextHolderAwareRequestFilter"
        class="org.acegisecurity.wrapper.SecurityContextHolderAwareRequestFilter"/>
这个bean保存当前的请求到SavedRequest,并存入Session,然后转到登录页。


1.8顺序添加rememberMeProcessingFilter
    <bean id="rememberMeProcessingFilter"
        class="org.acegisecurity.ui.rememberme.RememberMeProcessingFilter">
        <property name="authenticationManager"
                ref="authenticationManager"/>
        <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>
 这个bean以cookie的形式来保存认证信息。负责在用户登录后在本地机上记录用户cookies信息,免除下次再次登陆。检查 AuthenticationManager 中是否已存在Authentication对象,如果不存在则会调用RememberMeServices的aotoLogin方法来从cookies中 获取Authentication对象。

1.9顺序添加nonymousProcessingFilter
    <bean id="anonymousProcessingFilter"
        class="org.acegisecurity.providers.anonymous.AnonymousProcessingFilter">
        <property name="key" value="changeThis"/>
        <property name="userAttribute"
                value="anonymousUser,ROLE_ANONYMOUS"/>
    </bean>
这个bean负责为当不存在任何授权信息时,自动为Authentication对象添加userAttribute中定义的匿名用户权限


1.10顺序添加exceptionTranslationFilter
    <bean id="exceptionTranslationFilter"
        class="org.acegisecurity.ui.ExceptionTranslationFilter">
        <property name="authenticationEntryPoint">
            <bean
                    class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
                <property name="loginFormUrl" value="/acegilogin.jsp"/>
                <property name="forceHttps" value="false"/>
            </bean>
        </property>
        <property name="accessDeniedHandler">
            <bean
                    class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
                <property name="errorPage" value="/accessDenied.jsp"/>
            </bean>
        </property>
    </bean>
这个bean负责处理各种异常,然后重定向到相应的页面中。

1.11顺序添加filterInvocationInterceptor
    <bean id="filterInvocationInterceptor"
        class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
        <property name="authenticationManager"
                ref="authenticationManager"/>
        <property name="accessDecisionManager">
            <bean class="org.acegisecurity.vote.AffirmativeBased">
                <property name="allowIfAllAbstainDecisions"
                        value="false"/>
                <property name="decisionVoters">
                    <list>
                        <bean class="org.acegisecurity.vote.RoleVoter"/>
                        <bean
                                class="org.acegisecurity.vote.AuthenticatedVoter"/>
                    </list>
                </property>
            </bean>
        </property>
        <property name="objectDefinitionSource"
                ref="filterDefinitionSource"/>
    </bean>
这个bean会首先调用AuthenticationManager判断用户是否已登陆认证,如还没认证成功,则重定向到登陆界面.认证成功,则从 Authentication中获取用户的权限.然后从objectDefinitionSource属性获取各种URL资源所对应的权限.最后调用 AccessDecisionManager来判断用户所拥有的权限与当前受保护的URL资源所对应的权限是否相匹配.如果匹配失败,则返回403错误给 用户.匹配成功则用户可以访问受保护的URL资源

1.12至此,我们在1.3中所声明的8个Filter全部装载完成。试运行,会提示找不到rememberMeServices,这个rememberMeServices在1.6和1.8中声明,添加rememberMeServices:
    <bean id="rememberMeServices"
        class="org.acegisecurity.ui.rememberme.TokenBasedRememberMeServices">
        <property name="userDetailsService" ref="userDetailsService"/>
        <property name="key" value="changeThis"/>
    </bean>
这个bean负责通过以cookie的形式保存先前的用户登录信息。在Authentication对象不存在时, rememberMeProcessingFilter会调用rememberMeServices的autoLogin()方法,尝试在cookie中 获取用户登录信息,如果存在则并返回Authentication对象。在每次用户登录时,如果设置了RememberMe功能,在验证用户身份成功后, 则会调用loginSuccess()方法记录用户信息在cookie中,否则调用loginFail()方法清除cookie。

1.13为1.12添加userDetailsService:
    <bean id="userDetailsService"
        class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
        <property name="dataSource" ref="dataSource"/>
        <property name="usersByUsernameQuery">
            <value>
                select loginid,passwd,1 from ss_users where status='1'
                and loginid = ?
            </value>
        </property>
        <property name="authoritiesByUsernameQuery">
            <value>
                select u.loginid,r.name from ss_users u,ss_roles r,ss_user_role ur
                where u.id=ur.user_id and r.id=ur.role_id and u.status='1'
                and u.loginid=?
            </value>
        </property>
    </bean>


1.14为1.11添加authenticationManager:
    <bean id="authenticationManager"
        class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref local="daoAuthenticationProvider"/>
                <bean
                        class="org.acegisecurity.providers.anonymous.AnonymousAuthenticationProvider">
                    <property name="key" value="changeThis"/>
                </bean>
                <bean
                        class="org.acegisecurity.providers.rememberme.RememberMeAuthenticationProvider">
                    <property name="key" value="changeThis"/>
                </bean>
            </list>
        </property>
    </bean>
AuthenticationManager的其中一个实现是ProviderManager,它负责把身份验证的工作委托给一个或多个Provider(认证提供者).
Provider都是实现AuthenticationProvider接口,该接口有两个方法authenticate()和support(). authenticate()方法会尝试验证用户身份,若验证成功则返回一个Authentication对象,否则抛出一个 AuthenticationException.
support()方法会评估当前Authentication对象是否适合这个Provider来进行进一步的处理,而不是指已经通过.
Provir有多个实现.例如daoAuthenticationProvider,anonymousAuthenticationProvider,rememberMeAuthenticationProvider.

1.15添加daoAuthenticationProvide:
    <bean id="daoAuthenticationProvider"
        class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
        <property name="userDetailsService" ref="userDetailsService"/>
        <property name="userCache" ref="userCache"/>
        <property name="passwordEncoder" ref="passwordEncoder"/>
    </bean>
daoAuthenticationProvider负责提供用户信息,包括用户名和密码。其中取用户名密码的工作就交给 userDetailsService来做。通过userCache来缓存用户信息,减少查询数据库次数。用passwordEncoder来使用加密密 码。userDetailsService的接口实现有jdbcDaoImpl和inMemoryDaoImpl。jdbcDaoImpl通过数据库获取 用户名和密码,而inMemoryDaoImpl则只是通过xml定义的方式来获取。
userCache的接口实现有EhCacheBasedUserCache和NullUserCache。NullUserCache实际上就是不进行缓存。EhCacheBasedUserCache是基于ehcache的开源缓存项目来实现的。
passwordEncoder是使用加密器对用户输入的明文进行加密。Acegi提供了三种加密器:
PlaintextPasswordEncoder---默认,不加密,返回明文.
ShaPasswordEncoder---哈希算法(SHA)加密
d5PasswordEncoder---消息摘要(MD5)加密

1.16添加userCache:
    <bean name="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
        <property name="cache">
            <bean
                    class="org.springframework.cache.ehcache.EhCacheFactoryBean" autowire="byName">
                <property name="cacheManager" ref="cacheManager"/>
                <property name="cacheName" value="userCache"/>
            </bean>
        </property>
    </bean>

1.17添加cacheManager:
    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation">
            <value>classpath:org/springside/bookstoredemo/components/acegi/ehcache.xml</value>
        </property>
    </bean>

1.18添加bookstoredemo/arc/java/org/springside/bookstoredemo/components/acegi/ehcache.xml,内容参照源代码。
1.19添加passwordEncoder:
    <!-- 使用Md5算法加密 -->
    <bean id="passwordEncoder"
        class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/>

1.20添加filterDefinitionSource
    <bean id="filterDefinitionSource"
        class="org.springside.bookstoredemo.components.acegi.intercept.web.DBFilterInvocationDefinitionSource">
        <property name="convertUrlToLowercaseBeforeComparison"
                value="true"/>
        <property name="useAntPath" value="true"/>
        <property name="acegiCacheManager" ref="acegiCacheManager"/>
    </bean>


1.21添加
org.springside.bookstoredemo.components.acegi.domain.Resource;
org.springside.bookstoredemo.components.acegi.domain.Role;
org.springside.bookstoredemo.components.acegi.domain.User;
org.springside.bookstoredemo.components.acegi.resourcedetails.Resourceimport org.springside.bookstoredemo.components.acegi.Constants;
org.springside.bookstoredemo.components.acegi.resourcedetails.ResourceCache;
org.springside.bookstoredemo.components.acegi.cache.AcegiCacheManager;
org.springside.bookstoredemo.components.acegi.resourcedetails.ResourceDetails;
org.springside.bookstoredemo.components.acegi.intercept.web.DBFilterInvocationDefinitionSource.java;
添加
org.springside.bookstoredemo.components.acegi.domain.hbm.Resource.hbm.xml;
org.springside.bookstoredemo.components.acegi.domain.hbm.Role.hbm.xml;
org.springside.bookstoredemo.components.acegi.domain.hbm.Role.hbm.xml;
这几个文件稍稍改下class的映射地址
在dataAccessContext-hibernate.xml中合适地方添加
        <property name="mappingDirectoryLocations">
            <list>
                <value>
                    classpath*:/org/springside/bookstoredemo/components/acegi/domain/hbm/
                </value>
            </list>
        </property>

1.22添加acegiCacheManager
    <bean id="acegiCacheManager" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"
        abstract="false" autowire="byName">
        <property name="transactionManager" ref="transactionManager"/>
        <property name="proxyTargetClass" value="true"/>
        <property name="target">
            <bean class="org.springside.bookstoredemo.components.acegi.cache.AcegiCacheManagerImpl">
                <property name="resourceCache" ref="resourceCache"></property>
                <property name="userCache" ref="userCache"></property>
                <property name="sessionFactory" ref="sessionFactory"></property>
            </bean>
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="modify*">PROPAGATION_REQUIRED</prop>
                <prop key="auth*">PROPAGATION_REQUIRED</prop>
                <prop key="init*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="refresh*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
            </props>
        </property>
    </bean>

1.23添加resourceCache
    <!-- 自建一个resourceCache以存放resource对应的role关系 -->
    <!--
        resource缓存实际执行对象
    -->
    <bean id="resourceCache"
        class="org.springside.bookstoredemo.components.acegi.resourcedetails.ResourceCache"
        autowire="byName">
        <property name="cache">
            <bean id="resourceCacheBackend"
                class="org.springframework.cache.ehcache.EhCacheFactoryBean">
                <property name="cacheManager" ref="cacheManager"/>
                <property name="cacheName" value="resourceCache"/>
            </bean>
        </property>
    </bean>

1.24添加loggerListener
    <!-- This bean is optional; it isn't used by any other bean as it only listens and logs -->
    <bean id="loggerListener"
        class="org.acegisecurity.event.authentication.LoggerListener"/>


1.25打开浏览器,输入http://localhost:8080/bookstoredemo/admin/,提示404错误。我们在webapp 目录下新建文件acegilogin.jsp,并把bookstore例子中的文件内容拷贝过来,再访问http://localhost: 8080/bookstoredemo/admin/,会打开acegilogin,jsp页面,这个页面用来供用户登录。我们先看看Acegi是如何转 到这个登录页面的:首先浏览器发送http://localhost:8080/bookstoredemo/admin/请求,调用 httpSessionContextIntegrationFilter,从HttpSession中获取Authentication对象,然后把 Authentication存于一个新的ContextHolder对象。因为没有Authentication信息,Acegi转到异常处理 Filter(1.10声明),并转向到loginFormUrl。

1.26登录失败的流程
    1.26.1用户不存在
    用户输入错误的用户名或密码,Acegi会转到表单认证Filter(1.6中声明),转向到authenticationFailureUrl;
    1.26.2用户没有权限
    用户登录信息正确,Acegi通过系列数据库查询,会得出用户没有任何权限,Acegi会通过/j_acegi_security_check自动转向到/accessDenied.jsp。
1.26登录成功的流程
    用户登录信息正确,Acegi通过系列数据库查询,得到用户admin对/admin/下的所有请求都有权限(可以自行分析数据库的数据)。Acegi转到表单认证,转向到defaultTargetUrl。

1.27登出流程
    用户登出,如果没有声明注销处理Filter(1.5中声明),会清除登录信息,并最终重定向到/j_acegi_logout上,而如果声明了注销处理,则重定向到constructor-arg的Value上

1.28需要注意的几点
登录用户名和密码必须是j_username,j_password;/j_acegi_logout;/j_acegi_security_check.

另外,还有几个重要的拦截器和组件:
1.29
    <bean id="securityInterceptor"
        class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
        <property name="validateConfigAttributes">
            <value>false</value>
        </property>
        <property name="authenticationManager">
            <ref local="authenticationManager" />
        </property>
        <property name="accessDecisionManager">
            <ref local="accessDecisionManager" />
        </property>
        <property name="runAsManager">
            <ref local="runAsManager" />
        </property>
        <property name="objectDefinitionSource">
            <value>
                sample.contact.ContactManager.create=ROLE_USER
                sample.contact.ContactManager.getAllRecipients=ROLE_ADMIN
            </value>
        </property>
    </bean>
MethodSecurityInterceptor实现了org.aopalliance.intercept.MethodInterceptor接 口.在方法被调用之前,拦截器会先调用AuthenticationManager判断用户身份是否已验证,然后从 objectDefinitionSource中获取方法所应用的权限,再调用AccessDecisionManager来匹配用户权限和方法对应的权 限.如果用户没有足够权限调用当前方法,则抛出AccessDeniedException是方法不能被调用.调用runAsManager,使在调用方 法前动态改变authentication中获取用户权限.
1.30
    <bean id="autoProxyCreator" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
        <property name="proxyTargetClass" value="true" />
        <property name="interceptorNames">
            <list>
                <idref local="securityInterceptor" />
            </list>
        </property>
        <property name="beanNames">
            <list>
                <idref bean="userManager"/>
                <idref bean="roleManager"/>
                <idref bean="rescManager"/>
            </list>
        </property>
    </bean>
1.31
BeanNameAutoProxyCreator
设置AOP代理的最简单方法就是用Spring的BeanNameAutoProxyCreator.在BeanNameAutoProxyCreator中选出你所需要的interceptor,和列出你所需要保护的Bean.
    <bean id="accessDecisionManager" class="org.acegisecurity.vote.UnanimousBased">
        <property name="allowIfAllAbstainDecisions">
            <value>false</value>
        </property>
        <property name="decisionVoters">
            <list>
                <ref local="roleVoter" />
            </list>
        </property>
    </bean>
    <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter" />
AccessDecisionManager接口有decide()和support()方法.decide()方法是进行决策是否批准通过,如果没有抛 出AccessDeniedException则为允许访问资源,否则拒绝访问.support()方法是根据配置属性和受保护资源的类来判断是否需要对 该资源作出决策判断.
AccessDecisionManager有三个实现类,功能各不相同:
AffirmativeBased:当至少有一个Voter投允许票才通过
UnanimousBased:没有Voter投反对票时才通过
ConsensusBased:当所有Voter都投允许票时才通过
1.32
    <bean id="runAsManager" class="org.acegisecurity.runas.RunAsManagerImpl">
        <property name="key">
            <value>my_run_as_password</value>
        </property>
    </bean>
runAsmanager提供了动态替换ContextHolder中Ahthentication对象的功能


2.Book管理
按照前面所讲的Acegi登录成功以后,左边会出现 Book Manager和Shop Manager,意味着我们可以在权限内对Book和Shop进行管理了。

2.1Book和Pojo和Manager我们可以用刚开始建的,现在只要建立Book的Controller:
    新建org.springside.bookstoredemo.web.admin.BookManagerAction.java,内容如下:
public class BookManagerAction extends StrutsEntityAction<Book, BookManager> {
    private BookManager bookManager;
   
    public void setBookManager(BookManager bookManager) {
        this.bookManager = bookManager;
    }
   
   
}
当然必要的import和package不能省。
2.2 在/WEB-INF/web.xml中的struts Action Mapping部分添加struts-congfig-admin.xml路径;
2.3在struts-config-admin.xml中添加
    <form-beans>
        <form-bean name="bookForm" type="org.apache.struts.validator.LazyValidatorForm"/>
    </form-beans>

以及

        <action path="/admin/book" name="bookForm" parameter="method" scope="request" validate="false">
            <forward name="list" path="/WEB-INF/pages/admin/bookList.jsp"/>
            <forward name="edit" path="/WEB-INF/pages/admin/bookForm.jsp"/>
            <forward name="success" path="/admin/book.do" redirect="true"/>
        </action>


2.4新建
            /WEB-INF/pages/admin/bookList.jsp
            /WEB-INF/pages/admin/bookForm.jsp

   
2.5在/WEB-INF/modules/spring-config-admin.xml中添加
    <bean name="/admin/book" class="org.springside.bookstoredemo.web.admin.BookManagerAction"/>
2.6以上步骤完成以后,当我们点击Book Management时,右边能正常出现Boos的列表,但是我们还不能对Book进行添加或编辑等操作,页面会提示出错,而后台出错说明是 Connot find "categories" in any scope,查看bookList.jsp文件我们就知道,这个categories是用来生成一个目录下拉框的。而Book实体里的确是没有 categories这个属性的,所以我们只有自己在BookManagerAction里把categories加进去。
    /**
    * 在List 与 Edit View中放入Category列表.
    *
    * @see StrutsEntityAction#edit(org.apache.struts.action.ActionMapping,org.apache.struts.action.ActionForm,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
    * @see StrutsEntityAction#view(org.apache.struts.action.ActionMapping,org.apache.struts.action.ActionForm,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
    * @see StrutsEntityAction#list(org.apache.struts.action.ActionMapping,org.apache.struts.action.ActionForm,javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
    */
    @Override
    protected void refrenceData(HttpServletRequest request) {
        request.setAttribute("categories", bookManager.getCategories());
    }

2.7新的错误!
System Runtime Error:
No form found under 'bookForm' in locale 'zh_CN'
这个错误是由validation(验证)造成的,在bookForm.jsp文件里有2行
<html:javascript formName="bookForm" staticJavascript="false" dynamicJavascript="true" cdata="false"/>
<script type="text/javascript" src="${ctx}/scripts/validator.jsp"></script>
这2行主要是用来动态生成验证规则的,验证规则描述在struts-config.xml中有声明:
    <plug-in className="org.apache.struts.validator.ValidatorPlugIn">
        <set-property property="pathnames"
                    value="/WEB-INF/validator-rules.xml,/WEB-INF/modules/validation-admin.xml"/>
    </plug-in>
这样,当bookForm.jsp文件被调用时,会按照规则生成javascript语句,但是当validation-admin.xml没有声明或是 有错误的声明bookForm的表单元素时,就会出现这个错误。我们修改validation-admin.xml内容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE form-validation PUBLIC
        "-//Apache Software Foundation//DTD Commons Validator Rules Configuration 1.3.0//EN"
        "http://jakarta.apache.org/commons/dtds/validator_1_3_0.dtd">
<form-validation>
    <formset>
        <form name="bookForm">
            <field property="name" depends="required">
                <arg key="book.name"/>
            </field>
            <field property="unitprice" depends="float">
                <arg key="book.unitprice"/>
            </field>
            <field property="inventory" depends="integer">
                <arg key="book.inventory"/>
            </field>
        </form>
    </formset>
</form-validation>

2.8新的错误
null is required!
我们编辑其中一本书,把书名删掉,再点Save,就会出来1个错误提示框,上面的内容就是“null is required!”其实我们更想要的提示是“书名是必填项!”这是因为我们还没有配置i18n,我们打开 bookstoredemo/src/resources/i18n/cn/messages_zh_CN.properties,会发现内容很少。我把 修改以前的文件和修改后的文件放在了bookstoredemo/src/resources/i18n/cn/下,可以仔细比较这2个文件,要注意表单 名、表单元素名、errors、bookForm里的内容要严格的对应!至于messages.properties文件是在哪里加载的,可以看看 web.xml文件中,有一段代码
    <!-- 默认i18n资源文件 -->
    <context-param>
        <param-name>javax.servlet.jsp.jstl.fmt.localizationContext</param-name>
        <param-value>i18n/messages</param-value>
    </context-param>
    <context-param>
        <param-name>extremecomponentsPreferencesLocation</param-name>
        <param-value>/config/extremetable.properties</param-value>
    </context-param>
    <context-param>
        <param-name>extremecomponentsMessagesLocation</param-name>
        <param-value>i18n/messages</param-value>
    </context-param>
这段代码用来指定默认的i18n资源文件。再看看bookstoredemo/bin/build_compile.xml文件,有一段代码
    <!-- ===================================
            中文i18N文件进行UTF-8转换
        ==================================== -->
    <target name="i18n" description="中文i18N文件进行UTF-8转换">
        <if>
            <available file="${resources.dir}/i18n/cn"/>
            <then>
                <native2ascii src="${resources.dir}/i18n/cn" dest="${resources.dir}/i18n" encoding="UTF-8"/>
            </then>
        </if>
    </target>
这段代码指定了中文的资源文件在i18n/cn目录下。


3.Book订购
3.1顾客Customer、订单Orders、订单项OrderItem、货物Product的表结构分析:顾客可以下多个订单,而订单可以有多个订单项(1张订单可以订购多种货物),1种货物可以在多个订单项里出现。因此,这几张表的关联关系就是:
3.1.1.Customer和Orders形成一对多在关联关系,反过来,Orders和Customer则形成多对一的关联关系;
3.1.2.Orders与OrderItem构成父子集合关系。
3.1.3.OrderItem与Product构成多对一关系。
3.2
    3.2.1.编写带注解的Customer.java,Orders.java,OrderItem.java。在dataAccessContext-hibernate.xml文件中加入
                <value>org.springside.bookstoredemo.model.Customer</value>
                <value>org.springside.bookstoredemo.model.Orders</value>
                <value>org.springside.bookstoredemo.model.OrderItem</value>
    3.2.2.编写订单的Manager类OrderManager.java,CustomerManager。在serviceContext.xml中注入orderManager,customerManager:
                <bean id="orderManager" class="org.springside.bookstoredemo.service.order.OrderManager"/>
                <bean id="customerManager" class="org.springside.bookstoredemo.service.order.CustomerManager"/>
    3.2.3.编写订单的Action类OrderAction.java。在spring-config-shop.xml中加入:
                <bean name="/shop/order" class="org.springside.bookstoredemo.web.shop.OrderAction"/>
        在struts-config-shop.xml中加入:
        <action path="/shop/order" parameter="method" validate="false">
<!--            <forward name="" path=""/> -->
        </action>
    3.2.4.编写购物车Cart.java。在spring-config-shop.xml中加入:
    <bean id="cart" class="org.springside.bookstoredemo.web.shop.support.Cart" scope="session">
        <aop:scoped-proxy/>
    </bean>
    把购物车加载到Session中

原创粉丝点击