随笔-295  评论-26  文章-1  trackbacks-0
 

五 FAQ 

5.1 FAQ

  1. Q:   能否脱离Spring框架来使用Acegi?
    A:  虽然Acegi 没有要求必须使用Spring Framework,但事实上Acegi很大程度上利用了Spring的IOC和AOP,很难脱离Spring的单独使用。
  2. Q:  Acegi有对xfire的支持吗?
    A: 有,详见http://jira.codehaus.org/browse/XFIRE-389
  3. Q: 为何无论怎么设置都返回到登陆页面无法成功登陆?
    A:  检查登陆页面或登陆失败页面是否只有ROLE_ANONYMOUS权限

5.2 Acegi 补习班

要了解Acegi,首先要了解以下几个重要概念:

  1. Authentication
    Authentication对象包含了principal, credentials 和 authorities(authorities要赋予给principal的),同时也可以包含一些附加的认证请求信息,如TCP/IP地址和Session id等。
  2. SecurityContextHolder
    SecurityContextHolder包含ThreadLocal私有属性用于存取SecurityContext, SecurityContext包含Authentication私有属性, 看以下一段程序


    public void getSecurityContextInformations() {
      SecurityContext sc = SecurityContextHolder.getContext();
      Authentication auth = sc.getAuthentication();
      Object principal = auth.getPrincipal();
      if (principal instanceof UserDetails) {
       //用户密码
       String password = ((UserDetails) principal).getPassword();
       //用户名称
       String username = ((UserDetails) principal).getUsername();
       //用户权限
       GrantedAuthority[] authorities = ((UserDetails) principal).getAuthorities();
       for (int i = 0; i < authorities.length; i++) {
        String authority = authorities[i].getAuthority();
       }
      }
      Object details = auth.getDetails();
      if (details instanceof WebAuthenticationDetails) {
       //用户session id
       String SessionId = ((WebAuthenticationDetails) details).getSessionId();
      }
     }
  3. AuthenticationManager
    通过Providers验证在当前 ContextHolder中的Authentication对象是否合法。
  4. AccessDecissionManager
    经过投票机制来审批是否批准操作
  5. RunAsManager
    当执行某个操作时,RunAsManager可选择性地替换Authentication对象
  6. Interceptors
    拦截器(如FilterSecurityInterceptor,JoinPoint,MethodSecurityInterceptor等)用于协调授权,认证等操作
posted @ 2007-09-12 14:46 华梦行 阅读(128) | 评论 (0)编辑 收藏

三 Acegi安全系统扩展 

      相信side对Acegi的扩展会给你耳目一新的感觉,提供完整的扩展功能,管理界面,中文注释和靠近企业的安全策略。side只对Acegi不符合企业应用需要的功能进行扩展,尽量不改动其余部分来实现全套权限管理功能,以求能更好地适应Acegi升级。

3.1 基于角色的权限控制(RBAC)

    Acegi 自带的 sample 表设计很简单: users表{username,password,enabled} authorities表{username,authority},这样简单的设计无法适应复杂的权限需求,故SpringSide选用RBAC模型对权限控制数据库表进行扩展。 RBAC引入了ROLE的概念,使User(用户)和Permission(权限)分离,一个用户拥有多个角色,一个角色拥有有多个相应的权限,从而减少了权限管理的复杂度,可更灵活地支持安全策略。

    同时,我们也引入了resource(资源)的概念,一个资源对应多个权限,资源分为ACL,URL,和FUNTION三种。注意,URL和FUNTION的权限命名需要以AUTH_开头才会有资格参加投票, 同样的ACL权限命名需要ACL_开头。


3.2 管理和使用EhCache

3.2.1 设立缓存

在SpringSide里的 Acegi 扩展使用 EhCache 就作为一种缓存解决方案,以缓存用户和资源的信息和相对应的权限信息。

首先需要一个在classpath的ehcache.xml 文件,用于配置EhCache。

				
						
								<ehcache>
        <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            overflowToDisk="true"
            timeToIdleSeconds="0"
            timeToLiveSeconds="0"
            diskPersistent="false"
           diskExpiryThreadIntervalSeconds= "120"/>
    <!-- acegi cache-->
    <cache name="userCache"
           maxElementsInMemory="10000"
           eternal="true"
          overflowToDisk= "true"/>
    <!-- acegi cache-->
    <cache name="resourceCache"
           maxElementsInMemory="10000"
           eternal="true"
           overflowToDisk="true"/>
</ehcache>

     maxElementsInMemory设定了允许在Cache中存放的数据数目,eternal设定Cache是否会过期,overflowToDisk设定内存不足的时候缓存到硬盘,timeToIdleSeconds和timeToLiveSeconds设定缓存游离时间和生存时间,diskExpiryThreadIntervalSeconds设定缓存在硬盘上的生存时间,注意当eternal="true"时,timeToIdleSeconds,timeToLiveSeconds和diskExpiryThreadIntervalSeconds都是无效的。

<defaultCache>是除制定的Cache外其余所有Cache的设置,针对Acegi 的情况, 专门设置了userCache和resourceCache,都设为永不过期。在applicationContext-acegi-security.xml中相应的调用是

				
				
				<bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="cacheName" value=" userCache"/>
    </bean>
    <bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache" autowire="byName">
        <property name="cache" ref="userCacheBackend"/>
    </bean>
    <bean id="resourceCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="cacheName" value=" resourceCache"/>
    </bean>
    <bean id="resourceCache" class="org.springside.modules.security.service.acegi.cache.ResourceCache" autowire="byName">
        <property name="cache" ref="resourceCacheBackend"/>
    </bean>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean"/>

"cacheName" 就是设定在ehcache.xml 中相应Cache的名称。

userCache使用的是Acegi 的EhCacheBasedUserCache(实现了UserCache接口), resourceCache是SpringSide的扩展类

				public interface UserCache   {
				
						
    public UserDetails getUserFromCache (String username);

    public void putUserInCache (UserDetails user);

    public void removeUserFromCache (String username);
}
				public class ResourceCache   {
    public ResourceDetails getAuthorityFromCache (String resString) {...    }
    public void putAuthorityInCache (ResourceDetails resourceDetails) {...  }
 
   public void removeAuthorityFromCache (String resString) {... }
    public List getUrlResStrings() {... }
    public List getFunctions() {.. }
}

UserCache 就是通过EhCache对UserDetails 进行缓存管理, 而ResourceCache 是对ResourceDetails 类进行缓存管理

				public interface UserDetails   extends Serializable {
    public boolean isAccountNonExpired();
    public boolean isAccountNonLocked();

    public GrantedAuthority[] getAuthorities();

    public boolean isCredentialsNonExpired();

    public boolean isEnabled();

    public String getPassword();

    public String getUsername();
}
				public interface ResourceDetails   extends Serializable {
				
						
    public String getResString();

    public String getResType();

    public GrantedAuthority[] getAuthorities();
}

UserDetails 包含用户信息和相应的权限,ResourceDetails 包含资源信息和相应的权限。

				public interface GrantedAuthority     {
    public String getAuthority ();
}

     GrantedAuthority 就是权限信息,在Acegi 的 sample 里GrantedAuthority 的信息如ROLE_USER, ROLE_SUPERVISOR, ACL_CONTACT_DELETE, ACL_CONTACT_ADMIN等等,网上也有很多例子把角色作为GrantedAuthority ,但事实上看看ACL 就知道, Acegi本身根本就没有角色这个概念,GrantedAuthority 包含的信息应该是权限,对于非ACL的权限用 AUTH_ 开头更为合理, 如SpringSide里的 AUTH_ADMIN_LOGIN, AUTH_BOOK_MANAGE 等等。

3.2.2 管理缓存

     使用AcegiCacheManager对userCache和resourceCache进行统一缓存管理。当在后台对用户信息进行修改或赋权的时候, 在更新数据库同时就会调用acegiCacheManager相应方法, 从数据库中读取数据并替换cache中相应部分,使cache与数据库同步。

				public class AcegiCacheManager extends BaseService {
    private ResourceCache resourceCache ;
    private UserCache userCache ;
    /**
     * 修改User时更改userCache
     */

    public void modifyUserInCache (User user, String orgUsername) {...    }
    /**
     * 修改Resource时更改resourceCache
     */

    public void modifyResourceInCache (Resource resource, String orgResourcename) {...    }
    /**
     * 修改权限时同时修改userCache和resourceCache
     */

    public void modifyPermiInCache (Permission permi, String orgPerminame) {...  }
    /**
     * User授予角色时更改userCache
     */
    public void authRoleInCache (User user) {...    }
    /**
     * Role授予权限时更改userCache和resourceCache
     */

    public void authPermissionInCache (Role role) {...  }
    /**
     * Permissioni授予资源时更改resourceCache
     */

    public void authResourceInCache (Permission permi) {...  }
    /**
     * 初始化userCache
     */

    public void initUserCache () {...  }
    /**
     * 初始化resourceCache
     */

    public void initResourceCache () {... }
    /**
     * 获取所有的url资源
     */

    public List getUrlResStrings () {...  }
    /**
     * 获取所有的Funtion资源
     */

    public List getFunctions () {...  }
    /**
     * 根据资源串获取资源
     */

    public ResourceDetails getAuthorityFromCache (String resString) {...  }
  
 ......


}

 

