CDI Interceptor - PullRequest
       104

CDI Interceptor

0 голосов
/ 05 марта 2020

Я работал над проектом, в котором я хотел бы динамически перехватывать вызовы внешних зависимостей и проверять их возврат во время выполнения. Я начал с реализации небольшого доказательства концепции, которое вы можете найти здесь:

https://gitlab.com/connorbutch/mock-manager

По сути, этот проект делает:

Однако ошибка, с которой я сталкиваюсь:

Exception in thread "main" javax.enterprise.inject.CreationException
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at java.base/java.lang.Class.newInstance(Class.java:584)
at org.jboss.weld.security.NewInstanceAction.run(NewInstanceAction.java:33)
//way more logs here

Вот содержимое моих бобов. xml:

<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd" bean-discovery-mode="all" version="2.0">

<interceptors>
    <class>com.connor.mock.IntegrationTestInterceptor</class>
</interceptors>

Вот класс перехватчика:

package com.connor.mock;

import java.lang.reflect.Method;

import javax.inject.Inject;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptor;
import javax.interceptor.InvocationContext;

import com.connor.ws.ExternalDependency;


/**
 * This class holds the interceptors
 * @author connor
 *
 */
@Interceptor
@MockForIntegrationTest
public class IntegrationTestInterceptor {

private final MockManagerExternalDependencyImpl mockManager;

/**
 * Cdi-enabled constructor
 * @param mockManager
 */
@Inject
public IntegrationTestInterceptor(MockManagerExternalDependencyImpl mockManager) {
    this.mockManager = mockManager;
}

/**
 * This method intercepts external calls for integration tests, and passes them to the mock
 * @param context
 * @throws Exception
 */
@AroundInvoke
public Object interceptAndMockExternalCall(InvocationContext context) throws Exception {
    System.out.println("Inside InterceptorHolder.interceptAndMockExternalCall");

    //get what we can from the 
    Class<?> clazz = context.getTarget().getClass();
    String methodName = context.getMethod().getName();
    Object[] argsForMethod = context.getParameters();

    //loop through parameters and create an array of their types for use with reflection later
    Class<?>[] parameterTypes = new Class<?>[argsForMethod.length];
    for(int i = 0; i < argsForMethod.length; ++i) {
        parameterTypes[i] = argsForMethod[i].getClass();
    }       

    //this means that this annotation was used on an incorrect method/class that does not implement our marker interface
    if(!ExternalDependency.class.isAssignableFrom(clazz)) {
        throw new Exception("Please only annotate methods with mockforintegration test interceptor binding if the class implements the marker interface ApiClient");
    }

    //we know this is assignable, so we can cast here
    //get our mock from the singleton mock manager
    @SuppressWarnings("unchecked") //we know this is safe to cast because of the assignable from check above
    Object mockToInvoke = mockManager.getMockedInstance((Class<? extends ExternalDependency>) clazz);

    //get the method to invoke on the mock from the method name and parameters
    Method methodToInvoke = mockToInvoke.getClass().getMethod(methodName, parameterTypes);
    Object returnValue;
    try {
        returnValue = methodToInvoke.invoke(mockToInvoke, argsForMethod);
    }catch(Exception e) {
        //TODO
        //NOTE: break out into separate catch blocks for each type -- original method throws exception, then rethrow
        throw new Exception("There was an error using reflection to invoke method on mock", e);
    }

    //log success here
    System.out.println(returnValue);
    return returnValue;     
    }   
}

Вот класс с используемым перехватчиком:

package com.connor.ws;

import com.connor.mock.MockForIntegrationTest;

public class ExternalDependencyPolicyClientImpl implements ExternalDependency {

/**
 * 
 */
private static final long serialVersionUID = -8845199637628066567L;

/**
 * This is a dummy method
 * @return
 */
@MockForIntegrationTest
public String dummy() {
    return "dummy";
}
}

Вот копия из моего pom:

<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.connor</groupId>
<artifactId>mock-manager</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>This project demonstrates how to use mock manager for integration tests involving outside web services.</description>

<properties>
    <!-- dependency versions, please keep in alphabetical order -->
    <cdi.version>2.0.SP1</cdi.version>
    <inject.version>1</inject.version>
    <interceptor.version>1.2.2</interceptor.version>
    <mockito.version>3.1.0</mockito.version>
    <weld.version>3.1.3.Final</weld.version>

    <!-- use java 8: TODO make use java 14 -->
    <maven.compiler.target>1.8</maven.compiler.target>
    <maven.compiler.source>1.8</maven.compiler.source>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>javax.enterprise</groupId>
            <artifactId>cdi-api</artifactId>
            <version>${cdi.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.inject</groupId>
            <artifactId>javax.inject</artifactId>
            <version>${inject.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.interceptor</groupId>
            <artifactId>javax.interceptor-api</artifactId>
            <version>${interceptor.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <version>${mockito.version}</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.weld.se</groupId>
            <artifactId>weld-se-core</artifactId>
            <version>${weld.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>javax.enterprise</groupId>
        <artifactId>cdi-api</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.inject</groupId>
        <artifactId>javax.inject</artifactId>
    </dependency>
    <dependency>
        <groupId>javax.interceptor</groupId>
        <artifactId>javax.interceptor-api</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mockito</groupId>
        <artifactId>mockito-core</artifactId>
    </dependency>
    <dependency>
        <groupId>org.jboss.weld.se</groupId>
        <artifactId>weld-se-core</artifactId>
    </dependency>
</dependencies>

Любые идеи приветствуются.

PS код работает без перехвата Торс (включая CDI)

1 Ответ

0 голосов
/ 05 марта 2020

Я решил это! Перехватчики не могут использовать cdi (либо конструктор, либо внедрение поля), поэтому я закончил тем, что удалил синглтонную аннотацию cdi в своем классе и заменил ее «традиционным» одноэлементным методом, который я сделал производителем. Затем я могу использовать компонент с помощью cdi или без cdi. Я должен был использовать позже внутри перехватчика.

...