權限子系統與文檔處理子系統交織的弊病
如同一個成規模的公司一樣,對于一個成規模的應用來說,內中會存在著各種子系統以執行不同的功能,而且為了協調工作,它們往往是交織在一起,就像上一個版本中權限子系統與文檔處理子系統交織一樣。
子系統交織是必要的,但子系統間過多過緊密的耦合并不合理。因為交織會增加軟件系統的熵值,使得系統隨著功能的增加和細化變得越來越復雜而難于控制,最后不得不推倒重來,給個人和公司帶來巨大的損失。這是軟件業普遍的問題,在國內公司和外包公司都不罕見。
因此,在程序的編寫過程中,程序員必須有意識的減少子系統之間的交織,使它們離散化,利用Spring的AOP可以有效幫助我們做到這一點。對于簡單值權限系統,使用AOP進行子系統離散化的具體思路如下:
1.將DocService中負責權限的代碼(屬性和函數)都分離,放置到一個專門的權限處理類PrivilegeService中。
2.使用Spring的ProxyFactoryBean,做成DocService的代理,讓PrivilegeService作為它的前置處理器。
3.在PrivilegeService的before方法中,執行權限檢查,如果權限值不夠則拋出異常。
分離后DocService簡潔的代碼
package com.heyang.aopstyle.service;

import com.heyang.traditonal.domain.Doc;
import com.heyang.traditonal.domain.User;
import com.heyang.traditonal.service.IDocService;


/** *//**
* 為領域對象Doc提供服務
*
* @author 何楊(heyang78@gmail.com)
*
* @since 2008-12-30 下午05:13:29
* @version 1.00
*/

public class DocService implements IDocService
{

public void add(Doc doc, User user)
{
System.out.println("將" + doc + "交由dao處理(存入數據庫)");
}


public void delete(Doc doc, User user)
{
System.out.println("將" + doc + "交由dao處理(從數據庫刪除)");
}


public void update(Doc doc, User user)
{
System.out.println("將" + doc + "交由dao處理(更新數據庫中對應的記錄)");
}
}
新組建的PrivilegeService類
package com.heyang.aopstyle.service;

import java.lang.reflect.Method;

import org.springframework.aop.MethodBeforeAdvice;

import com.heyang.aopstyle.exception.PrivilegeNotEnoughException;
import com.heyang.traditional2.dao.PrivilegeDao;
import com.heyang.traditonal.domain.User;


/** *//**
* 實現權限子系統
* @author: 何楊(heyang78@gmail.com)
* @date: 2009-1-2-下午04:19:13
*/

public class PrivilegeService implements MethodBeforeAdvice
{
private PrivilegeDao privilegeDao;
// 添加doc需要的權限名
private String addDocPrivilegeName;
// 刪除doc需要的權限名
private String deleteDocPrivilegeName;
// 更新doc需要的權限名
private String updateDocPrivilegeName;

/** *//**
* 在IDocService的實際方法開始前進行前置處理--權限檢查
*/

public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable
{
// 取得方法名
String mothodName=arg0.getName();
// 取得用戶權限
User user=(User)arg1[1];
int userPrivilegeValue=user.getPrivilegePoint();
// 根據方法名判斷用戶是否達到了需要的權限,否則拋出異常

if("add".equals(mothodName))
{

if(userPrivilegeValue<=getPrivilegeValueBy(addDocPrivilegeName))
{
throw new PrivilegeNotEnoughException("用戶權限必須達到"+getPrivilegeValueBy(addDocPrivilegeName)+"才能執行添加操作");
}
}

else if("delete".equals(mothodName))
{

if(userPrivilegeValue<=getPrivilegeValueBy(deleteDocPrivilegeName))
{
throw new PrivilegeNotEnoughException("用戶權限必須達到"+getPrivilegeValueBy(deleteDocPrivilegeName)+"才能執行刪除操作");
}
}

else if("update".equals(mothodName))
{

if(userPrivilegeValue<=getPrivilegeValueBy(updateDocPrivilegeName))
{
throw new PrivilegeNotEnoughException("用戶權限必須達到"+getPrivilegeValueBy(updateDocPrivilegeName)+"才能執行更新操作");
}
}
}

private int getPrivilegeValueBy(String name)
{
return privilegeDao.getValueBy(name);
}


public PrivilegeDao getPrivilegeDao()
{
return privilegeDao;
}


public void setPrivilegeDao(PrivilegeDao privilegeDao)
{
this.privilegeDao = privilegeDao;
}


public String getAddDocPrivilegeName()
{
return addDocPrivilegeName;
}


public void setAddDocPrivilegeName(String addDocPrivilegeName)
{
this.addDocPrivilegeName = addDocPrivilegeName;
}


public String getDeleteDocPrivilegeName()
{
return deleteDocPrivilegeName;
}


public void setDeleteDocPrivilegeName(String deleteDocPrivilegeName)
{
this.deleteDocPrivilegeName = deleteDocPrivilegeName;
}


public String getUpdateDocPrivilegeName()
{
return updateDocPrivilegeName;
}


public void setUpdateDocPrivilegeName(String updateDocPrivilegeName)
{
this.updateDocPrivilegeName = updateDocPrivilegeName;
}
}
起輔助作用的PrivilegeNotEnoughException類,因為不想改變接口(或不能改變接口)故把異常類型定位運行期異常:
package com.heyang.aopstyle.exception;


/** *//**
* 權限不足異常
* @author: 何楊(heyang78@gmail.com)
* @date: 2009-1-2-下午04:02:50
*/

