Search code examples
genericsproxyjhipsterspring-aop

$Proxy293 cannot be cast to ...service.impl.ext.MyService in Spring AOP using JDK Dynamic Proxy


I am attempting to use a JDK dynamic proxy in addition to Spring AOP with Spring-Boot to cut in between a method provided for me by JHipster so I can mask/block data on a page all from the backend. A couple questions. Why is my proxy not casting correctly and how may I get the class represented by PageImpl(a runtime type)? If Page is my interface, PageImpl is my underlying concrete impl, how to get the type of PageImpl? Thanks for your future input. Here's the code:

First, proxy implementation.

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyInterceptor implements InvocationHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyInterceptor.class);
    
    private final Object target;

    public MyInterceptor(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target, args);
    }

    @SuppressWarnings("unchecked")
    public static <T> T getProxy(T target, Class interfaceType) {
        return (T) Proxy.newProxyInstance(
                interfaceType.getClassLoader(),
            new Class[] { interfaceType },
            new MyInterceptor(target));
    }
}

Now, my aspect.

public class MyAspect {

    private final Logger LOGGER = LoggerFactory.getLogger(MyAspect.class);

    @Autowired
    private MyService myService; 
    
    private final Environment env;

    public MyAspect(Environment env) {
        this.env = env;
    }
    
    @AfterReturning(value = "execution(* com.some.package.service..findAll(*))", returning="page")
    private void entityResourceFindAll(JoinPoint joinPoint, Object page) {
        LOGGER.debug("entityResourceFindaAll");

        // The concrete impl is PageIml<T>. How can I get the type
        // that PageIml represents, such as MyAttachment?

        Page<?> entityPage = (Page<?>)page;
        LOGGER.debug("simple name: {}", entityPage.getClass().getSimpleName());
        LOGGER.debug("joinPoint typeName {}", joinPoint.getSignature().getDeclaringTypeName());
        MyService proxy = MyInterceptor.getProxy(myService, MyServiceImpl.class); 

        // error shows here: .$Proxy293 cannot be cast to ...service.impl.ext.MyService

        LOGGER.debug("proxy getClass: {}", proxy.getClass());
        LOGGER.debug("proxy typeName: {}", proxy.getClass().getTypeName());
        proxy.determineMaskOrBlockPaginate(entityPage);
        
    }
}

Then, MyService

@Service
public class MyService<T> implements MyServiceImpl{

    private final Logger LOGGER = LoggerFactory.getLogger(MyService.class);

    public void determineMaskOrBlockPaginate(Page<?> page) {
        /*
         * 
         * Get user authorization in determining to mask/block or both
         * come to think of it, I don't think you can do both 
         * block wipes data from UI
         */
        
        if(blocked) {
            testYourAPIBlockPaginate(page);
        }else if(masked) {
            testYourAPIBlockPaginate(page);
        }
    }
}

Then, interface to represent the proxy on my object(MyService).

import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Page;

public interface MyServiceImpl {
    
    public List<Object> testYourAPIMaskPaginate(Page<?> page);
    public List<Object> testYourAPIBlockPaginate(Page<?> page);
    public void testYourAPIBlockPaginate(Page<?> page);
}

The entity's Service implementation with the JHipster provided methods. I am obtaining Page< SiteAttachments > which will later be Page< T >.

@Service
@Transactional
public class SiteAttachmentsServiceImpl implements SiteAttachmentsService {

    private final Logger log = LoggerFactory.getLogger(SiteAttachmentsServiceImpl.class);

    private final SiteAttachmentsRepository siteAttachmentsRepository;

    public SiteAttachmentsServiceImpl(SiteAttachmentsRepository siteAttachmentsRepository) {
        this.siteAttachmentsRepository = siteAttachmentsRepository;
    }

    /**
     * Get all the siteAttachments.
     *
     * @param pageable the pagination information.
     * @return the list of entities.
     */
    @Override
    @Transactional(readOnly = true)
    public Page<SiteAttachments> findAll(Pageable pageable) {
        log.debug("Request to get all SiteAttachments");
        return siteAttachmentsRepository.findAll(pageable);
    }
}