3.3 资源权限定义扩展

     Acegi给出的sample里,资源权限对照关系是配置在xml中的,试想一下如果你的企业安全应用有500个用户,100个角色权限的时候,维护这个xml将是个繁重无比的工作,如何动态更改用户权限更是个头痛的问题。

   <bean id="contactManagerSecurity" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
      <property name="authenticationManager"><ref bean="authenticationManager"/></property>
      <property name="accessDecisionManager"><ref local="businessAccessDecisionManager"/></property>
      <property name="afterInvocationManager"><ref local="afterInvocationManager"/></property>
      <property name="objectDefinitionSource">
         <value>
            sample.contact.ContactManager.create=ROLE_USER
            sample.contact.ContactManager.getAllRecipients=ROLE_USER
            sample.contact.ContactManager.getAll=ROLE_USER,AFTER_ACL_COLLECTION_READ
            sample.contact.ContactManager.getById=ROLE_USER,AFTER_ACL_READ
            sample.contact.ContactManager.delete=ACL_CONTACT_DELETE
            sample.contact.ContactManager.deletePermission=ACL_CONTACT_ADMIN
            sample.contact.ContactManager.addPermission=ACL_CONTACT_ADMIN
         </value>
      </property>
   </bean>
  <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
      <property name="authenticationManager"><ref bean="authenticationManager"/></property>
      <property name="accessDecisionManager"><ref local="httpRequestAccessDecisionManager"/></property>
      <property name="objectDefinitionSource">
         <value>
       CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
       PATTERN_TYPE_APACHE_ANT
       /index.jsp=ROLE_ANONYMOUS,ROLE_USER
       /hello.htm=ROLE_ANONYMOUS,ROLE_USER
       /logoff.jsp=ROLE_ANONYMOUS,ROLE_USER
       /switchuser.jsp=ROLE_SUPERVISOR
       /j_acegi_switch_user=ROLE_SUPERVISOR
       /acegilogin.jsp*=ROLE_ANONYMOUS,ROLE_USER
     /**=ROLE_USER
         </value>
      </property>
   </bean>

 对如此不Pragmatic的做法,SpringSide进行了扩展, 让Acegi 能动态读取数据库中的权限资源关系。

3.3.1 Aop Invocation Authorization

    <bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
        <property name="objectDefinitionSource" ref="methodDefinitionSource"/>
    </bean>
    <bean id="methodDefinitionSource" class="org.springside.security.service.acegi.DBMethodDefinitionSource">
        <property name="acegiCacheManager" ref="acegiCacheManager"/>
    </bean>

     研究下Aceig的源码,ObjectDefinitionSource的实际作用是返回一个ConfigAttributeDefinition对象,而Acegi Sample 的方式是用MethodDefinitionSourceEditor把xml中的文本Function资源权限对应关系信息加载到MethodDefinitionMap ( MethodDefinitionSource 的实现类 )中, 再组成ConfigAttributeDefinition,而我们的扩展目标是从缓存中读取信息来组成ConfigAttributeDefinition。

     MethodSecurityInterceptor是通过调用AbstractMethodDefinitionSource的lookupAttributes(method)方法获取ConfigAttributeDefinition。所以我们需要实现自己的ObjectDefinitionSource,继承AbstractMethodDefinitionSource并实现其lookupAttributes方法,从缓存中读取资源权限对应关系组成并返回ConfigAttributeDefinition即可。SpringSide中的DBMethodDefinitionSource类的部分实现如下 :

public class DBMethodDefinitionSource extendsAbstractMethodDefinitionSource {
......
    protected ConfigAttributeDefinitionlookupAttributes(Method mi) {
        Assert.notNull(mi, "lookupAttrubutes in the DBMethodDefinitionSource is null");
        String methodString = mi.getDeclaringClass().getName() + "." + mi.getName();
        if (!acegiCacheManager.isCacheInitialized()) {
            //初始化Cache
            acegiCacheManager.initResourceCache();
        }
        //获取所有的function
        List methodStrings = acegiCacheManager.getFunctions();
        Set auths = new HashSet();
        //取权限的合集
        for (Iterator iter = methodStrings.iterator(); iter.hasNext();) {
            String mappedName = (String) iter.next();
            if (methodString.equals(mappedName)
                    || isMatch(methodString, mappedName)) {
                ResourceDetails resourceDetails = acegiCacheManager.getAuthorityFromCache(mappedName);
                if (resourceDetails == null) {
                    break;
                }
                GrantedAuthority[] authorities = resourceDetails.getAuthorities();
                if (authorities == null || authorities.length == 0) {
                    break;
                }
                auths.addAll(Arrays.asList(authorities));
            }
        }
        if (auths.size() == 0)
            return null;
        ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor();
        String authoritiesStr = " ";
        for (Iterator iter = auths.iterator(); iter.hasNext();) {
            GrantedAuthority authority = (GrantedAuthority) iter.next();
            authoritiesStr += authority.getAuthority() + ",";
        }
        String authStr = authoritiesStr.substring(0, authoritiesStr.length() - 1);
        configAttrEditor.setAsText(authStr);
       //组装并返回ConfigAttributeDefinition
        return (ConfigAttributeDefinition) configAttrEditor.getValue();
    }
    ......
}

要注意几点的是:
1) 初始化Cache是比较浪费资源的,所以SpringSide中除第一次访问外的Cache的更新是针对性更新。

2) 因为method采用了匹配方式(详见 isMatch() 方法) , 即对于*Book和save*这两个资源来说,只要当前访问方法是Book结尾或以save开头都算匹配得上,所以应该取这些能匹配上的资源的相对应的权限的合集。

3) 使用ConfigAttributeEditor 能更方便地组装ConfigAttributeDefinition。

3.3.2 Filter Invocation Authorization

    <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
        <property name="objectDefinitionSource" ref="filterDefinitionSource"/>
    </bean>

    <bean id="filterDefinitionSource" class="org.springside.security.service.acegi.DBFilterInvocationDefinitionSource">
        <property name="convertUrlToLowercaseBeforeComparison" value="true"/>
        <property name="useAntPath" value="true"/>
        <property name="acegiCacheManager" ref="acegiCacheManager"/>
    </bean>

     PathBasedFilterInvocationDefinitionMap和RegExpBasedFilterInvocationDefinitionMap都是 FilterInvocationDefinitionSource的实现类,当PATTERN_TYPE_APACHE_ANT字符串匹配上时时,FilterInvocationDefinitionSourceEditor 选用PathBasedFilterInvocationDefinitionMap 把xml中的文本URL资源权限对应关系信息加载。

     FilterSecurityInterceptor通过FilterInvocationDefinitionSource的lookupAttributes(url)方法获取ConfigAttributeDefinition。 所以,我们可以通过继承FilterInvocationDefinitionSource的抽象类AbstractFilterInvocationDefinitionSource,并实现其lookupAttributes方法,从缓存中读取URL资源权限对应关系即可。SpringSide的DBFilterInvocationDefinitionSource类部分实现如下:

public class DBFilterInvocationDefinitionSource extends AbstractFilterInvocationDefinitionSource {

......
    public ConfigAttributeDefinition lookupAttributes(String url) {
        if (!acegiCacheManager.isCacheInitialized()) {
            acegiCacheManager.initResourceCache();
        }

        if (isUseAntPath()) {
            // Strip anything after a question mark symbol, as per SEC-161.
            int firstQuestionMarkIndex = url.lastIndexOf("?");
            if (firstQuestionMarkIndex != -1) {
                url = url.substring(0, firstQuestionMarkIndex);
            }
        }
        List urls = acegiCacheManager.getUrlResStrings();
        //URL资源倒叙排序
        Collections.sort(urls);
        Collections.reverse(urls);
//是否先全部转为小写再比较
        if (convertUrlToLowercaseBeforeComparison) {
            url = url.toLowerCase();
        }
        GrantedAuthority[] authorities = new GrantedAuthority[0];
        for (Iterator iterator = urls.iterator(); iterator.hasNext();) {
            String resString = (String) iterator.next();
            boolean matched = false;
//可选择使用AntPath和Perl5两种不同匹配模式
            if (isUseAntPath()) {
                matched = pathMatcher.match(resString, url);
            } else {
                Pattern compiledPattern;
                Perl5Compiler compiler = new Perl5Compiler();
                try {
                    compiledPattern = compiler.compile(resString,
                            Perl5Compiler.READ_ONLY_MASK);
                } catch (MalformedPatternException mpe) {
                    throw new IllegalArgumentException(
                            "Malformed regular expression: " + resString);
                }
                matched = matcher.matches(url, compiledPattern);
            }
            if (matched) {
                ResourceDetails rd = acegiCacheManager.getAuthorityFromCache(resString);
                authorities = rd.getAuthorities();
                break;
            }
        }
        if (authorities.length > 0) {
            String authoritiesStr = " ";
            for (int i = 0; i < authorities.length; i++) {
                authoritiesStr += authorities[i].getAuthority() + ",";
            }
            String authStr = authoritiesStr.substring(0, authoritiesStr
                    .length() - 1);
            ConfigAttributeEditor configAttrEditor = new ConfigAttributeEditor();
            configAttrEditor.setAsText(authStr);
            return (ConfigAttributeDefinition) configAttrEditor.getValue();
        }
        return null;
    }

......
 }

继承AbstractFilterInvocationDefinitionSource注意几点:
1)  需要先把获取回来的URL资源按倒序派序,以达到 a/b/c/d.* 在 a/.* 之前的效果(详见 Acegi sample 的applicationContext-acegi-security.xml 中的filterInvocationInterceptor的注释),为的是更具体的URL可以先匹配上,而获取具体URL的权限,如a/b/c/d.*权限AUTH_a, AUTH_b 才可查看,  a/.* 需要权限AUTH_a 才可查看,则如果当前用户只拥有权限AUTH_b,则他只可以查看a/b/c/d.jsp 而不能察看a/d.jsp。

2) 基于上面的原因,故第一次匹配上的就是当前所需权限,而不是取权限的合集。

3) 可以选用AntPath 或 Perl5 的资源匹配方式,感觉AntPath匹配方式基本足够。

4) Filter 权限控制比较适合于较粗颗粒度的权限,如设定某个模块下的页面是否能访问等,对于具体某个操作如增删修改,是否能执行,用Method  Invocation 会更佳些,所以注意两个方面一起控制效果更好

 

3.4 授权操作

     RBAC模型中有不少多对多的关系,这些关系都能以一个中间表的形式来存放,而Hibernate中可以不建这中间表对应的hbm.xml , 以资源与权限的配置为例,如下:

<hibernate-mapping package="org.springside.modules.security.domain">
    <class name="Permission" table="PERMISSIONS" dynamic-insert="true" dynamic-update="true">
        <cache usage="nonstrict-read-write"/>
        <id name="id" column="ID">
            <generator class="native"/>
        </id>
        <property name="name" column="NAME" not-null="true"/>
        <property name="descn" column="DESCN"/>
        <property name="operation" column="OPERATION"/>
        <property name="status" column="STATUS"/>
        <set name="roles" table="ROLE_PERMIS" lazy="true" inverse="true" cascade="save-update" batch-size="5">
            <key>
                <column name="PERMIS_ID" not-null="true"/>
            </key>
            <many-to-many class="Role" column="ROLE_ID" outer-join="auto"/>
        </set>
        <set name="resources" table="PERMIS_RESC" lazy="true" inverse="false" cascade="save-update" batch-size="5">
            <key>
                <column name="PERMIS_ID" not-null="true"/>
            </key>
            <many-to-many class="Resource" column="RESC_ID"/>
        </set>
    </class>
</hibernate-mapping>
<hibernate-mapping package="org.springside.modules.security.domain">
    <class name="Resource" table="RESOURCES" dynamic-insert="true" dynamic-update="true">
        <cache usage="nonstrict-read-write"/>
        <id name="id" column="ID">
            <generator class="native"/>
        </id>
        <property name="name" column="NAME" not-null="true"/>
        <property name="resType" column="RES_TYPE" not-null="true"/>
        <property name="resString" column="RES_STRING" not-null="true"/>
        <property name="descn" column="DESCN"/>
        <set name="permissions" table="PERMIS_RESC" lazy="true" inverse="true" cascade="save-update" batch-size="5">
            <key>
                <column name="RESC_ID" not-null="true"/>
            </key>
            <many-to-many class="Permission" column="PERMIS_ID" outer-join="auto"/>
        </set>
    </class>
</hibernate-mapping>

配置时注意几点:

1) 因为是分配某个权限的资源,所以权限是主控方,把inverse设为false,资源是被控方inverse设为true

2) cascade是"save-update",千万别配成delete

3) 只需要 permission.getResources().add(resource), permission.getResources()..remove(resource) 即可很方便地完成授权和取消授权操作

posted @ 2007-09-12 14:44 华梦行 阅读(223) | 评论 (0)编辑 收藏

二 Acegi安全系统的配置 

      Acegi 的配置看起来非常复杂,但事实上在实际项目的安全应用中我们并不需要那么多功能,清楚的了解Acegi配置中各项的功能,有助于我们灵活的运用Acegi于实践中。

2.1 在Web.xml中的配置

1)  FilterToBeanProxy
  Acegi通过实现了Filter接口的FilterToBeanProxy提供一种特殊的使用Servlet Filter的方式,它委托Spring中的Bean -- FilterChainProxy来完成过滤功能,这好处是简化了web.xml的配置,并且充分利用了Spring IOC的优势。FilterChainProxy包含了处理认证过程的filter列表,每个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>

2) filter-mapping
  <filter-mapping>限定了FilterToBeanProxy的URL匹配模式,只有*.do和*.jsp和/j_acegi_security_check 的请求才会受到权限控制,对javascript,css等不限制。

   <filter-mapping>
      <filter-name>Acegi Filter Chain Proxy</filter-name>
      <url-pattern>*.do</url-pattern>
    </filter-mapping>
   
    <filter-mapping>
      <filter-name>Acegi Filter Chain Proxy</filter-name>
      <url-pattern>*.jsp</url-pattern>
    </filter-mapping>
   
    <filter-mapping>
      <filter-name>Acegi Filter Chain Proxy</filter-name>
      <url-pattern>/j_acegi_security_check</url-pattern>
</filter-mapping>

3) HttpSessionEventPublisher
  <listener>的HttpSessionEventPublisher用于发布HttpSessionApplicationEvents和HttpSessionDestroyedEvent事件给spring的applicationcontext。

    <listener>
        <listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
    </listener>


2.2 在applicationContext-acegi-security.xml中

2.2.1 FILTER CHAIN

  FilterChainProxy会按顺序来调用这些filter,使这些filter能享用Spring ioc的功能, CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON定义了url比较前先转为小写, PATTERN_TYPE_APACHE_ANT定义了使用Apache ant的匹配模式

    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
               /**=httpSessionContextIntegrationFilter,authenticationProcessingFilter,
basicProcessingFilter,rememberMeProcessingFilter,anonymousProcessingFilter,
exceptionTranslationFilter,filterInvocationInterceptor
            </value>
        </property>
    </bean>

2.2.2 基础认证

1) authenticationManager
  起到认证管理的作用,它将验证的功能委托给多个Provider,并通过遍历Providers, 以保证获取不同来源的身份认证,若某个Provider能成功确认当前用户的身份,authenticate()方法会返回一个完整的包含用户授权信息的Authentication对象,否则会抛出一个AuthenticationException。
Acegi提供了不同的AuthenticationProvider的实现,如:
        DaoAuthenticationProvider 从数据库中读取用户信息验证身份
        AnonymousAuthenticationProvider 匿名用户身份认证
        RememberMeAuthenticationProvider 已存cookie中的用户信息身份认证
        AuthByAdapterProvider 使用容器的适配器验证身份
        CasAuthenticationProvider 根据Yale中心认证服务验证身份, 用于实现单点登陆
        JaasAuthenticationProvider 从JASS登陆配置中获取用户信息验证身份
        RemoteAuthenticationProvider 根据远程服务验证用户身份
        RunAsImplAuthenticationProvider 对身份已被管理器替换的用户进行验证
        X509AuthenticationProvider 从X509认证中获取用户信息验证身份
        TestingAuthenticationProvider 单元测试时使用

        每个认证者会对自己指定的证明信息进行认证,如DaoAuthenticationProvider仅对UsernamePasswordAuthenticationToken这个证明信息进行认证。

<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref local="daoAuthenticationProvider"/>
                <ref local="anonymousAuthenticationProvider"/>
                <ref local="rememberMeAuthenticationProvider"/>
            </list>
        </property>
</bean>


2) daoAuthenticationProvider
  进行简单的基于数据库的身份验证。DaoAuthenticationProvider获取数据库中的账号密码并进行匹配,若成功则在通过用户身份的同时返回一个包含授权信息的Authentication对象,否则身份验证失败,抛出一个AuthenticatiionException。

    <bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
        <property name="userDetailsService" ref="jdbcDaoImpl"/>
        <property name="userCache" ref="userCache"/>
        <property name="passwordEncoder" ref="passwordEncoder"/>
   </bean>


3) passwordEncoder
  使用加密器对用户输入的明文进行加密。Acegi提供了三种加密器:
PlaintextPasswordEncoder—默认,不加密,返回明文.
ShaPasswordEncoder—哈希算法(SHA)加密
Md5PasswordEncoder—消息摘要(MD5)加密

<bean id="passwordEncoder" class="org.acegisecurity.providers.encoding.Md5PasswordEncoder"/>


4) jdbcDaoImpl
  用于在数据中获取用户信息。 acegi提供了用户及授权的表结构,但是您也可以自己来实现。通过usersByUsernameQuery这个SQL得到你的(用户ID,密码,状态信息);通过authoritiesByUsernameQuery这个SQL得到你的(用户ID,授权信息)

 
<bean id="jdbcDaoImpl" 
class="org.acegisecurity.userdetails.jdbc.JdbcDaoImpl">
        <property name="dataSource" ref="dataSource"/>
        <property name="usersByUsernameQuery">
            <value>select loginid,passwd,1 from users where loginid = ?</value>
        </property>
        <property name="authoritiesByUsernameQuery">
            <value>select u.loginid,p.name from users u,roles r,permissions p,user_role ur,role_permis rp where u.id=ur.user_id and r.id=ur.role_id and p.id=rp.permis_id and
                r.id=rp.role_id and p.status='1' and u.loginid=?</value>
        </property>
</bean>

5) userCache &  resourceCache
  缓存用户和资源相对应的权限信息。每当请求一个受保护资源时,daoAuthenticationProvider就会被调用以获取用户授权信息。如果每次都从数据库获取的话,那代价很高,对于不常改变的用户和资源信息来说,最好是把相关授权信息缓存起来。(详见 2.6.3 资源权限定义扩展 )
userCache提供了两种实现: NullUserCache和EhCacheBasedUserCache, NullUserCache实际上就是不进行任何缓存,EhCacheBasedUserCache是使用Ehcache来实现缓功能。

    <bean id="userCacheBackend" 
class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="cacheName" value="userCache"/>
    </bean>
    <bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache" autowire="byName">
        <property name="cache" ref="userCacheBackend"/>
    </bean>
    <bean id="resourceCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="cacheName" value="resourceCache"/>
    </bean>
    <bean id="resourceCache" class="org.springside.modules.security.service.acegi.cache.ResourceCache" autowire="byName">
        <property name="cache" ref="resourceCacheBackend"/>
    </bean>


6) basicProcessingFilter
  用于处理HTTP头的认证信息,如从Spring远程协议(如Hessian和Burlap)或普通的浏览器如IE,Navigator的HTTP头中获取用户信息,将他们转交给通过authenticationManager属性装配的认证管理器。如果认证成功,会将一个Authentication对象放到会话中,否则,如果认证失败,会将控制转交给认证入口点(通过authenticationEntryPoint属性装配)

    <bean id="basicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationEntryPoint" ref="basicProcessingFilterEntryPoint"/>
    </bean>

7) basicProcessingFilterEntryPoint
  通过向浏览器发送一个HTTP401(未授权)消息,提示用户登录。
处理基于HTTP的授权过程, 在当验证过程出现异常后的"去向",通常实现转向、在response里加入error信息等功能。

 <bean 
id="basicProcessingFilterEntryPoint" 
class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
        <property name="realmName" value="SpringSide Realm"/>
</bean>

8) authenticationProcessingFilterEntryPoint
  当抛出AccessDeniedException时,将用户重定向到登录界面。属性loginFormUrl配置了一个登录表单的URL,当需要用户登录时,authenticationProcessingFilterEntryPoint会将用户重定向到该URL

 
<bean id="authenticationProcessingFilterEntryPoint" 
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
        <property name="loginFormUrl">
            <value>/security/login.jsp</value>
        </property>
        <property name="forceHttps" value="false"/>
</bean>

2.2.3 HTTP安全请求

1) httpSessionContextIntegrationFilter
  每次request前 HttpSessionContextIntegrationFilter从Session中获取Authentication对象,在request完后, 又把Authentication对象保存到Session中供下次request使用,此filter必须其他Acegi filter前使用,使之能跨越多个请求。

<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"></bean>
    <bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
        <property name="allowIfAllAbstainDecisions" value="false"/>
        <property name="decisionVoters">
            <list>
                <ref bean="roleVoter"/>
            </list>
        </property>
</bean>


2) httpRequestAccessDecisionManager
  经过投票机制来决定是否可以访问某一资源(URL或方法)。allowIfAllAbstainDecisions为false时如果有一个或以上的decisionVoters投票通过,则授权通过。可选的决策机制有ConsensusBased和UnanimousBased

    <bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
        <property name="allowIfAllAbstainDecisions" value="false"/>
        <property name="decisionVoters">
            <list>
                <ref bean="roleVoter"/>
            </list>
        </property>
    </bean>


3) roleVoter
   必须是以rolePrefix设定的value开头的权限才能进行投票,如AUTH_ , ROLE_

    <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter">
        <property name="rolePrefix" value="AUTH_"/>
   </bean>

4)exceptionTranslationFilter
  异常转换过滤器,主要是处理AccessDeniedException和AuthenticationException,将给每个异常找到合适的"去向" 

   <bean id="exceptionTranslationFilter" class="org.acegisecurity.ui.ExceptionTranslationFilter">
        <property name="authenticationEntryPoint" ref="authenticationProcessingFilterEntryPoint"/>
    </bean>

5) authenticationProcessingFilter
  和servlet spec差不多,处理登陆请求.当身份验证成功时,AuthenticationProcessingFilter会在会话中放置一个Authentication对象,并且重定向到登录成功页面
         authenticationFailureUrl定义登陆失败时转向的页面
         defaultTargetUrl定义登陆成功时转向的页面
         filterProcessesUrl定义登陆请求的页面
         rememberMeServices用于在验证成功后添加cookie信息

    <bean id="authenticationProcessingFilter" class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationFailureUrl">
            <value>/security/login.jsp?login_error=1</value>
        </property>
        <property name="defaultTargetUrl">
            <value>/admin/index.jsp</value>
        </property>
        <property name="filterProcessesUrl">
            <value>/j_acegi_security_check</value>
        </property>
        <property name="rememberMeServices" ref="rememberMeServices"/>
    </bean>

6) filterInvocationInterceptor
  在执行转向url前检查objectDefinitionSource中设定的用户权限信息。首先,objectDefinitionSource中定义了访问URL需要的属性信息(这里的属性信息仅仅是标志,告诉accessDecisionManager要用哪些voter来投票)。然后,authenticationManager掉用自己的provider来对用户的认证信息进行校验。最后,有投票者根据用户持有认证和访问url需要的属性,调用自己的voter来投票,决定是否允许访问。

    <bean id="filterInvocationInterceptor" class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
        <property name="objectDefinitionSource" ref="filterDefinitionSource"/>
    </bean>


7) filterDefinitionSource (详见 2.6.3 资源权限定义扩展)
  自定义DBFilterInvocationDefinitionSource从数据库和cache中读取保护资源及其需要的访问权限信息 

<bean id="filterDefinitionSource" class="org.springside.modules.security.service.acegi.DBFilterInvocationDefinitionSource">
        <property name="convertUrlToLowercaseBeforeComparison" value="true"/>
        <property name="useAntPath" value="true"/>
        <property name="acegiCacheManager" ref="acegiCacheManager"/>
</bean>

2.2.4 方法调用安全控制

(详见 2.6.3 资源权限定义扩展)

1) methodSecurityInterceptor
  在执行方法前进行拦截,检查用户权限信息
2) methodDefinitionSource
  自定义MethodDefinitionSource从cache中读取权限

   <bean id="methodSecurityInterceptor" class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
        <property name="objectDefinitionSource" ref="methodDefinitionSource"/>
    </bean>
    <bean id="methodDefinitionSource" class="org.springside.modules.security.service.acegi.DBMethodDefinitionSource">
        <property name="acegiCacheManager" ref="acegiCacheManager"/>
    </bean>

2.3 Jcaptcha验证码

采用 http://jcaptcha.sourceforge.net 作为通用的验证码方案,请参考SpringSide中的例子,或网上的:
http://www.coachthrasher.com/page/blog?entry=jcaptcha_with_appfuse

差沙在此过程中又发现acegi logout filter的错误,进行了修正。

另外它默认提供的图片比较难认,我们custom了一个美观一点的版本。

posted @ 2007-09-12 14:43 华梦行 阅读(123) | 评论 (0)编辑 收藏

一 Acegi安全系统介绍 

    Author: cac 差沙

    Acegi是Spring Framework 下最成熟的安全系统,它提供了强大灵活的企业级安全服务,如完善的认证和授权机制,Http资源访问控制,Method 调用访问控制,Access Control List (ACL) 基于对象实例的访问控制,Yale Central Authentication Service (CAS) 耶鲁单点登陆,X509 认证,当前所有流行容器的认证适配器,Channel Security频道安全管理等功能。

1.1 网站资源

官方网站      http://acegisecurity.sourceforge.net
论坛            http://forum.springframework.org/forumdisplay.php?f=33
Jira              http://opensource.atlassian.com/projects/spring/browse/SEC

1.2 多方面的安全控制粒度

  1. URL 资源访问控制
     http://apps:8080/index.htm -> for public
     http://apps:8080/user.htm -> for authorized user
  2. 方法调用访问控制
    public void getData() -> all user
    public void modifyData() -> supervisor only
  3. 对象实例保护
    order.getValue() < $100 -> all user
    order.getValue() > $100 -> supervisor only

1.3 非入侵式安全架构

  1. 基于Servlet Filter和Spring aop,  使商业逻辑和安全逻辑分开,结构更清晰
  2. 使用Spring 来代理对象,能方便地保护方法调用

1.4 其它安全架构

    Acegi只是安全框架之一,其实还存在其它优秀的安全框架可供选择:

posted @ 2007-09-12 14:42 华梦行 阅读(178) | 评论 (0)编辑 收藏
http://wiki.springside.org.cn/display/springside/Acegi+Reference


http://www.springside.org.cn/docs/reference/Acegi2.htm

http://www.springside.org.cn/docs/reference/Acegi3.htm


http://www.springside.org.cn/docs/reference/Acegi4.htm
http://www.springside.org.cn/docs/reference/Acegi5.htm
http://www.springside.org.cn/docs/reference/Acegi6.htm
posted @ 2007-09-12 14:39 华梦行 阅读(148) | 评论 (0)编辑 收藏

package org.springframework.samples;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.generic.GenericBeanFactoryAccessor;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.ColumnMapRowMapper;
import org.springframework.jdbc.core.ConnectionCallback;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.ResultSetExtractor;
import org.springframework.jdbc.core.RowCallbackHandler;
import org.springframework.jdbc.core.RowCountCallbackHandler;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.StatementCallback;
import org.springframework.jdbc.support.JdbcUtils;
import org.springframework.samples.petclinic.Person;

/**
 *
 * @author worldheart
 *
 */
