View Javadoc
1   /**
2    * Copyright By Gransfoft Company Limited.  
3    * 2014年3月19日 下午5:23:26
4    */
5   package gboat2.base.dao.aspect;
6   
7   import gboat2.base.core.dao.QuerySupport;
8   import gboat2.base.core.annotation.Relation;
9   import gboat2.base.core.annotation.Relations;
10  import gboat2.base.dao.util.GBoatDaoClassLoader;
11  
12  import java.lang.reflect.Proxy;
13  import java.sql.SQLException;
14  import java.util.Map;
15  import java.util.Map.Entry;
16  import java.util.Set;
17  
18  import org.apache.commons.collections.MapUtils;
19  import org.apache.commons.lang3.ClassUtils;
20  import org.apache.commons.lang3.ArrayUtils;
21  import org.apache.commons.lang3.ObjectUtils;
22  import org.apache.commons.lang3.StringUtils;
23  import org.aspectj.lang.JoinPoint;
24  import org.aspectj.lang.Signature;
25  import org.hibernate.SessionFactory;
26  import org.springframework.beans.factory.InitializingBean;
27  import org.springframework.jdbc.CannotGetJdbcConnectionException;
28   
29  /**
30   * 实现 SessionFactory 动态切换的切面,该切面执行 SessionFactory 切换的规则:
31   * <ol>
32   * <li>如果目标实例的类型或方法(全限定名)配置了指定的 SessionFactory,则直接切换,如下面的配置示例中的
33   * glodon.gbp.xxx.service.TestServiceImpl,在执行此类的所有方法时都会使用  mysqlSessionFactory</li>
34   * <li>方法参数中包含 Class 类型参数,且该 Class 的名称匹配 targetSessionFactorys 中的任一规则</li>
35   * <li>方法参数中包含 Map 类型参数,且该 Map 中 key 为  {@link QuerySupport#PARAM_TABLENAME} 的值匹配 targetSessionFactorys 中的任一规则</li>
36   * <li>方法有且只有一个参数,且参数对象的 getClass() 方法得到的 Class 满足上面的第 2 点</li>
37   * <li>不满足上述任意切换 SessionFactory的条件时,将清除 SessionFactory切换设置,直接使用默认 SessionFactory</li>
38   * </ol>
39   * 
40   * 要启用该切面,需在 Spring 配置文件中新增如下配置(配置示例):
41   * <pre>
42   * <code>
43   * &lt;-- 声明切面实例 --&gt;
44   * &lt;bean id="gboatSessionFactoryAspect" class="gboat2.base.dao.aspect.GboatSessionFactoryAspect"&gt;
45   *     &lt;property name="targetSessionFactorys"&gt;
46   *         &lt;!--  SessionFactory 与类名的映射关系,默认 SessionFactory不用配置 --&gt;
47   *         &lt;map key-type="java.lang.String"&gt;
48   *             &lt;!-- 默认数据源的对应的 SessionFactory 不用在此处配置 --/&gt;
49   *             &lt;entry key="glodon\.gbp\.enterprise\.model\..*" value-ref="gfmSessionFactory" /&gt;
50   *         &lt;/map&gt;
51   *     &lt;/property&gt;
52   * &lt;/bean&gt;
53   * 
54   * &lt;-- 配置切面的拦截规则 --&gt;
55   * &lt;aop:config&gt;
56   *     &lt;aop:pointcut id="gboatSessionFactoryPointcut" expression="execution(* gboat2.base.core.dao.IBaseDAO+.*(..))" /&gt;
57   *     &lt;aop:aspect ref="gboatSessionFactoryAspect" order="1" &gt;
58   *         &lt;aop:before method="dynamicSetSessionFactory" pointcut-ref="gboatSessionFactoryPointcut"/&gt;
59   *     &lt;/aop:aspect&gt;
60   * &lt;/aop:config&gt;
61   * </code>
62   * </pre>
63   * 
64   * @author <a href="mailto:[email protected]">何明旺</a>
65   * @since 3.0
66   * @date 2014年3月19日
67   * @see gboat2.base.dao.impl.GboatSessionFactoryBean
68   * @see gboat2.base.dao.GboatTransactionManager
69   */
70  public class GboatSessionFactoryAspect implements InitializingBean{
71      
72      /** 方法名、类名(均为全限定名)的正则表达式与其需要使用的 SessionFactory 的映射关系 */
73      private Map<String, SessionFactory> targetSessionFactorys;
74  
75      public void dynamicSetSessionFactory(JoinPoint joinPoint) throws Exception {
76          Signature signature = joinPoint.getSignature(); // 切面拦截的方法签名
77          Class<?> clazz = joinPoint.getTarget().getClass();
78          if (ClassUtils.isAssignable(clazz, Proxy.class)) {
79              clazz = signature.getDeclaringType();
80          }
81  
82  //        String className = clazz.getName(); // 类名
83  //        String methodName = signature.getName(); // 方法名
84  //        String fullMethodName = className + "." + methodName;
85          Object[] arguments = joinPoint.getArgs(); // 参数列表
86          
87          String regex = null;
88          SessionFactory tempSf = null;
89          SessionFactory sessionFactory = null;
90          Set<Entry<String, SessionFactory>> entrySet = this.getTargetSessionFactorys().entrySet();
91          int i = 0;
92          targetSfLoop : for (Entry<String, SessionFactory> entry : entrySet) {
93              regex = StringUtils.trimToEmpty(entry.getKey());
94              tempSf = entry.getValue();
95              /*
96              // 当前拦截目标的方法或类型设置了 SessionFactory
97              if (fullMethodName.matches(regex) // 方法名匹配
98                      || className.matches(regex)){ // 类名匹配
99                  sessionFactory = tempSf;
100                 break targetSfLoop;
101             }
102             */
103             if (ArrayUtils.isEmpty(arguments))
104                 break;
105             
106             for (Object arg : arguments) {
107                 if (arg instanceof Class) { // 根据主键查询(唯一查询)
108                     if(i == 0) {
109                         // 外层循环为第一次时,检查 VO 对象关联的 PO 是否来自同一个 SessionFactory
110                         validateVo((Class<?>) arg);
111                     }
112                     
113                     // 方法传入参数的类型为 Class<?>, 且对传入的参数的类型指定了 SessionFactory
114                     if (((Class<?>) arg).getName().matches(regex)) {
115                         sessionFactory = tempSf;
116                         break targetSfLoop;
117                     }
118                 } else if (arg instanceof Map) { // 列表查询
119                     // 要查询的表名
120                     Object table = ((Map<?, ?>) arg).get(QuerySupport.PARAM_TABLENAME);
121                     if (table != null) {
122                         Class<?> tableClass = null;
123                         if (table instanceof Class) {
124                             tableClass = ((Class<?>) table);
125                         } else if (table instanceof CharSequence) {
126                             GBoatDaoClassLoader daoClassLoader = GBoatDaoClassLoader.getInstance();
127                             if (daoClassLoader == null)
128                                 throw new IllegalStateException(
129                                         "Gboat2 DAO Bundle 的类加载器没有被实例化,无法加载 PO 或 VO [" + table + "]");
130                             // 如果 TABLENAME 对应的值是字符串,而非 Class,则取得其对应的类定义
131                             tableClass = daoClassLoader.loadClass(table.toString());
132                         }
133                         
134                         if (tableClass != null) {
135                             if (i == 0) {
136                                 // 外层循环为第一次时,检查 VO 对象关联的 PO 是否来自同一个 SessionFactory
137                                 validateVo(tableClass);
138                             }
139                             
140                             if (tableClass.getName().matches(regex)) {
141                                 sessionFactory = tempSf;
142                                 break targetSfLoop;
143                             }
144                         }
145                     }
146                 } else if(arg != null && arguments.length == 1) { // 保存和更新(save、update)
147                     // 保存和更新的对象只可能是 PO,而不会是 VO,所以此处无需调用 validateVo(Class) 方法进行 VO 对象检查
148                     if (arg.getClass().getName().matches(regex)) {
149                         sessionFactory = tempSf;
150                         break targetSfLoop;
151                     }
152                 }
153             }
154             i++; // 每完成一次循环,将 i 自增 1
155         }
156 
157         if(sessionFactory == null) {
158             SessionFactoryHolder.clearSessionFactory();
159         } else {
160             SessionFactoryHolder.setSessionFactory(sessionFactory);
161         }
162     }
163 
164     /**
165      * 检查 vo 对象所关联的所有 po 是否均属于同一个 SessionFactory 管理,如果不是,则抛出
166      * {@link org.springframework.jdbc.CannotGetJdbcConnectionException.CannotGetJdbcConnectionException}
167      * 异常
168      * 
169      * @param voClass VO 对象的类定义。如果为 null 或没有被
170      *            &#64;gboat2.base.core.dao.annotation.Relations 注解,此方法将直接返回
171      */
172     public void validateVo(Class<?> voClass){
173         if(voClass == null)
174             return;
175         
176         Relations relations = voClass.getAnnotation(Relations.class);
177         if(relations == null)
178             return;
179         
180         String baseClassName = relations.base().getName();
181         SessionFactory baseSessionFactory = null; // VO 对应的 PO 基类的 SessionFactory
182         Set<Entry<String, SessionFactory>> entrySet = getTargetSessionFactorys().entrySet();
183         for (Entry<String, SessionFactory> entry : entrySet) {
184             if(baseClassName.matches(entry.getKey())) {
185                 baseSessionFactory = entry.getValue();
186                 break;
187             }
188         }
189         
190         Relation[] array = relations.value();
191         SessionFactory referSessionFactory = null;
192         for (Relation relation : array) {
193             String referClassName = relation.refer().getName();
194             for (Entry<String, SessionFactory> entry : entrySet) {
195                 if(referClassName.matches(entry.getKey())) {
196                     referSessionFactory = entry.getValue();
197                     break;
198                 }
199             }
200             
201             // PO 基类的 SessionFactory 和被关联的 PO 类的 SessionFactory 不相同
202             // 如果所有被关联的 PO 类的 SessionFactory 都和 PO 基类的 SessionFactory 相同,则所有被关联的 PO 所属的 SessionFactory 都相同
203             if(!ObjectUtils.equals(baseSessionFactory, referSessionFactory))
204                 throw new CannotGetJdbcConnectionException("VO [" + voClass.getName() + "] 对象所关联的 PO 类由不同的 SessionFactory 进行管理",
205                         new SQLException("baseClass[" + baseClassName + "] --> " + baseSessionFactory + ", referClass[" + referClassName + "] -->" + referSessionFactory));
206         }
207     }
208     
209     public Map<String, SessionFactory> getTargetSessionFactorys() {
210         return targetSessionFactorys;
211     }
212 
213     public void setTargetSessionFactorys(Map<String, SessionFactory> targetSessionFactorys) {
214         this.targetSessionFactorys = targetSessionFactorys;
215     }
216 
217     @Override
218     public void afterPropertiesSet() throws Exception {
219         if(MapUtils.isEmpty(this.getTargetSessionFactorys()))
220             throw new IllegalArgumentException(getClass().getName() + " 切面的 targetSessionFactorys 属性不能为空");
221     }
222 
223 }