2

I am trying to avoid dao call from controller class . If the call is done from service package then dao call should be succesfull else I will throw an exception . I dont want to write this logic in each method of dao class so planned to use aspectj to intercept the dao call . How can I prevent dao from controller and allow it from service class only . Shall I use any other api/approach . Any suggetion

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

    }
}
Roberto Pegoraro
  • 1,313
  • 2
  • 16
  • 31
Ashish Banker
  • 2,243
  • 3
  • 20
  • 21
  • There are multiple solutions to this: static code analysis (e.g. a SonarQube custom rule), a build-system plugin (e.g. for Maven). Using a runtime-evaluated approach imposes a (subjectively) unnecessary performance penalty on something that should be handled at compile / review time (and AspectJ is quite a heavy weight itself). Why do you want to protect yourself this way? Are you exposing your DAO as some sort of library? Some more context would help – roookeee Sep 12 '19 at 20:41
  • Nope I am working on legacy project . To ensure the best practices i want to do this , so that any call to dao from controller will throw an exception . it must go through service – Ashish Banker Sep 12 '19 at 20:45
  • I would recommend you not do this (as outlined above) but [this](https://stackoverflow.com/a/29459753/4934324) answer seems like what you need – roookeee Sep 12 '19 at 20:49
  • @roookeee, how is AspectJ with a 120K runtime a heavy weight? – kriegaex Sep 20 '19 at 02:23
  • @kriegaex I was not talking about code size but complexity by the inherit amount of possibilities AspectJ brings to the table. "Solving" a problem at runtime via AspetJ that should fail at compile time in a project that elsewise doesn't make use of AspectJ seems like buying a top of the line construction toolkit to turn on the light - that's what I was referring to with the "heavy weight", I could've been more clear on my part. Don't get me wrong AspectJ ist a great library and tool I wouldn't want to miss :) – roookeee Sep 20 '19 at 07:50
  • 2
    Which is why in my answer I recommended to use the compile-time approach via AspectJ's `@DeclareError` (or `declare error` in native syntax). I think your suggested SonarQube custom rule might be heavy weight too if they don't use that tool yet. Furthermore, compilation failure is as early as it gets, IMO preferable to static code analysis later. It would also make local developer builds fail, not just the ones running SonarQube on an integration system. Of course you can run SQ locally, too, but that is really heavy weight and slows down builds. – kriegaex Sep 20 '19 at 10:16
  • You are right, i didnt consider your answer below when responding to your question. Its a great compile time solution, didnt even know AspectJ was capable of that :) – roookeee Sep 20 '19 at 11:03

1 Answers1

3

Disclaimer: I am not a Spring user. Maybe there is an easier on-board means to achieve this via interceptors, Spring Security or what have you. But you asked for an AOP solution.

We first have to differentiate between

  • AspectJ (fast, efficient, no proxies, more powerful) and
  • Spring AOP (proxy-based, delegation pattern, "AOP light", only method interception).

Compile-time checks on calling code with @DeclareError

When using AspectJ and just recompiling your legacy library with the AspectJ compiler (drop-in replacement for Java compiler with AOP enhancements), you can use @DeclareError in order to make compilation fail if a call from the wrong class or package pattern(s) is found. This way you would not need any expensive runtime checks, reflection or other tricks. For your Maven build you can use AspectJ Maven plugin. See my answers here for further information about @DeclareError:

This is what I recommend: Detect the invalid calls at build time, fix the code in order to make it compile and be sorrow-free during runtime.

Run-time checks using AspectJ load-time weaving (LTW) with call() pointcut

If however you either don't want to use the AspectJ compiler (even though you tagged the question aspectj and not spring-aop) or have no compile-time influence on the calling code, you can still use AspectJ load-time weaving (LTW) from Spring. Spring AOP is definitely not enough if you want to avoid creating Exceptions for the sole purpose of analysing their callstacks in order to find the caller. Instead, in full AspectJ there is a pointcut named call() which is unavailable in Spring AOP. You can weave into the calling code via LTW and then use EnclosingStaticPart in order to find the caller. The only caveat is that in Spring you might use proxies instead of direct calls, which might mess up the caller by indirection, but you can give it a try.

Here is an MCVE in plain Java + AspectJ (no Spring involved):

DAO, service, controller:

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();
  }
}

Driver application:

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();
  }
}

Console log without aspect:

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

Actually the list line should not be printed because a call from the controller to the DAO is forbidden.

Aspect:

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");
  }
}

Console log with aspect:

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)
kriegaex
  • 63,017
  • 15
  • 111
  • 202