public class MainTestForJdbcTemplate {

 private static final Log log = LogFactory.getLog(MainTestForJdbcTemplate.class);
 
 public static void main(String[] args) {  
  ListableBeanFactory cbf = new ClassPathXmlApplicationContext("ac1.xml");  
  GenericBeanFactoryAccessor gbfa = new GenericBeanFactoryAccessor(cbf);
  
  JdbcTemplate jt = gbfa.getBean("jdbcTemplate");
  
  jt.execute(new ConnectionCallback(){
   public Object doInConnection(Connection con) throws SQLException, DataAccessException {
    System.out.println(con.getMetaData().getDriverName());
    return null;
   }
  });
  
  List nameList = (List)jt.execute(new StatementCallback(){
   public Object doInStatement(Statement stmt) throws SQLException, DataAccessException {
    System.out.println(stmt.getConnection().getMetaData().getDatabaseProductVersion());
    List<String> nameList = new ArrayList<String>();
    ResultSet rs = null;
    try{
     rs = stmt.executeQuery("select name from types");
     while(rs.next()){
      nameList.add(rs.getString("name"));
     }
    }finally{
     JdbcUtils.closeResultSet(rs);
    }
    return nameList;
   }
  });
  System.out.println(nameList);
  
  List perList = (List)jt.query("select * from vets",
    new ResultSetExtractor(){
      public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
       List<Person> personList = new ArrayList<Person>();
       while(rs.next()){
        Person per = new Person();
        per.setId(rs.getInt("id"));
        per.setFirstName(rs.getString("first_name"));
        per.setLastName(rs.getString("last_name"));
        personList.add(per);
       }
       return personList;
    }
  });
  for(Iterator iterator = perList.iterator(); iterator.hasNext();){
   Person person = (Person)iterator.next();
   System.out.println(person.getId() + "," + person.getFirstName() + "," + person.getLastName());
  }

