作者sbrhsieh (十年一梦)
看板java
标题Re: [问题] spring, 与架构问题
时间Tue May 6 02:15:04 2014
※ 引述《ghchen (Seele)》之铭言:
: 主要问题:
: 如何禁止从 Controller 直接呼叫 Repository
: 问题描述:
: 我使用 spring 来开发 webapp
: 采用三层式架构
: Controller -> Service -> Repository
: // Repository 是 Spring Data 使用的术语,可以想像成 DAO
: 原本打算把 jta, auditing log 加在 Service layer 上
: 所以我不希望其他开发人员从 Controller 这里直接呼叫 Repository
: 但是其他开发人员不可能被我控制
: 他们可以随意的在 controller 里面任意 autowired repository
: 接下来就随便他们搞了
: 後来我加了一个 AOP 来限制 repository 的呼叫
: 如果是从 service package 呼叫的就放行,否则抛出例外
: @Aspect
: public class ModelAdvice {
: private Pattern pattern = Pattern.compile("^demo\\.services\\..*");
: @Before("execution(* demo.repositories..*Repository.*(..))")
: public void protectRepositories(JoinPoint joinPoint) {
: StackTraceElement[] stElements =
: Thread.currentThread().getStackTrace();
: for (StackTraceElement element : stElements) {
: Matcher matcher = pattern.matcher(element.getClassName());
: if (matcher.matches()) {
: return;
: }
: }
: throw new RuntimeException("security violation!");
: }
: }
: 虽然可以动,但是觉得不太漂亮,而且可能会很慢
使用 AOP 来作 policy enforcement 是不错的选择,但是应该就不要在 runtime
再去分析 runtime 时的资讯。
试着透过复合 within pointcut 直接把不是在 service classes 范围下,去 call
repository methods 的部份织上抛出 exception 的码,等於是在 weaving time 就
直接把不合规定的部份找出来,没有额外 runtime 成本。
另外,需要考虑使用 reflection 去调用 repository classes 的功能,这部份也
可以透过 AspectJ 去拦截,但是就确实需要在 runtime 去判断是否有符合你的
policy。
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 218.173.236.72
※ 文章网址: http://webptt.com/cn.aspx?n=bbs/java/M.1399313711.A.CA1.html
1F:推 ghchen:感谢S大的意见,我会朝你说的方向去试试看 05/06 09:19
2F:推 dream1124:请问第二段可以举更具体的例子吗? 很有意思 05/06 19:08
3F:推 swpoker:但只能从runtime来侦测吧~顶多只在test case那边测测 05/08 11:43
第二段的部分在实作上应该还挺直觉的,所以我才没有直接给出范例。
如果要给个具体的例子,那就拿原帖中应用为例。假设所有 Repository 相关的
classes 都在 package demo.repositories 之下(含 subpackage);所有 Service
相关的 classes 都在 package demo.services 之下(含 subpackage)。
不能直接碰 repository classes 的原则是指,除了 repository classes 与
services classes 之外的所有 class 都不能够直接去调用 repository classes
的任何 method,也不能存取任何 repository classes 的 field。
要强制遵守这个原则大致上可以这麽做(假设违例处抛出一个带有提示讯息的
runtime exception 即可达到成效):
public aspect DontTouchModel {
pointcut manipulateRepository() : call(* demo.repositories..*.*(..));
pointcut readFieldOfRepository() : get(* demo.repositories..*.*);
pointcut writeFieldOfRepository() : set(* demo.repositories..*.*);
before() : manipulateRepository()
&& !within(demo.repositories..*) && !within(demo.services..*) {
throw new RuntimeException(
"Please don't touch repositories classes directly.");
}
before() : readFieldOfRepository()
&& !within(demo.repositories..*) && !within(demo.services..*) {
throw new RuntimeException(
"Please don't touch repositories classes directly.");
}
before() : writeFieldOfRepository()
&& !within(demo.repositories..*) && !within(demo.services..*) {
throw new RuntimeException(
"Please don't touch repositories classes directly.");
}
}
把整个 project 的 classes 拿来与 DontTouchModel 织在一起後,非 repository/
service classes 者不论如何拿到 repository classes 的 object/class,只要
他有把 reference casting 成 repository classes/interface 来使用,就会被
揪出来(除非他透过 reflection 去使用这些 reference,这就是第三段的部分)。
这种做法对於那些符合原则的 statement 不会添加 runtime 成本(因为这些码完全
没有被加工),只有不符合原则的部分有额外成本(其实也没有啦,你根本就要他挂掉
了 :D)。
※ 编辑: sbrhsieh (1.172.225.226), 05/09/2014 17:14:33
4F:推 ghchen:再次感谢S大的解说!! 05/10 09:46