public class PrivilegeNotEnoughException extends RuntimeException
{
private static final long serialVersionUID = -6594794529337011115L;


public PrivilegeNotEnoughException(String msg)
{
super(msg);
}
}
最終配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<!-- 數據源 -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName"
value="org.gjt.mm.mysql.Driver">
</property>
<property name="url" value="jdbc:mysql://127.0.0.1/test">
</property>
<property name="username" value="root"></property>
<property name="password" value="hy"></property>
</bean>

<!-- 用于訪問數據庫的jdbcTemplate -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource">
<ref bean="dataSource" />
</property>
</bean>
<!-- 用于訪問數據庫的PrivilegeTB表,按權限名取出權限值的數據庫訪問類 -->
<bean id="privilegeDao"
class="com.heyang.traditional2.dao.PrivilegeDao">
<property name="jdbcTemplate">
<ref bean="jdbcTemplate" />
</property>
</bean>

<!-- 用于文件處理的IDocService實現類DocService -->
<bean id="docService" class="com.heyang.aopstyle.service.DocService"/>
<!-- 在執行docService的實際方法前進行權限檢查 -->
<bean id="privilegeService" class="com.heyang.aopstyle.service.PrivilegeService">
<property name="privilegeDao">
<ref bean="privilegeDao" />
</property>
<property name="addDocPrivilegeName" value="addDocPrivilege" />
<property name="deleteDocPrivilegeName" value="deleteDocPrivilege" />
<property name="updateDocPrivilegeName" value="updateDocPrivilege" />
</bean>
<!-- docService的代理對象 -->
<bean id="docServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>com.heyang.traditonal.service.IDocService</value>
</property>
<property name="interceptorNames">
<list>
<value>privilegeService</value>
</list>
</property>
<property name="target">
<ref bean="docService"/>
</property>
</bean>
</beans>
業務處理模擬過程:
package com.heyang.aopstyle;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.heyang.aopstyle.exception.PrivilegeNotEnoughException;
import com.heyang.traditonal.domain.Doc;
import com.heyang.traditonal.domain.User;
import com.heyang.traditonal.service.IDocService;



public class Main
{

public static void main(String[] args)
{
ApplicationContext ctx = new ClassPathXmlApplicationContext("aopCtx.xml");
IDocService docService=(IDocService)ctx.getBean("docServiceProxy");
Doc doc=new Doc("論美國次貸危機的產生及影響");
User user=new User("中科院經濟所研究員",50);
// 用戶向系統添加文章

try
{
docService.add(doc, user);
}

catch(PrivilegeNotEnoughException ex)
{
System.out.println(ex.getMessage());
}
// 用戶向系統更新文章

try
{
doc.setName("論美國次貸危機的產生及影響和我國應該采取的應對措施");
docService.update(doc, user);
}

catch(PrivilegeNotEnoughException ex)
{
System.out.println(ex.getMessage());
}
// 用戶從系統撒刪除文章

try
{
docService.delete(doc, user);
}

catch(PrivilegeNotEnoughException ex)
{
System.out.println(ex.getMessage());
}
}
}

輸出:
2009-01-02 16:26:53,406 DEBUG [main] (DriverManagerDataSource.java:289) - Creating new JDBC Connection to [jdbc:mysql://127.0.0.1/test]
2009-01-02 16:26:56,578 DEBUG [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource
將文件 名=論美國次貸危機的產生及影響交由dao處理(存入數據庫)
2009-01-02 16:26:56,593 DEBUG [main] (JdbcTemplate.java:382) - Executing SQL query [select value from PrivilegeTB where name='updateDocPrivilege' ]
2009-01-02 16:26:56,593 DEBUG [main] (DataSourceUtils.java:112) - Fetching JDBC Connection from DataSource
2009-01-02 16:26:56,593 DEBUG [main] (DriverManagerDataSource.java:289) - Creating new JDBC Connection to [jdbc:mysql://127.0.0.1/test]
2009-01-02 16:26:56,640 DEBUG [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource
將文件 名=論美國次貸危機的產生及影響和我國應該采取的應對措施交由dao處理(更新數據庫中對應的記錄)
2009-01-02 16:26:56,640 DEBUG [main] (JdbcTemplate.java:382) - Executing SQL query [select value from PrivilegeTB where name='deleteDocPrivilege' ]
2009-01-02 16:26:56,640 DEBUG [main] (DataSourceUtils.java:112) - Fetching JDBC Connection from DataSource
2009-01-02 16:26:56,640 DEBUG [main] (DriverManagerDataSource.java:289) - Creating new JDBC Connection to [jdbc:mysql://127.0.0.1/test]
2009-01-02 16:26:56,703 DEBUG [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource
2009-01-02 16:26:56,859 DEBUG [main] (JdbcTemplate.java:382) - Executing SQL query [select value from PrivilegeTB where name='deleteDocPrivilege' ]
2009-01-02 16:26:56,859 DEBUG [main] (DataSourceUtils.java:112) - Fetching JDBC Connection from DataSource
2009-01-02 16:26:56,859 DEBUG [main] (DriverManagerDataSource.java:289) - Creating new JDBC Connection to [jdbc:mysql://127.0.0.1/test]
2009-01-02 16:26:56,953 DEBUG [main] (DataSourceUtils.java:312) - Returning JDBC Connection to DataSource
用戶權限必須達到60才能執行刪除操作

例程下載:
http://www.tkk7.com/Files/heyang/AOPPrivilegeSample20090102170039.rar
需要自行載入的包為:
commons-logging-1.0.4.jar,log4j-1.2.14.jar,spring.jar,mysql-connector-java-5.0.6-bin.jar