  final List<Person> pSonList = new ArrayList<Person>();
  jt.query("select * from vets", new RowCallbackHandler(){
   public void processRow(ResultSet rs) throws SQLException {
    Person per = new Person();
    per.setId(rs.getInt("id"));
    per.setFirstName(rs.getString("first_name"));
    per.setLastName(rs.getString("last_name"));
    pSonList.add(per);
   }
  });
  for(Person pSon: pSonList){
   System.out.println(pSon.getId() + "," + pSon.getFirstName() + "," + pSon.getLastName());
  }
  
  RowCountCallbackHandler rcch = new RowCountCallbackHandler();
  jt.query("select * from vets", rcch);
  for(String colName: rcch.getColumnNames())
   System.out.println(colName);
  for(int colType: rcch.getColumnTypes())
   System.out.println(colType);
  System.out.println(rcch.getColumnCount());
  System.out.println(rcch.getRowCount());
  
  List vetsList = (List)jt.query("select * from vets",
    new RowMapper(){
     public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
      Person pers = new Person();
      pers.setId(rs.getInt("id"));
      pers.setFirstName(rs.getString("first_name"));
      pers.setLastName(rs.getString("last_name"));
      return pers;
     }
  });
  System.out.println(vetsList);
  
  ColumnMapRowMapper cmrw = new ColumnMapRowMapper();
  List vList = (List)jt.query("select * from vets", cmrw);
  System.out.println(vList);
  
  System.out.println(jt.queryForInt("select count(*) from vets where id = ?",
    new Object[]{3}));
      
  jt.execute("update owners set address = 'GuangZhou' where telephone = ?",
    new PreparedStatementCallback(){
     public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
      ps.setString(1, "16068008");
      ps.addBatch();
      ps.setString(1, "6085555487");
      ps.addBatch();
      return ps.executeBatch();
     }
  });
  
  System.out.println(jt.query("select address from owners where first_name = ? and last_name = ?",
    new PreparedStatementSetter(){
     public void setValues(PreparedStatement ps) throws SQLException {
      ps.setString(1, "Jeff");
      ps.setString(2, "Black");
     }
    },
    new RowMapper(){
     public Object mapRow(ResultSet rs, int rowNum) throws SQLException {      
      return rs.getString("address");
     }
    }));
  
  System.out.println(jt.execute(
    new PreparedStatementCreator(){
     public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
      return con.prepareStatement("select address from owners");
     }
    },
    new PreparedStatementCallback(){
     public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
      List<String> list = new ArrayList<String>();
      ResultSet rs = null;
      try{
       rs = ps.executeQuery();
       while(rs.next()){
        list.add(rs.getString("address"));
       }
      }finally{
       JdbcUtils.closeResultSet(rs);
      }
      return list;
     }
    }));
  
 }

}

