CAS和Shiro在spring中集成+Hibernate缓存优化

来源:互联网 发布:程序员显示器推荐2017 编辑:程序博客网 时间:2024/04/29 17:54

CAS和Shiro在spring中集成


shiro是权限管理框架,现在已经会利用它如何控制权限。为了能够为多个系统提供统一认证入口,又研究了单点登录框架cas。因为二者都会涉及到对session的管理,所以需要进行集成。

 

Shiro在1.2.0的时候提供了cas的集成因此在项目中添加shiro-cas的依赖
    <dependency>
       <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-cas</artifactId>
       <version>${shiro.version}</version>
    </dependency>

 

Shirocas集成后,cas client的配置更加简单了。原理就是将casFilter添加到到shiroFilter的filterChain中。 shiroFilter是在web.xml中定义的,前文已经讲过。


在Spring项目中集成Shiro和CAS

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <?xmlversionxmlversion="1.0" encoding="UTF-8"?>  
  2. <beansxmlnsbeansxmlns="http://www.springframework.org/schema/beans"  
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  4. xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans-2.5.xsd"  
  5. default-lazy-init="true">  
  6.    
  7. <beanidbeanid="shiroFilter"class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  8. <propertynamepropertyname="securityManager" ref="securityManager" />  
  9.    
  10. <!--没有单点登录下的配置:没有权限或者失败后跳转的页面 -->  
  11. <!--<property name="loginUrl" value="/login/toLoginAction"/> -->  
  12.    
  13. <!--有单点登录的配置:登录 CAS 服务端地址,参数 service 为服务端的返回地址 -->   
  14. <propertynamepropertyname="loginUrl"  
  15. value="http://localhost:18080/cas/login?service=http://localhost:8080/gxpt_web_qx_login/shiro-cas"/>  
  16. <!--<property name="successUrl" value="/page/index.jsp"/> -->  
  17. <propertynamepropertyname="successUrl" value="/indexAction" />  
  18.    
  19. <propertynamepropertyname="filters">  
  20. <map>  
  21. <!--添加casFilter到shiroFilter -->  
  22. <entrykeyentrykey="casFilter" value-ref="casFilter">  
  23. </entry>  
  24. </map>  
  25. </property>  
  26.    
  27.                  <propertynamepropertyname="filterChainDefinitions">  
  28. <value>  
  29. /shiro-cascasFilter  
  30. /styles/**= anon  
  31. /**= user  
  32. </value>  
  33. </property>  
  34.    
  35. <!--没有单点登录下的配置: -->  
  36. <!--<property name="filterChainDefinitions">  
  37. <value>  
  38. /styles/**= anon  
  39. /login/loginActionanon  
  40. /login/logoutActionlogout  
  41. /**= user  
  42. </value>  
  43. </property>-->  
  44. </bean>  
  45.    
  46. <beanidbeanid="casFilter" class="org.apache.shiro.cas.CasFilter">  
  47. <!--配置验证错误时的失败页面(Ticket 校验不通过时展示的错误页面) -->  
  48. <propertynamepropertyname="failureUrl" value="/page/error.jsp" />  
  49. </bean>  
  50.    
  51. <beanidbeanid="securityManager"class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  52. <!--Single realm app. If you have multiple realms, use the 'realms' property  
  53. instead.-->  
  54. <!--没有单点登录下的配置: -->          
  55. <!--<property name="realm" ref="shiroDbRealm" /> -->  
  56.    
  57. <propertynamepropertyname="realm" ref="casRealm" />  
  58. <propertynamepropertyname="subjectFactory" ref="casSubjectFactory" />  
  59.    
  60. <propertynamepropertyname="cacheManager" ref="shiroEhcacheManager" />  
  61. </bean>  
  62.    
  63. <beanidbeanid="casRealm" class="web.qx.login.shiro.MyCasRealm">  
  64. <propertynamepropertyname="defaultRoles" value="ROLE_USER"/>   
  65. <propertynamepropertyname="casServerUrlPrefix"value="http://localhost:18080/cas" />  
  66. <!--客户端的回调地址设置,必须和上面的shiro-cas过滤器拦截的地址一致 -->  
  67. <propertynamepropertyname="casService"  
  68. value="http://localhost:8080/gxpt_web_qx_login/shiro-cas"/>  
  69. </bean>  
  70.    
  71. <!--Define the realm you want to use to connect to your back-end security  
  72. datasource:-->  
  73. <!--  
  74. <beanidbeanid="shiroDbRealm"class="web.qx.login.shiro.ShiroDbRealm">  
  75. <propertynamepropertyname="loginService"ref="login-loginBean"></property>  
  76. </bean>  
  77.  -->  
  78.    
  79. <beanidbeanid="casSubjectFactory"class="org.apache.shiro.cas.CasSubjectFactory" />  
  80.    
  81. <!--用户授权/认证信息Cache, 采用EhCache 缓存 -->  
  82. <beanidbeanid="shiroEhcacheManager"class="org.apache.shiro.cache.ehcache.EhCacheManager">  
  83. <propertynamepropertyname="cacheManagerConfigFile"value="classpath:config/ehcache-shiro.xml" />  
  84. </bean>  
  85.    
  86.    
  87. <!--保证实现了Shiro内部lifecycle函数的bean执行 -->  
  88. <beanidbeanid="lifecycleBeanPostProcessor"class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />  
  89.    
  90.    
  91. <!--AOP式方法级权限检查 -->  
  92. <!--Enable Shiro Annotations for Spring-configured beans. Only run after -->  
  93. <!--the lifecycleBeanProcessor has run: -->  
  94. <bean  
  95. class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"  
  96. depends-on="lifecycleBeanPostProcessor">  
  97. <propertynamepropertyname="proxyTargetClass" value="true" />  
  98. </bean>  
  99. <bean  
  100. class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">  
  101. <propertynamepropertyname="securityManager" ref="securityManager" />  
  102. </bean>  
  103.    
  104. </beans>  

 

没有单点登录情况下的话,登录认证和授权认证默认在AuthorizingRealm的doGetAuthorizationInfo和doGetAuthenticationInfo中进行,所以我这里是通过shiroDbRealm(继承AuthorizingRealm的自定义类)覆写doGetAuthorizationInfo和doGetAuthenticationInfo,实现自定义登录认证和授权认证。

 

有单点登录情况下,登录认证是在casserver进行的,那么执行流程是这样的:用户从 cas server登录成功后,跳到cas client的CasRealm执行默认的doGetAuthorizationInfo和doGetAuthenticationInfo,此时doGetAuthenticationInfo做的工作是把登录用户信息传递给shiro,保持默认即可,而对于授权的处理,可以通过MyCasRealm(继承CasRealm的自定义类)覆写doGetAuthorizationInfo进行自定义授权认证。


2、HIbernate缓存优化


  近来坤哥推荐我我们一款性能监控、调优工具——JavaMelody,通过它让我觉得项目优化是看得见摸得着的,优化有了针对性。而无论是对于分布式,还是非分布,缓存是提示性能的有效工具。

    数据层是EJB3.0实现的,而EJB3.0内部也是通过Hibernate实现的,而Hibernate本身提供了很好的缓存机制,我们只需要学会使用它驾驭它就够了。

    缓存的机能可以简单理解为将从数据库中访问的数据放在内存中,在以后再次使用到这些数据时可以直接从内存中读取而不必要再次访问数据库,尽量减少和数据库的交互提高性能。

    

概念讲解

hibernate中提供了二种缓存机制:一级缓存、二级缓存,因为二级缓存策略是针对于ID查询的缓存策略,对于条件查询则毫无作用,为此,Hibernate提供了针对条件查询的Query Cache(查询缓存)。

 一、一级缓存:

     一级缓存是hibernate自带的,不受用户干预,其生命周期和session的生命周期一致,当前session一旦关闭,一级缓存就会消失,因此,一级缓存也叫session缓存或者事务级缓存,一级缓存只存储实体对象,不会缓存一般的对象属性,即:当获得对象后,就将该对象缓存起来,如果在同一个session中再去获取这个对象时,它会先判断缓存中有没有这个对象的ID,如果有,就直接从缓存中取出,否则,则去访问数据库,取了以后同时会将这个对象缓存起来。

 

二、二级缓存:

 二级缓存也称为进程缓存或者sessionFactory级的缓存,它可以被所有的session共享,二级缓存的生命周期和sessionFactory的生命周期一致,二级缓存也是只存储实体对象

二级缓存的一般过程如下:

     ①:条件查询的时候,获取查询到的实体对象

     ②:把获得到的所有数据对象根据ID放到二级缓存中

     ③:当Hibernate根据ID访问数据对象时,首先从sesison的一级缓存中查,查不到的时候如果配置了二级缓存,会从二级缓存中查找,如果还查不到,再查询数据库,把结果按照ID放入到缓存中

     ④:进行deleteupdateadd操作时会同时更新缓存

 

三、查询缓存:

查询缓存是对普通属性结果集的缓存,对实体对象的结果集只缓存id,对于经常使用的查询语句,如果启用了查询缓存,当第一次执行查询语句时,Hibernate会把查询结果存放在二级缓存中,以后再次执行该查询语句时,只需从缓存中获得查询结果,从而提高查询性能,查询缓存中以键值对的方式存储的,key键为查询的条件语句(具体的key规则应该是:类名+方法名+参数列表),value为查询之后等到的结果集的ID列表

 

查询缓存的一般过程如下:

     ①Query Cache保存了之前查询执行过的SelectSQL,以及结果集等信息组成一个Query Key

     ②:当再次遇到查询请求的时候,就会根据QueryKeyQueryCache中找,找到就返回,但当数据表发生数据变动的话,hbiernate就会自动清除QueryCache中对应的Query Key

    我们从查询缓存的策略中可以看出,Query Cache只有在特定的条件下才会发挥作用,而且要求相当严格:

     ①:完全相同的SelectSQL重复执行

     ②:重复执行期间,QueryKey对应的数据表不能有数据变动


启用缓存的配置

EJB中配置查询缓存和二级缓存

1、在persistence.xml中启用缓存

[html] view plaincopy在CODE上查看代码片派生到我的代码片
  1. <persistence-unitnamepersistence-unitname="gxpt-qx-entity" transaction-type="JTA" >  
  2. <!--对jpa进行性能测试 -->  
  3. <provider>net.bull.javamelody.JpaPersistence</provider>  
  4.    
  5.                  <jta-data-source>java:/MySqlDS</jta-data-source>  
  6.                  <!--<jta-data-source>java:/MyOracleDS</jta-data-source>   -->  
  7.                    
  8.                  <properties>   
  9.                           <!-- <propertyname="hibernate.dialect"value="org.hibernate.dialect.Oracle10gDialect"/> -->  
  10.                           <propertynamepropertyname="hibernate.dialect"value="org.hibernate.dialect.MySQLDialect"/>  
  11. <propertynamepropertyname="hibernate.hbm2ddl.auto" value="update" />  
  12. <propertynamepropertyname="hibernate.show_sql" value="true" />  
  13.    
  14. <!--指定二级缓存产品的提供商 -->  
  15. <propertynamepropertyname="hibernate.cache.provider_class"  
  16. value="net.sf.ehcache.hibernate.SingletonEhCacheProvider"/>  
  17. <!--                        <propertynamepropertyname="hibernate.cache.region.factory_class"  
  18. value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>-->  
  19. <!--开启二级缓存 -->  
  20. <propertynamepropertyname="hibernate.cache.use_second_level_cache"value="true"/>  
  21. <!--开启查询缓存 -->  
  22. <propertynamepropertyname="hibernate.cache.use_query_cache"value="true"/>   
  23. <!--指定缓存配置文件位置   -->  
  24. <propertynamepropertyname="hibernate.cache.provider_configuration_file_resource_path"  
  25. value="/ehcache.xml"/>  
  26.    
  27.                  </properties>  
  28. </persistence-unit>  

 

2、通过注解指定User类使用二级缓存

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. @Entity  
  2. @Table(name="tq_user")  
  3. @Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)  
  4. publicclass User  

 

3、开启查询缓存,除了在persistence.xml中有以上配置外,还需要在底层代码手动开启查询缓存

query.setHint("org.hibernate.cacheable", true);

或者query.setCacheable(true);

 

性能测试

通过sql语句

1、二级缓存关闭时,查询缓存开启和关闭情况对比*****普通属性查询

    

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public String myCache() {  
  2.         List<String> strings = this.userServiceImpl  
  3.                 .search("selectu.username from User u where id<4 order by id asc");  
  4.         for (String str : strings){  
  5.            System.out.println("username:" + str);  
  6.         }  
  7.    
  8.         System.out.println("===================================");  
  9.         List<String> strings2 =this.userServiceImpl  
  10.                 .search("select u.usernamefrom User u where id<4 order by id asc");  
  11.         for (String str : strings2){  
  12.            System.out.println("username:" + str);  
  13.         }  
  14.         return "/mycache";  
  15.     }  

 

     当前二级缓存为关闭状态,看看查询缓存关闭时的查询结果:

  

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. public List<String> search(Stringhql) {  
  2.       List<String> rtnStrs = newArrayList<String>();  
  3.       try {  
  4.          Session session = this.sessionFactory.openSession();  
  5.           session.beginTransaction();  
  6.   
  7.          Query query = session.createQuery(hql);  
  8.           //query.setCacheable(true);//手动开启查询缓存  
  9.           rtnStrs =(List<String>) query.list();  
  10.   
  11.          session.getTransaction().commit();  
  12.       } catch (Exception e){  
  13.           System.out.println("DAO层根据HQL语句查询失败");  
  14.       }  
  15.       return rtnStrs;  
  16.   }  

   上面代码中屏蔽了query.setCacheable(true)

    

    关闭二级缓存、关闭查询缓存 运行如下:


 

   开启查询缓存、关闭二级缓存运行如下


结论:对于查询普通属性,无论二级缓存是否开启,只要开启了查询缓存,当两次执行的sql语句相同时,第二次不会发出sql语句,直接从内存中获取。

 

2、查询缓存开启时,二级缓存打开和关闭情况对比*******查询实体对象

   

[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. /** 
  2.      * 查询缓存开启,二级缓存关闭*******查询实体对象 
  3.      * 
  4.      *运行结果:如果关闭查询缓存和二级缓存,在两次查询时都发出sql语句,此时为两条查询语句 
  5.      * 
  6.      *运行结果:如果开启查询缓存,关闭二级缓存,第二次会发出根据ID查询实体的n条查询语句 
  7.      * 
  8.      *运行结论:第一次执行list时,会把查询对象的ID缓存到查询缓存中,第二次执行list时(两次的查询SQL语句相同),会遍历查询缓存中的ID到 
  9.      * (一级、二级)缓存里找实体对象, 此时没有,则发出查询语句到数据库中查询 
  10.      * 
  11.     */          
  12. publicString mycache3() {  
  13. List<User>users1 = this.userServiceImpl.search();  
  14.            for(User u : users1) {  
  15.               System.out.println("users1:username:"+ u.getUsername());  
  16.           }  
  17.    System.out.println("===============");  
  18.    
  19.         List<User> users2 =this.userServiceImpl.search();  
  20.         for (User u : users2) {  
  21.            System.out.println("users2:usersname:" + u.getUsername());  
  22.         }  
  23. return"/mycache";  
  24.     }  

 