Last, my Resource class or the front-end door.

@RestController
@RequestMapping("/api")
public class SiteAttachmentsResource {

    private final Logger log = LoggerFactory.getLogger(SiteAttachmentsResource.class);

    @Value("${jhipster.clientApp.name}")
    private String applicationName;

    private final SiteAttachmentsService siteAttachmentsService;
    
    @Autowired
    private MyService<?> myService;

    public SiteAttachmentsResource(SiteAttachmentsService siteAttachmentsService) {
        this.siteAttachmentsService = siteAttachmentsService;
    }

    /**
     * {@code GET  /site-attachments} : get all the siteAttachments.
     * @param pageable the pagination information.
     * @return the {@link ResponseEntity} with status {@code 200 (OK)} and the list of siteAttachments in body.
     */
    @GetMapping("/site-attachments")
    public ResponseEntity<List<SiteAttachments>> getAllSiteAttachments(Pageable pageable) {
        log.debug("REST request to get a page of SiteAttachments");

         // I am intercepting here (getting Page<SiteAttachments>)
         // to then call the below commented out code (later overwritten by JHipster)
         // that will be called in the entityResourceFindAll() in MyAspect.

        Page<SiteAttachments> page = siteAttachmentsService.findAll(pageable); 

//        List<Object> maskedSiteAttachmentList = gsamService.testYourAPIMaskPaginate(page);      
//        List<Object> blockedSiteAttachmentsList = gsamService.testYourAPIBlockPaginate(page);    
//        page = new PageImpl(blockedSiteAttachmentsList);

        HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(ServletUriComponentsBuilder.fromCurrentRequest(), page);
        log.debug("getAllSiteAttachments------------------------------------------------");
        return ResponseEntity.ok().headers(headers).body(page.getContent());
    }
}

Solution

  • There are several strange things about your code, for example:

    • Why do you call your interface MyServiceImpl as if it was the implementation, but then call the actual implementation MyService? That is horrible naming. Should it not be the other way around?

    • Your interface does not compile because both testYourAPIBlockPaginate methods have the same type erasure. Did you not try to compile your code before posting it here?

    • The way you use generics is also quite strange, but not the root cause of the problem here, so I will refrain from commenting on it in detail.

    You are also confusing interface and implementation in your own code because this line in your aspect:

    MyService proxy = MyInterceptor.getProxy(myService, MyServiceImpl.class);
    

    should actually be (according to your upside-down class naming scheme):

    MyServiceImpl proxy = MyInterceptor.getProxy(myService, MyServiceImpl.class);
    

    This is because MyServiceImpl is the name of the interface type in your example.

    Let us rename the two classes to reflect what they are actually doing, shall we? Then this example works (no Spring, no AOP, just concentrating on your problem):

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    import java.util.Arrays;
    import java.util.List;
    
    class Scratch {
      public static void main(String[] args) {
        MyServiceImpl myService = new MyServiceImpl();
        MyService proxy = MyInterceptor.getProxy(myService, MyService.class);
      }
    
      public static class MyInterceptor implements InvocationHandler {
        private final Object target;
    
        public MyInterceptor(Object target) {
          this.target = target;
        }
    
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          return method.invoke(target, args);
        }
    
        @SuppressWarnings("unchecked")
        public static <T> T getProxy(T target, Class interfaceType) {
          return (T) Proxy.newProxyInstance(
            interfaceType.getClassLoader(),
            new Class[] { interfaceType },
            new MyInterceptor(target)
          );
        }
      }
    
      public interface MyService {
        List<Object> doSomething(String text);
    
        List<Object> doSomethingElse(String text);
      }
    
      public static class MyServiceImpl<T> implements MyService {
        @Override
        public List<Object> doSomething(String text) {
          return Arrays.asList("doSomething", text);
        }
    
        @Override
        public List<Object> doSomethingElse(String text) {
          return Arrays.asList("doSomethingElse", text);
        }
      }
    
    }
    

    In order to reproduce your error you have to change MyService proxy to MyServiceImpl proxy. This is exactly your problem, it only looks reverse because I renamed the classes to what they should be named.