posted @ 2007-09-11 11:22 华梦行 阅读(285) | 评论 (0)编辑 收藏
public class BindStatus
extends Object
		

Simple adapter to expose the bind status of a field or object. Set as a variable both by the JSP bind tag and Velocity/FreeMarker macros.

  简单的适配器获取域或者是对象绑定状态, 同样适用于模板。
Obviously, object status representations (i.e. errors at the object level rather than the field level) do not have an expression and a value but only error codes and messages. For simplicity's sake and to be able to use the same tags and macros, the same status class is used for both scenarios.
显然,对象的状态表示(例如错误是对象级的而不是域级别的)没有表达式或者值,而是只有错误码和消息

Method Summary
 int doEndTag ()
           
 void doFinally ()
           
protected  int doStartTagInternal ()
          Called by doStartTag to perform the actual work.
 PropertyEditor getEditor ()
          Retrieve the PropertyEditor for the property that this tag is currently bound to.
 Errors getErrors ()
          Retrieve the Errors instance that this tag is currently bound to.
 String getPath ()
          Return the path that this tag applies to.
 String getProperty ()
          Retrieve the property that this tag is currently bound to, or null if bound to an object rather than a specific property.
 boolean isIgnoreNestedPath ()
          Return whether to ignore a nested path, if any.
 void setIgnoreNestedPath (boolean ignoreNestedPath)
          Set whether to ignore a nested path, if any.
 void setPath (String path)
          Set the path that this tag should apply.
posted @ 2007-09-11 11:00 华梦行 阅读(237) | 评论 (0)编辑 收藏

1 TagSupport与BodyTagSupport的区别

 TagSupport与BodyTagSupport的区别主要是标签处理类是否需要与标签体交互,如果不需要交互的就用TagSupport,否则如果不需要交互就用BodyTagSupport。

     交互就是标签处理类是否要读取标签体的内容和改变标签体返回的内容。

    用TagSupport实现的标签,都可以用BodyTagSupport来实现,因为BodyTagSupport继承了TagSupport。

 2 doStartTag(),doEndTag()

   doStartTag()方法是遇到标签开始时会呼叫的方法,其合法的返回值是EVAL_BODY_INCLUDE与SKIP_BODY,前者表示将显示标签间的文字,后者表示不显示标签间的文字;doEndTag()方法是在遇到标签结束时呼叫的方法,其合法的返回值是EVAL_PAGE与 SKIP_PAGE,前者表示处理完标签后继续执行以下的JSP网页,后者是表示不处理接下来的JSP网页

    doAfterBody(),这个方法是在显示完标签间文字之后呼叫的,其返回值有EVAL_BODY_AGAIN与SKIP_BODY,前者会再显示一次标签间的文字,后者则继续执行标签处理的下一步。

   预定的处理顺序是:doStartTag()返回SKIP_BODY,doAfterBodyTag()返回SKIP_BODY,doEndTag()返回EVAL_PAGE.

  如果继承了TagSupport之后,如果没有改写任何的方法,标签处理的执行顺序是:

   doStartTag() ->不显示文字 ->doEndTag()->执行接下来的网页

  如果您改写了doStartTag(),则必须指定返回值,如果指定了EVAL_BODY_INCLUDE,则执行顺序是

   doStartTag()->显示文字->doAfterBodyTag()->doEndTag()->执行下面的网页

 

