最近我在设计通用权限组件的时候遇到AOP拦截器方面的一个问题,寻求解决方案,先描述一下应用场景:
通用权限组件以AOP的形式“插入”业务系统,AOP的拦截机制有两种:
(A)通过Filter对用户请求的URI进行拦截
(B)通过Interceptor对用户调用的服务接口进行拦截
例子如下:
某业务系统有A、B两大模块,分别提供两大类型的服务,A、B两个模块都包含了若干的服务接口方法:
public interface AService {
public void a1() throws ServiceException;
public void a2() throws ServiceException;
}
public interface BService {
public void b1() throws ServiceException;
public void b2() throws ServiceException;
}
为了实现以Interceptor的方式进行服务接口方法拦截,以实现权限控制,需求在权限系统把每个服务接口方法都进行注册,使其成为受控资源实体:
com.xxx.AService.a1
com.xxx.AService.a2
com.xxx.BService.b1
com.xxx.BService.b2
然后把这些实体分配给角色,例如:
Role1:com.xxx.AService.a1
Role2:com.xxx.AService.a1;com.xxx.AService.b1
问题出现了,请看服务的实现类
public class AServiceImpl implements AService {
private BService bService;
public void a1() thorws ServiceException() {
// Business logic
bService.b1();
// Other business logic
...
}
...
}
由于服务A.a1的某些业务逻辑与服务B.b重复了,合理的设计是,把B作为成员变量注入到A中,并且A以接口的方式使用B(如上述代码片断),但由于实际的需要,某个角色Role1只能访问a1服务,无权访问b1服务,由于使用了AOP的拦截器方式,Role1在访问AService.a1()的时候,拦截器把这个动作拦截下来,判断Role1发现他拥有AService.a1()的调用权,本次拦截通过,Role1顺利的调用了a1()方法,但随即,a1()调用了BService.b1()方法,这时候拦截器再次对Role1进行权限判断,发现Role1并没有b1()的调用权,最终,本次调用被拒绝。但实际上,从用户的角度考虑,用户不应该知道实现细节,他在配置权限的时候,只关心业务,我们不可能要求用户要从技术角度去知道a1本身调用了b1。
上述的情况不常见,因为如果用户拥有a1的使用权,而a1本身又需要b1提供服务,正常的情况下该用户应该同时拥有a1、b1的使用权,但事实可能真的会存在上述的情况,我想到的其中一种办法是:
在AService、BService的“前面”增加一个Facade(门面),权限拦截器拦截这个Facade,而不是拦截直接AService和BService。
其实这样的设计是合理的,虽然为系统增加了一个“层”,但这个“层”刚好把系统抽象的分离为“对外服务接口”和“对内服务接口”了,服务之间的调用,直接使用Service接口,外部访问系统,使用Facade接口,整个系统的设计更加合理和清晰,灵活性也更强。
但同时我又必须考虑一个问题:我设计的是一个通用的权限组件,为了尽可能通用,组件必须能最大限度适应外界的各种情况,而且,通用组件不可能要求外部系统必须要使用这种“Facade Pattern”,如果是这样的话,不但加大了对外部使用者的限制,而且会让外部系统与权限组件形成“循环依赖”,因为权限组件不得不依赖于外部系统提供Facade。
本人对AOP的概念理解或者算是比较清晰,但对于AOP的技术实现(Spring AOP、AspectJ等)并不精通,请教一下各位,有没有更好的解决方案呢?
[该贴被johnnylzb于2008-02-19 17:21修改过]