关于AOP拦截器使用的一个问题,寻求解决方案

08-02-19 johnnylzb
         

最近我在设计通用权限组件的时候遇到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修改过]

         

banq
2008-02-20 18:14

AService需要调用BService中代码,那么就将BService中这些代码独立出来创建一个新类,供Aservice和BService共同调用。

相信这些代码不会一成不变给两个Service调用,可以为AService和BService使用装饰模式做一些改变。

codeslave
2008-02-21 11:49

bang老大的做法跟楼主的做法很相似。

呵呵!其实感觉上楼主的问题不是在于这个,而是那个组件通用性的问题,但个人认为这种通用性的前题都应该有规则,那怕你有更好的解决方案。如果是自已设计的系统,当然可以很好的避开这种冲突的设计,但其他(不属于自已设计理念的系统)使用你的组件出现冲突时,如果使用“Facade Pattern”可以解决,那本身就是一种方案,而且这种规则可以在接受的范围内(个人感觉)。
当然也会存在其他方案,但可能只是一种变相,只是规则不同而矣。

johnnylzb
2008-02-21 16:31

谢谢两位,banq大哥的做法应该跟Refactoring里面的Extract Method和MoveMethod类似,实际上是把两个类公用的逻辑抽取出来(Extract Method),转移到一个第三放的类中(Move Method),这种做法是一种解决方案,正如codeslave大哥说的,应该跟本人的Facade模式类似,其实也是增加了一层。

codeslave也说得有道理,就是要求业务系统设计人员在可接收的范围内通过改善设计而解决上述出现的冲突。而本人的设计理念是:如果业务系统的业务层需要同时供外部和内部使用,提供一个Facade层,在抽象角度上会把系统的外部行为和内部行为进行了清晰的划分。

呵呵,不过,我为公司各个项目组做这个通用权限组件,各个项目组的设计虽然大同小异,但也有不同的设计理念,而且某些项目已经是完成了开发,到了整合权限的阶段,这才是我的头痛之处。

freeren
2009-04-15 11:36

这个权限的问题的确是个头大的问题,本人觉得和一开始的设计也很有关系。如我们的系统一开始就耦合了数据库,比如在报表和列表查询中要加入对数据查询的过滤--数据权限。结果列表查询要通过我们的业务逻辑来处理,而报表很直接就是拼SQL转成EXCEl,这样问题就来了,我们在做功能权限可以利用代理来控制,但是数据权限没办法了,只能和数据库耦合了!