下面這個程式簡單的示範如何使用自訂標籤來對網頁內容作一些保護:

    * GuardTag.java

package onlyfun.caterpillar; 

 

import java.io.*; 

import javax.servlet.jsp.*; 

import javax.servlet.jsp.tagext.*; 

 

public class GuardTag extends TagSupport { 

    public int doStartTag() throws JspException { 

        String valid = 

            pageContext.getRequest().getParameter("valid"); 

 

        // 如果flag設定為key,就顯示本體文字內容 

        if(valid.equals("valid_user")) { 

            return EVAL_BODY_INCLUDE; 

        } 

 

        return SKIP_BODY; 

    } 




同樣的,程式編譯完之後,放置在WEB-INF/classes/之下,然後編譯.tld檔案,如下:

    * guardtag.tld

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

 

<taglib xmlns="http://java.sun.com/xml/ns/j2ee" 

    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 

    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

                        web-jsptaglibrary_2_0.xsd" 

    version="2.0"> 

    

    <description>Tag Demo</description> 

    <tlib-version>1.0</tlib-version> 

    <jsp-version>2.0</jsp-version> 

    <short-name>TagDemo</short-name> 

    <uri>/TagDemo</uri> 


    <tag> 

        <description>Cuard BodyText</description> 

        <name>guard</name> 

        <tag-class>onlyfun.caterpillar.GuardTag</tag-class> 

        <body-content>JSP</body-content> 

    </tag> 


</taglib> 


在<body-content>中設定的是JSP,這表示如果本體中包括JSP網頁的內容,將會被編譯執行,接下來您可以在web.xml中定義uri與.tld的名稱對應關係,方法與前一個主題相同,這邊就不重複說明了,然後撰寫一個測試的JSP網頁:

    * test.jsp

<%@taglib prefix="caterpillar" 

           uri="http://caterpillar.onlyfun.net/"%> 

<html> 

<body> 

    這個網頁包括受保護的內容OOOXXXX。。。。。。<p> 

    <caterpillar:guard> 

        ${ param.user }, 您好,幸運密碼是 oooxxx ! 

    </caterpillar:guard> 

</body> 

</html> 


為了要能看到幸運密碼,您必須在請求中包括guard參數,假設請求是:
http://localhost:8080/myjsp/test.jsp?valid=valid_user&user=Justin


這樣就可以看到幸運密碼了:
<html>

<body>

    這個網頁包括受包護的內容OOOXXXX。。。。。。<p>

   

        Justin, 您好,幸運密碼是: oooxxx !

   

</body>
</html>

posted @ 2007-09-11 10:09 华梦行 阅读(703) | 评论 (1)编辑 收藏

 public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {

    JspFactory _jspxFactory = null;
    PageContext pageContext = null;
    HttpSession session = null;
    ServletContext application = null;
    ServletConfig config = null;
    JspWriter out = null;
    Object page = this;
    JspWriter _jspx_out = null;
    PageContext _jspx_page_context = null;


    try {
      _jspxFactory = JspFactory.getDefaultFactory();
      response.setContentType("text/html;charset=UTF-8");
      pageContext = _jspxFactory.getPageContext(this, request, response,
         null, true, 8192, true);
      _jspx_page_context = pageContext;
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;

      out.write("\n");
      out.write("\n");
      out.write("\n");
      out.write("\n");
      out.write("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n");
      out.write("   \"http://www.w3.org/TR/html4/loose.dtd\">\n");
      out.write("\n");
      out.write("<html>\n");
      out.write("    <head>\n");
      out.write("        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n");
      out.write("        <title>JSP Page</title>\n");
      out.write("    </head>\n");
      out.write("    <body>\n");
      out.write("\n");
      out.write("    <h1>JSP Page</h1>\n");
      out.write("\n");
      out.write("    \n");
      out.write("    </body>\n");
      out.write("</html>\n");
    } catch (Throwable t) {
      if (!(t instanceof SkipPageException)){
        out = _jspx_out;
        if (out != null && out.getBufferSize() != 0)
          out.clearBuffer();
        if (_jspx_page_context != null) _jspx_page_context.handlePageException(t);
      }
    } finally {
      if (_jspxFactory != null) _jspxFactory.releasePageContext(_jspx_page_context);
    }
  }

posted @ 2007-09-10 17:00 华梦行 阅读(290) | 评论 (0)编辑 收藏
JSP的运行内幕

文章作者:
责任编辑: 录入时间:2004-11-1 18:26:13 来源:第七频道
频道声明:本频道的文章除部分特别声明禁止转载的专稿外,可以自由转载.但请务必注明出出处和原始作者 文章版权归本频道与文章作者所有.对于被频道转载文章的个人和网站,我们表示深深的谢意. 

经常有朋友问起,JSP和Servlet之间有什么区别,两者之间又有什么联系?其实Servlet技术的出现时间很早,是当时为了Java的服务器端应用而开发的。大家都知道Applet是应用小程序,Servlet就是服务器端小程序了。但在Microsoft公司的ASP技术出现后,使用Servlet进行响应输出时一行行的输出语句就显得非常笨拙,对于复杂布局或者显示页面更是如此。JSP就是为了满足这种需求在Servlet技术之上开发的。可见,JSP和Servlet之间有着内在的血缘关系,在学习JSP时,如果能够抓住这种联系,就能更深刻地理解JSP的运行机理,达到事半功倍的效果。

本文将通过对一个JSP运行过程的剖析,深入JSP运行的内幕,并从全新的视角阐述一些JSP中的技术要点。

HelloWorld.jsp

我们以Tomcat 4.1.17服务器为例,来看看最简单的HelloWorld.jsp是怎么运行的。

代码清单1:HelloWorld.jsp

HelloWorld.jsp
<%
 String message = "Hello World!";
%>
<%=message%>


  这个文件非常简单,仅仅定义了一个String的变量,并且输出。把这个文件放到Tomcat的webapps\ROOT\目录下,启动Tomcat,在浏览器中访问http://localhost:8080/HelloWorld.jsp,浏览器中的输出为“HelloWorld!”

  让我们来看看Tomcat都做了什么。转到Tomcat的\work\Standalone\localhost\_目录下,可以找到如下的HelloWorld_jsp.java,这个文件就是Tomcat解析HelloWorld.jsp时生成的源文件:

  代码清单2:HelloWorld_jsp.java

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;

public class HelloWorld_jsp extends HttpJspBase {
 ......
public void _jspService(HttpServletRequest request, 
HttpServletResponse response)throws java.io.IOException, ServletException
 {
  JspFactory _jspxFactory = null;
  javax.servlet.jsp.PageContext pageContext = null;
  HttpSession session = null;
  ServletContext application = null;
  ServletConfig config = null;
  JspWriter out = null;
  Object page = this;
  JspWriter _jspx_out = null;

  try {
   _jspxFactory = JspFactory.getDefaultFactory();
   response.setContentType("text/html;charset=ISO-8859-1");
   pageContext = _jspxFactory.getPageContext(this, request, 
response,null, true, 8192, true); application = pageContext.getServletContext(); config = pageContext.getServletConfig(); session = pageContext.getSession(); out = pageContext.getOut(); _jspx_out = out; String message = "Hello World!"; out.print(message); } catch (Throwable t) { out = _jspx_out; if (out != null && out.getBufferSize() != 0) out.clearBuffer(); if (pageContext != null) pageContext.handlePageException(t); } finally { if (_jspxFactory != null) _jspxFactory.releasePageContext(pageContext); } } }


  从上面可以看出,HelloWorld.jsp在运行时首先解析成一个Java类HelloWorld_jsp.java,该类继承于org.apache.jasper.runtime.HttpJspBase基类,HttpJspBase实现了HttpServlet接口。可见,JSP在运行前首先将编译为一个Servlet,这就是理解JSP技术的关键。

  我们还知道JSP页面中内置了几个对象,如pageContext、application、config、page、session、out等,你可能会奇怪,为什么在JSP中的代码片断中可以直接使用这些内置对象。观察_jspService()方法,实际上这几个内置对象就是在这里定义的。在对JSP文件中的代码片断进行解析之前,先对这几个内置对象进行初始化。