开启查询缓存、关闭二级缓存 运行如下:(两次都发出sql,而且第二次发出n条语句)



 

开启查询缓存、关闭二级缓存 运行如下(只发出一条语句)


 

总结

1、当只是用hibernate查询缓存,而关闭二级缓存的时候:

如果查询的是部分属性结果集,那么当第二次查询的时候就不会发出SQL语句,直接从Hibernate查询缓存中取数据

如果查询的是实体结果集(eg.from User)这个HQL,那么查询出来的实体,首先hibernate查询缓存存放实体的ID,第二次查询的时候,就到hibernate查询缓存中取出ID一条一条的到数据库查询,这样将发出NSQL语句,造成SQL泛滥。所以,在使用查询缓存的时候,最好配合开启二级缓存

 

2、当开启Hibernate查询缓存和二级缓存的时候:

如果查询的是部分属性结果集,这个和上面只用hbiernate查询缓存而关闭二级缓存的时候一致,因为不涉及实体,不会用到二级缓存。

如果查询的是实体结果集,那么查询出来的实体首先在查询缓存中存放实体的ID,并将实体对象保存到二级缓存中,第二次查询的时候,就到hibernate查询缓存中取ID,根据ID去二级缓存中匹配数据,如果有数据就不会发出sql语句,如果都有,第二次查询一条SQL语句都不会发出,直接从二级缓存中取数据。


通过Javamelody

JavaMelody能够在运行环境中监测JavaJava EE应用程序服务器。并以图表的形式显示:Java内存和Java CPU使用情况,用户Session数量,JDBC连接数,和http请求、sql请求、jsp页面与业务接口方法(EJB3SpringGuice)的执行数量,平均执行时间,错误百分比等。

坤哥博客有介绍Java项目性能监控和调优工具-Javamelody

 

监控项目缓存个数

这是本次开发的权限项目中的缓存,共有12个,其中红色部分分别为二级缓存和查询缓存


 

spring的监控

加缓存情况


不加缓存情况


 

根据统计结果,发现缓存的确可以提高性能。

但是有时候使用了缓存反而性能会降低,比如update方法,因为数据发生变更后,hibernate需要保持缓存和数据库两份的数据同步,所以加上缓存后,update性能降低,adddelete操作也是相同的道理。

所以缓存适用于在项目中存在大量查询的情况,否则是没必要适用的。


小结

在想项目之所以很吸引自己,很重要的一点是因为我们在使用各种各样的工具,包括在此提到的缓存、javamelody,正如“君子生非异也,善假于物也”,更多工具的使用,会在后面详细介绍。


0 0