在 spring 中阻止来自控制器的 Dao 调用

Prevent dao call from controller in spring

我试图避免来自控制器的 dao 调用 class。如果调用是从服务包完成的,那么 dao 调用应该是成功的,否则我将抛出异常。我不想在dao的每个方法中都写这个逻辑class所以打算用aspectj来拦截dao的调用。我怎样才能阻止控制器使用 dao 并仅允许它使用 class 服务。我可以使用任何其他 api/approach 吗?任何建议

package com;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class DaoAspect {

    @Before(value = "execution(* com.dao.*.*(..))")
    public void beforeAdvice( JoinPoint joinPoint) {
        // here I want to know the caller package/class
        // if its com.service allow ,if com.controller reject

    }
}

免责声明:我不是 Spring 用户。也许有一种更简单的机载方法可以通过拦截器、Spring 安全性或其他方式实现这一点。但是你要求一个AOP解决方案。

我们首先要区分

  • AspectJ(快速、高效、无代理、更强大)和
  • Spring AOP(基于代理,委托模式,"AOP light",仅方法拦截)。

使用 @DeclareError

对调用代码进行编译时检查

当使用 AspectJ 并使用 AspectJ 编译器重新编译遗留库时(使用 AOP 增强功能直接替换 Java 编译器),您可以使用 @DeclareError 以使编译失败,如果发现来自错误 class 或包模式的调用。这样你就不需要任何昂贵的运行时检查、反射或其他技巧。对于您的 Maven 构建,您可以使用 AspectJ Maven 插件。有关 @DeclareError:

的更多信息,请在此处查看我的回答

这是我的建议:在构建时检测无效调用,修复代码以使其在运行时编译和无忧。

运行-time 检查使用 AspectJ 加载时编织 (LTW) 和 call() 切入点

如果您不想使用 AspectJ 编译器(即使您将问题标记为 aspectj 而不是 spring-aop ) 或者对调用代码没有编译时影响,您仍然可以使用 Spring 中的 AspectJ load-time weaving (LTW)。 Spring 如果你想避免为了分析调用堆栈以找到调用者而创建异常,AOP 绝对是不够的。相反,在完整的 AspectJ 中有一个名为 call() 的切入点,它在 Spring AOP 中不可用。您可以通过 LTW 编织到调用代码中,然后使用 EnclosingStaticPart 来查找调用者。唯一需要注意的是,在 Spring 中,您可能会使用代理而不是直接调用,这可能会通过间接方式弄乱调用者,但您可以尝试一下。

这里是 MCVE 的普通 Java + AspectJ(不涉及 Spring):

DAO、服务、控制器:

package com.dao.ddd;

public class MyDao {
  public void doSomething() {
    System.out.println("Doing something in DAO");
  }
}
package com.service.sss;

import com.dao.ddd.MyDao;

public class MyService {
  public void doSomething() {
    System.out.println("Doing something in service");
    new MyDao().doSomething();
  }
}
package com.controller.ccc;

import com.dao.ddd.MyDao;

public class MyController {
  public void doSomething() {
    System.out.println("Doing something in controller");
    new MyDao().doSomething();
  }
}
package de.scrum_master.app;

import com.controller.ccc.MyController;
import com.service.sss.MyService;

public class Application {
  public static void main(String[] args) {
    // Allowed
    new MyService().doSomething();
    // Forbidden
    new MyController().doSomething();
  }
}

驱动申请:

package de.scrum_master.app;

import com.controller.ccc.MyController;
import com.service.sss.MyService;

public class Application {
  public static void main(String[] args) {
    // Allowed
    new MyService().doSomething();
    // Forbidden
    new MyController().doSomething();
  }
}

没有方面的控制台日志:

Doing something in service
Doing something in DAO
Doing something in controller
Doing something in DAO

实际上不应打印列表行,因为禁止从控制器调用 DAO。

看点:

package de.scrum_master.aspect;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.JoinPoint.EnclosingStaticPart;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class ContractEnforcerAspect {
  @Before("call(* com.dao..*(..))")
  public void beforeAdvice(JoinPoint joinPoint, EnclosingStaticPart enclosingStaticPart) {
    System.out.println("  Callee = " + joinPoint.getSignature());
    System.out.println("  Caller = " + enclosingStaticPart.getSignature());
    if (enclosingStaticPart.getSignature().getDeclaringType().getPackageName().startsWith("com.controller"))
      throw new RuntimeException("DAO must not be called from controller");
  }
}

具有方面的控制台日志:

Doing something in service
  Callee = void com.dao.ddd.MyDao.doSomething()
  Caller = void com.service.sss.MyService.doSomething()
Doing something in DAO
Doing something in controller
  Callee = void com.dao.ddd.MyDao.doSomething()
  Caller = void com.controller.ccc.MyController.doSomething()
Exception in thread "main" java.lang.RuntimeException: DAO must not be called from controller
    at de.scrum_master.aspect.ContractEnforcerAspect.beforeAdvice(ContractEnforcerAspect.aj:17)
    at com.controller.ccc.MyController.doSomething(MyController.java:8)
    at de.scrum_master.app.Application.main(Application.java:11)