  首先,调用JspFactory的getDefaultFactory()方法获取容器实现(本文中指Tomcat 4.1.17)的一个JspFactory对象的引用。JspFactory是javax.servlet.jsp包中定义的一个抽象类,其中定义了两个静态方法set/getDefaultFactory()。set方法由JSP容器(Tomcat)实例化该页面Servlet(即HelloWorld_jsp类)的时候置入,所以可以直接调用JspFactory.getDefaultFactory()方法得到这个JSP工厂的实现类。Tomcat是调用org.apache.jasper.runtime.JspFactoryImpl类。

  然后,调用这个JspFactoryImpl的getPageContext()方法,填充一个PageContext返回,并赋给内置变量pageConext。其它内置对象都经由该pageContext得到。具体过程见上面的代码,这里不再赘述。该页面Servlet的环境设置完毕,开始对页面进行解析。HelloWorld.jsp页面仅仅定义了一个String变量,然后直接输出。解析后的代码如下:

  代码清单3:JSP页面解析后的代码片断

String message = "Hello World!";
out.print(message);


  定制标签的解析过程

  在一个中大型的Web应用中,通常使用JSP定制标签来封装页面显示逻辑。剖析容器对定制标签的解析过程,对我们深入理解定制标签的运行机理非常有帮助。下面我们以Struts1.1b中附带的struts-example应用的主页运行为例加以说明。

  包含定制标签的index.jsp

  Struts1.1b的下载地址是http://jakarta.apache.org/struts/index.html。将下载的包解压,在webapps目录下可以找到struts-example.war。将该War包拷贝到Tomcat的webapps目录下,Tomcat会自动安装此应用包。在浏览器中通过http://localhost:8080/struts-example访问struts-example应用,将显示应用的首页(见图1)。



  图一 应用的首页

  代码清单4:index.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="/WEB-INF/struts-bean.tld" prefix="bean" %>
<%@ taglib uri="/WEB-INF/struts-html.tld" prefix="html" %>
<%@ taglib uri="/WEB-INF/struts-logic.tld" prefix="logic" %>
<html:html locale="true">
<head>
<title><bean:message key="index.title"/></title>
<html:base/>
</head>
<body bgcolor="white">
…… 
</body>
</html:html>


  我们仅以index.jsp中的<bean:message/>标签的解析为例进行分析,看容器是怎样把这个自定义标签解析成HTML输出的。上面代码省略了页面的其它显示部分。首先,查看上面浏览器中页面的源文件:

<html lang="zh">
<head>
<title>MailReader Demonstration Application (Struts 1.0)</title>
</head>
<body bgcolor="white">
……
</body>
</html>


  可见,容器已经把<bean:message key="index.title"/>替换成一个字串,显示为页面的标题。

  解析过程

  那么,JSP容器是怎样完成解析的呢?查看在工作目录jakarta-tomcat-4.1.17\work\Standalone\localhost\struts-example下解析后的index_jsp.java文件:

  代码清单5:index_jsp.java

package org.apache.jsp;

import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.jsp.*;
import org.apache.jasper.runtime.*;
public class index_jsp extends HttpJspBase {
 //为所有的定制标签定义处理器池类的引用
 private org.apache.jasper.runtime.TagHandlerPool ;
 _jspx_tagPool_bean_message_key;
 ……
 //页面类构造方法
 public index_jsp() {
  _jspx_tagPool_bean_message_key =
  new org.apache.jasper.runtime.TagHandlerPool();
   ……
 }

 public void _jspService(HttpServletRequest request,
   HttpServletResponse response) 
   throws java.io.IOException, ServletException {
  ……
  _jspxFactory = JspFactory.getDefaultFactory();
  response.setContentType("text/html;charset=UTF-8");
  pageContext = _jspxFactory.getPageContext(this,
    request, response,null, true, 8192, true);
  application = pageContext.getServletContext();
  config = pageContext.getServletConfig();
  session = pageContext.getSession();
  out = pageContext.getOut();
  _jspx_out = out;
  ……
  if (_jspx_meth_html_html_0(pageContext))
  return;
  ……
 }
 //页面在处理退出时释放所有定制标签的属性
 public void _jspDestroy() {
  _jspx_tagPool_bean_message_key.release();
  ……
 }
}


  生成的index_jsp.java继承于org.apache. jasper.runtime.HttpJspBase。研究这个文件为我们了解定制标签的运行机理提供了途径。

  从上面可以看出,Tomcat在解析一个JSP页面时,首先为每一个定制标签定义并实例化了一个TagHandlerPool对象。页面的处理方法覆盖父类的_ jspService()方法,_jspService方法首先初始化环境,为内置对象赋值。由于index.jsp页面整体由一个<html:html/>标签包裹,Tomcat对每一个标签都产生一个私有方法加以实现。<html:html/>标签的处理方法是_jspx_meth_html_html_0()。这个方法的命名规范大家也可以从这里看出,就是“_jspx_meth + 标签的前缀 + 标签名 + 该标签在JSP页面同类标签中出现的序号”。其它标签都被包含在该标签中,所以其它标签在_jspx_meth_html_html_0()方法中进行解析。具体的代码实现请参见赛迪网http://linux.ccidnet.com期刊浏览2003年第6期。

  在_jspx_meth_html_html_0()方法中,首先从_jspx_tagPool_html_html_locale池中得到一个org.apache.struts.taglib.html.HtmlTag的实例,然后设置这个tag实例的页面上下文及上级标签,由于html:html标签是页面的最顶层标签,所以它的parent是null。然后对该标签的内容进行解析。HTML代码直接输出,下面主要看看<html:html></html:html>标签之间包含的<bean:message key="index.title"/>标签的解析。对bean:message标签的解析类似于html:html,Tomcat也将其放入一个单独的方法_jspx_meth_bean_message_0()中进行。

  bean:message标签的解析

  代码清单7:_jspx_meth_bean_message_0()方法片断

//对message定制标签的处理方法
private boolean _jspx_meth_bean_message_0(
javax.servlet.jsp.tagext.Tag _jspx_th_html_html_0, 
javax.servlet.jsp.PageContext pageContext) throws Throwable {
 JspWriter out = pageContext.getOut();
 /* ----  bean:message ---- */
 org.apache.struts.taglib.bean.MessageTag
 _jspx_th_bean_message_0 =
 (org.apache.struts.taglib.bean.MessageTag) 
 _jspx_tagPool_bean_message_key.get(
 org.apache.struts.taglib.bean.MessageTag.class);
 _jspx_th_bean_message_0.setPageContext(pageContext);
 _jspx_th_bean_message_0.setParent(_jspx_th_html_html_0);
 _jspx_th_bean_message_0.setKey("index.title");
 int _jspx_eval_bean_message_0 = _jspx_th_bean_message_0.doStartTag();
 if (_jspx_th_bean_message_0.doEndTag()== javax.servlet.jsp.
tagext.Tag.SKIP_PAGE) return true; _jspx_tagPool_bean_message_key.reuse(_jspx_th_bean_message_0); return false; }


  同样,对html:bean也需要从池中得到一个标签类的实例,然后设置环境。这里不再赘述。我们只专注对MessageTag定制标签类特殊的处理部分。定制标签类的开发不在本文讨论范围之内。在index.jsp中定义了一个bean:message标签,并设置了一个属性:<bean:message key="index.title"/>。Tomcat在解析时,调用MessageTag对象的key属性设置方法setKey(),将该属性置入。然后调用MessageTag的doStartTag()和doEndTag()方法,完成解析。如果doEndTag()方法的返回值为javax.servlet.jsp.tagext.Tag. SKIP_PAGE,表明已经完成解析,返回true,Tomcat将立即停止剩余页面代码的执行,并返回。否则把该MessageTag的实例放回池中。

  标签类对象实例的池化

  为了提高运行效率,Tomcat对所有的定制标签类进行了池化,池化工作由org.apache.jasper. runtime.TagHandlerPool类完成。TagHandlerPool类主要有两个方法,代码如下:

  代码清单8:TagHandlerPool.java

public class TagHandlerPool {
 private static final int MAX_POOL_SIZE = 5;
 private Tag[] handlers;
 public synchronized Tag get(Class handlerClass) throws JspException {……}
 public synchronized void reuse(Tag handler) {……}
}


  TagHandlerPool简单地实现了对标签类的池化,其中MAX_POOL_SIZE是池的初始大小,handlers是一个Tag的数组,存储标签类的实例。get(Class handlerClass)得到一个指定标签类的实例,如果池中没有可用实例,则新实例化一个。reuse(Tag handler)把handler对象放回池中。

  至此,我们对JSP在容器中的运行过程已经了然于胸了。虽然每种JSP容器的解析结果会有差异,但其中的原理都雷同。对于编写JSP应用,我们并不需要干涉容器中的运行过程,但如果你对整个底层的运行机制比较熟悉,就能对JSP/Servlet技术有更深的认识。
posted @ 2007-09-10 16:53 华梦行 阅读(185) | 评论 (0)编辑 收藏
RowCountCallbackHandler rcch = new RowCountCallbackHandler();
  jt.query("select * from vets", rcch);
  for(String colName: rcch.getColumnNames())
   System.out.println(colName);
  for(int colType: rcch.getColumnTypes())
   System.out.println(colType);
  System.out.println(rcch.getColumnCount());
  System.out.println(rcch.getRowCount());
posted @ 2007-09-10 11:35 华梦行 阅读(1009) | 评论 (0)编辑 收藏
for(Person pSon: pSonList){
   System.out.println(pSon.getId() + "," + pSon.getFirstName() + "," + pSon.getLastName());
  }
posted @ 2007-09-10 11:31 华梦行 阅读(105) | 评论 (0)编辑 收藏
     摘要: /** * Copyright: Copyright (c) 2005-2005 * Company: JavaResearch(http://www.javaresearch.org) ...  阅读全文
posted @ 2007-09-10 11:24 华梦行 阅读(2374) | 评论 (0)编辑 收藏
  1. /**


  2. * Copyright: Copyright (c) 2005-2005
  3. * Company: JavaResearch(http://www.javaresearch.org)
  4. */
  5. package org.javaresearch.jerch;



  6. import java.sql.PreparedStatement;
  7. import java.sql.SQLException;

  8. /**
  9. * 批量更新时设置PreparedStatement的值的接口定义。
  10. * 最后更新日期:2005年3月25日
  11. * @author cherami
  12. */
  13. public interface BatchPreparedStatementSetter {
  14.   /**
  15.    * 得到批次的总次数。
  16.    * @return 批次的总次数
  17.    */
  18.   publicint getBatchSize();

  19.   /**
  20.    * 设置第i次的值。
  21.    * 请注意它是针对每一次设置值的,
  22.    * 如果所有的值不是保存在一个List类型的可以通过索引访问的集合中,
  23.    * 那么这个方法的实现中可能需要使用switch或者if语句进行判断。
  24.    * 你可以考虑使用PreparedStatementSetter一次性设置全部的批次。
  25.    * @param ps PreparedStatement
  26.    * @param i 执行批次
  27.    * @throws SQLException
  28.    */
  29.   publicvoid setValues(PreparedStatement ps, int i) throwsSQLException;
  30. }
posted @ 2007-09-10 11:21 华梦行 阅读(1073) | 评论 (0)编辑 收藏

所谓的回调就是在执行statement A过程中,用到了statement B,那么先保存以前的执行信息,此时statement B 可以使用Statement A 的内容,然后再执行 Statement B, Statement B执行完毕后,返回的结果,可以被Statement A 利用,然后继续执行statementA。这就是传说中回调

posted @ 2007-09-10 11:16 华梦行 阅读(171) | 评论 (0)编辑 收藏
jt.execute("update owners set address = 'GuangZhou' where telephone = ?",
    new PreparedStatementCallback(){
     public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
      ps.setString(1, "16068008");
      ps.addBatch();
      ps.setString(1, "6085555487");
      ps.addBatch();
      return ps.executeBatch();
     }
  });
  1. /**


  2. * Copyright: Copyright (c) 2005-2005
  3. * Company: JavaResearch(http://www.javaresearch.org)
  4. */
  5. package org.javaresearch.jerch;

  6. import java.sql.PreparedStatement;
  7. import java.sql.SQLException;

  8. /**
  9. * PreparedStatement回调接口定义。
  10. * 最后更新日期:2005年3月28日
  11. * @author cherami
  12. */
  13. publicinterfacePreparedStatementCallback {
  14.   /**
  15.    * 定义PreparedStatement中的执行内容。
  16.    * 执行前PreparedStatement的参数等内容都已经设置好了。
  17.    * @param stmt PreparedStatement
  18.    * @return 执行的结果
  19.    * @throws SQLException
  20.    */
  21.   publicObject doInPreparedStatement(PreparedStatement stmt) throwsSQLException;
  22. }
posted @ 2007-09-10 11:10 华梦行 阅读(4280) | 评论 (0)编辑 收藏
  1. /**
  2. * Copyright: Copyright (c) 2005-2005
  3. * Company: JavaResearch(http://www.javaresearch.org)
  4. */
  5. package org.javaresearch.jerch;

  6. import java.sql.ResultSet;
  7. import java.sql.SQLException;

  8. /**
  9. * 提取ResultSet中的全部数据的接口定义。
  10. * 最后更新日期:2005年3月28日
  11. * @author cherami
  12. */
  13. public interface ResultSetExtractor {
  14.   /**
  15.    * 提取ResultSet中的全部数据。
  16.    * @param rs ResultSet
  17.    * @return ResultSet中的全部数据
  18.    * @throws SQLException
  19.    */
  20.   publicObject extractSet(ResultSet rs) throwsSQLException;
  21. }

List perList = (List)jt.query("select * from vets",
    new ResultSetExtractor(){
      public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
       List<Person> personList = new ArrayList<Person>();
       while(rs.next()){
        Person per = new Person();
        per.setId(rs.getInt("id"));
        per.setFirstName(rs.getString("first_name"));
        per.setLastName(rs.getString("last_name"));
        personList.add(per);
       }
       return personList;
    }
  });

posted @ 2007-09-10 11:05 华梦行 阅读(298) | 评论 (0)编辑 收藏
  1. public interface Mappable {


  2.   /**
  3.    * 得到字段对应的填充方法的方法名。
  4.    * @param fieldName 数据库表的字段名
  5.    * @return 进行填充的方法名。
  6.    */
  7.   publicString getMapMethod(String fieldName);
  8.   /**
  9.    * 得到字段对应的填充方法的参数类型。
  10.    * 数据库返回的值的类型和Java中的可能不是匹配的,或者JavaBean中自己定义为原始类型了,因此需要指定。
  11.    * @param fieldName 数据库表的字段名
  12.    * @param dbType 数据库返回的类型常量定义
  13.    * @return 进行填充的方法的参数的类型
  14.    */
  15.   publicClass getMethodParameterType(String fieldName,int dbType);
  16. }
posted @ 2007-09-10 11:00 华梦行 阅读(146) | 评论 (0)编辑 收藏
  1. /**


  2. * Copyright: Copyright (c) 2005-2005
  3. * Company: JavaResearch(http://www.javaresearch.org)
  4. */
  5. package org.javaresearch.jerch;

  6. import java.sql.PreparedStatement;
  7. import java.sql.SQLException;

  8. /**
  9. * 设置PreparedStatement的参数的接口定义。
  10. * 最后更新日期:2005年3月28日
  11. * @author cherami
  12. */
  13. public interface PreparedStatementSetter {
  14.   /**
  15.    * 设置PreparedStatement所需的全部参数。
  16.    * @param ps PreparedStatement
  17.    * @throws SQLException
  18.    */
  19.   publicvoid setValues(PreparedStatement ps)  throwsSQLException;
  20. }
  21. jt.query("select address from owners where first_name = ? and last_name = ?",
        new PreparedStatementSetter(){
         public void setValues(PreparedStatement ps) throws SQLException {
          ps.setString(1, "Jeff");
          ps.setString(2, "Black");
         }
        },
        new RowMapper(){
         public Object mapRow(ResultSet rs, int rowNum) throws SQLException {      
          return rs.getString("address");
         }
        }));
posted @ 2007-09-10 10:54 华梦行 阅读(4000) | 评论 (0)编辑 收藏
  1. /**


  2. * Copyright: Copyright (c) 2005-2005
  3. * Company: JavaResearch(http://www.javaresearch.org)
  4. */
  5. package org.javaresearch.jerch;

  6. import java.sql.Connection;
  7. import java.sql.PreparedStatement;
  8. import java.sql.SQLException;

  9. /**
  10. * 创建PreparedStatement的接口定义。
  11. * 最后更新日期:2005年3月28日
  12. * @author cherami
  13. */
  14. public interface PreparedStatementCreator {
  15.   /**
  16.    * 从数据库连接中创建一个PreparedStatement。
  17.    * @param con 数据库连接
  18.    * @return 创建好的PreparedStatement
  19.    * @throws SQLException
  20.    */
  21.   publicPreparedStatement createPreparedStatement(Connection con) throwsSQLException;
  22. }
  23. 使用实例:
  24. jt  jdbctemplate:
      System.out.println(jt.execute(
        new PreparedStatementCreator(){
         public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
          return con.prepareStatement("select address from owners");
         }
        },
        new PreparedStatementCallback(){
         public Object doInPreparedStatement(PreparedStatement ps) throws SQLException, DataAccessException {
          List<String> list = new ArrayList<String>();
          ResultSet rs = null;
          try{
           rs = ps.executeQuery();
           while(rs.next()){
            list.add(rs.getString("address"));
           }
          }finally{
           JdbcUtils.closeResultSet(rs);
          }
          return list;
         }
        }));
posted @ 2007-09-10 10:38 华梦行 阅读(4142) | 评论 (0)编辑 收藏
仅列出标题
共15页: First 上一页 6 7 8 9 10 11 12 13 14 下一页 Last