Refactoring and added sql Helper
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
package de.neitzel.core.inject;
|
||||
|
||||
import de.neitzel.core.inject.annotation.Component;
|
||||
import org.reflections.Reflections;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* InjectableComponents scans a package for classes annotated with {@link Component}.
|
||||
* It determines which components can be instantiated and manages type mappings for dependency injection.
|
||||
*/
|
||||
public class InjectableComponentScanner {
|
||||
|
||||
/** Set of all detected @FXMLComponent classes within the given package. */
|
||||
private final Set<Class<?>> fxmlComponents = new HashSet<>();
|
||||
|
||||
/** Set of all superclasses and interfaces that are implemented by multiple @FXMLComponent classes. */
|
||||
private final Set<Class<?>> notUniqueTypes = new HashSet<>();
|
||||
|
||||
/** Map of unique superclasses/interfaces to a single corresponding @FXMLComponent class. */
|
||||
private final Map<Class<?>, Class<?>> uniqueTypeToComponent = new HashMap<>();
|
||||
|
||||
/** Map of instantiable @FXMLComponent classes and their corresponding interfaces/superclasses (if unique). */
|
||||
private final Map<Class<?>, Class<?>> instantiableComponents = new HashMap<>();
|
||||
|
||||
/** List of error messages generated when resolving component instantiability. */
|
||||
private final List<String> errors = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Constructs an InjectableComponents instance and scans the given package for @FXMLComponent classes.
|
||||
*
|
||||
* @param basePackage The base package to scan for @FXMLComponent classes.
|
||||
*/
|
||||
public InjectableComponentScanner(String basePackage) {
|
||||
scanForComponents(basePackage);
|
||||
analyzeComponentTypes();
|
||||
resolveInstantiableComponents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans the specified package for classes annotated with @FXMLComponent.
|
||||
*
|
||||
* @param basePackage The package to scan.
|
||||
*/
|
||||
private void scanForComponents(String basePackage) {
|
||||
Reflections reflections = new Reflections(basePackage);
|
||||
fxmlComponents.addAll(reflections.getTypesAnnotatedWith(Component.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyzes the collected @FXMLComponent classes to determine unique and non-unique superclasses/interfaces.
|
||||
*/
|
||||
private void analyzeComponentTypes() {
|
||||
Map<Class<?>, List<Class<?>>> superTypesMap = new HashMap<>();
|
||||
|
||||
for (Class<?> component : fxmlComponents) {
|
||||
Set<Class<?>> allSuperTypes = getAllSuperTypes(component);
|
||||
|
||||
for (Class<?> superType : allSuperTypes) {
|
||||
superTypesMap.computeIfAbsent(superType, k -> new ArrayList<>()).add(component);
|
||||
}
|
||||
}
|
||||
|
||||
for (Map.Entry<Class<?>, List<Class<?>>> entry : superTypesMap.entrySet()) {
|
||||
Class<?> superType = entry.getKey();
|
||||
List<Class<?>> implementations = entry.getValue();
|
||||
|
||||
if (implementations.size() > 1) {
|
||||
notUniqueTypes.add(superType);
|
||||
} else {
|
||||
uniqueTypeToComponent.put(superType, implementations.get(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which @FXMLComponent classes can be instantiated based on known dependencies.
|
||||
* It registers valid components and collects errors for components that cannot be instantiated.
|
||||
*/
|
||||
private void resolveInstantiableComponents() {
|
||||
Set<Class<?>> resolved = new HashSet<>();
|
||||
Set<Class<?>> unresolved = new HashSet<>(fxmlComponents);
|
||||
Map<Class<?>, Class<?>> knownTypes = new HashMap<>();
|
||||
|
||||
boolean progress;
|
||||
do {
|
||||
progress = false;
|
||||
Iterator<Class<?>> iterator = unresolved.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Class<?> component = iterator.next();
|
||||
if (canInstantiate(component, knownTypes.keySet())) {
|
||||
resolved.add(component);
|
||||
registerComponentWithSuperTypes(component, knownTypes);
|
||||
iterator.remove();
|
||||
progress = true;
|
||||
}
|
||||
}
|
||||
} while (progress);
|
||||
|
||||
instantiableComponents.putAll(knownTypes);
|
||||
|
||||
if (!unresolved.isEmpty()) {
|
||||
collectInstantiationErrors(unresolved);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a component along with its unique superclasses and interfaces in the known types map.
|
||||
*
|
||||
* @param component The component class.
|
||||
* @param knownTypes The map of known instantiable types.
|
||||
*/
|
||||
private void registerComponentWithSuperTypes(Class<?> component, Map<Class<?>, Class<?>> knownTypes) {
|
||||
knownTypes.put(component, component);
|
||||
|
||||
for (Class<?> superType : getAllSuperTypes(component)) {
|
||||
if (!notUniqueTypes.contains(superType)) {
|
||||
knownTypes.put(superType, component);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given @FXMLComponent class can be instantiated based on known dependencies.
|
||||
*
|
||||
* @param component The component class to check.
|
||||
* @param knownTypes Set of known instantiable types.
|
||||
* @return {@code true} if the component can be instantiated, otherwise {@code false}.
|
||||
*/
|
||||
private boolean canInstantiate(Class<?> component, Set<Class<?>> knownTypes) {
|
||||
for (Constructor<?> constructor : component.getConstructors()) {
|
||||
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||
if (paramTypes.length == 0 || Arrays.stream(paramTypes).allMatch(knownTypes::contains)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects error messages for components that cannot be instantiated and adds them to the error list.
|
||||
*
|
||||
* @param unresolved Set of components that could not be instantiated.
|
||||
*/
|
||||
private void collectInstantiationErrors(Set<Class<?>> unresolved) {
|
||||
for (Class<?> component : unresolved) {
|
||||
StringBuilder errorMsg = new StringBuilder("Component cannot be instantiated: " + component.getName());
|
||||
|
||||
boolean possibleWithUniqueTypes = false;
|
||||
|
||||
for (Constructor<?> constructor : component.getConstructors()) {
|
||||
Class<?>[] paramTypes = constructor.getParameterTypes();
|
||||
List<Class<?>> problematicTypes = Arrays.stream(paramTypes)
|
||||
.filter(t -> !instantiableComponents.containsKey(t) && !notUniqueTypes.contains(t))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (problematicTypes.isEmpty()) {
|
||||
possibleWithUniqueTypes = true;
|
||||
} else {
|
||||
errorMsg.append("\n ➤ Requires unknown types: ").append(problematicTypes);
|
||||
}
|
||||
}
|
||||
|
||||
if (possibleWithUniqueTypes) {
|
||||
errorMsg.append("\n ➤ Could be instantiated if multiple implementations of ")
|
||||
.append("interfaces/superclasses were resolved uniquely: ")
|
||||
.append(getConflictingTypes(component));
|
||||
}
|
||||
|
||||
errors.add(errorMsg.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma-separated list of conflicting types that prevent instantiation.
|
||||
*
|
||||
* @param component The component class.
|
||||
* @return A string listing the conflicting types.
|
||||
*/
|
||||
private String getConflictingTypes(Class<?> component) {
|
||||
return Arrays.stream(component.getConstructors())
|
||||
.flatMap(constructor -> Arrays.stream(constructor.getParameterTypes()))
|
||||
.filter(notUniqueTypes::contains)
|
||||
.map(Class::getName)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all superclasses and interfaces of a given class.
|
||||
*
|
||||
* @param clazz The class to analyze.
|
||||
* @return A set of all superclasses and interfaces of the given class.
|
||||
*/
|
||||
private Set<Class<?>> getAllSuperTypes(Class<?> clazz) {
|
||||
Set<Class<?>> result = new HashSet<>();
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
|
||||
while (superClass != null && superClass != Object.class) {
|
||||
result.add(superClass);
|
||||
superClass = superClass.getSuperclass();
|
||||
}
|
||||
|
||||
result.addAll(Arrays.asList(clazz.getInterfaces()));
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a map of instantiable @FXMLComponent classes and their associated interfaces/superclasses.
|
||||
*
|
||||
* @return A map of instantiable components.
|
||||
*/
|
||||
public Map<Class<?>, Class<?>> getInstantiableComponents() {
|
||||
return instantiableComponents;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the set of non-unique types (superclasses/interfaces with multiple implementations).
|
||||
*
|
||||
* @return A set of non-unique types.
|
||||
*/
|
||||
public Set<Class<?>> getNotUniqueTypes() {
|
||||
return notUniqueTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the map of unique types to corresponding @FXMLComponent implementations.
|
||||
*
|
||||
* @return A map of unique types.
|
||||
*/
|
||||
public Map<Class<?>, Class<?>> getUniqueTypeToComponent() {
|
||||
return uniqueTypeToComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of errors encountered during instantiation resolution.
|
||||
*
|
||||
* @return A list of error messages.
|
||||
*/
|
||||
public List<String> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package de.neitzel.core.inject.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Component {
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package de.neitzel.core.inject.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
public @interface Config {
|
||||
String value() default "";
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package de.neitzel.core.sql;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
/**
|
||||
* Helper class to build and execute parameterized SQL queries with factory-based result mapping.
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class Query<T> {
|
||||
|
||||
/**
|
||||
* An Integer Factory that can be used to read Integer Values of a ResultSet that only has Integers (e.g. a min oder max query)
|
||||
*/
|
||||
public static final Function<ResultSet, Integer> INTEGER_FACTORY = rs -> {
|
||||
try {
|
||||
return rs.getInt(1);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
};
|
||||
|
||||
private final Connection connection;
|
||||
private final List<ParameterSetter> parameterSetters = new ArrayList<>();
|
||||
private Function<ResultSet, T> factory;
|
||||
private String queryText;
|
||||
|
||||
public static void execute(final Connection connection, final String query) throws SQLException {
|
||||
new Query<Object>(connection)
|
||||
.setQueryText(query)
|
||||
.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* sets the query.
|
||||
*/
|
||||
public Query<T> setQueryText(final String queryText) {
|
||||
this.queryText = queryText;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the query content from a resource URL.
|
||||
*/
|
||||
public Query<T> setQueryResource(final String resourcePath) {
|
||||
log.info("loading resource: {}", resourcePath);
|
||||
try (Scanner scanner = new Scanner(Objects.requireNonNull(getClass().getResourceAsStream(resourcePath)),
|
||||
StandardCharsets.UTF_8)) {
|
||||
this.queryText = scanner.useDelimiter("\\A").next();
|
||||
}
|
||||
log.info("query text: {}", this.queryText);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces placeholders in the query.
|
||||
*/
|
||||
public Query<T> replace(final String placeholder, final String value) {
|
||||
this.queryText = this.queryText.replace(placeholder, value);
|
||||
log.info("query text: {}", this.queryText);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another parameter setter
|
||||
*
|
||||
* @param index Index of parameter in prepared statement
|
||||
* @param value Value to set.
|
||||
* @param setter Setter to use on prepared statement.
|
||||
* @param <X> Type of the Parameter.
|
||||
* @return The Query.
|
||||
*/
|
||||
public <X> Query<T> addParameter(final int index, final X value, final TriSqlFunction<PreparedStatement, Integer, X> setter) {
|
||||
parameterSetters.add(ps -> setter.apply(ps, index, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a ParameterSetter/
|
||||
*
|
||||
* @param setter Setter for a parameter.
|
||||
* @return the Qyery.
|
||||
*/
|
||||
public Query<T> addParameter(ParameterSetter setter) {
|
||||
parameterSetters.add(setter);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a result factory by name.
|
||||
*/
|
||||
public Query<T> factory(final Function<ResultSet, T> factory) {
|
||||
this.factory = factory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the query and returns a stream of results. This Stream must be closed so that the underlying ResultSet is closed.
|
||||
*/
|
||||
private Stream<T> stream() throws SQLException {
|
||||
ResultSet rs = createPreparedStatement().executeQuery();
|
||||
TrimmingResultSet trimmingResultSet = new TrimmingResultSet(rs);
|
||||
return streamFromResultSet(trimmingResultSet, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a PreparedStatement and applies all Parameter.
|
||||
*
|
||||
* @return the created PreparedStatement
|
||||
* @throws SQLException thrown if the PreparedStatement could not be created.
|
||||
*/
|
||||
private PreparedStatement createPreparedStatement() throws SQLException {
|
||||
PreparedStatement ps = connection.prepareStatement(queryText);
|
||||
for (ParameterSetter setter : parameterSetters) {
|
||||
setter.apply(ps);
|
||||
}
|
||||
return ps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes a query wich has no result.
|
||||
*
|
||||
* @throws SQLException Thrown when query cannot be executed.
|
||||
*/
|
||||
public void execute() throws SQLException {
|
||||
try (PreparedStatement ps = createPreparedStatement()) {
|
||||
ps.execute();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams the results of a ResultSet using a factory that creates instances for each row of the ResultSet
|
||||
*
|
||||
* @param rs ResultSet to stream from
|
||||
* @param factory Factory to create instances of T.
|
||||
* @return Stream of T instances.
|
||||
*/
|
||||
private Stream<T> streamFromResultSet(final ResultSet rs, final Function<ResultSet, T> factory) {
|
||||
Iterator<T> iterator = new Iterator<>() {
|
||||
private boolean hasNextChecked = false;
|
||||
private boolean hasNext;
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public boolean hasNext() {
|
||||
if (!hasNextChecked) {
|
||||
hasNext = rs.next();
|
||||
hasNextChecked = true;
|
||||
}
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
hasNextChecked = false;
|
||||
return factory.apply(rs);
|
||||
}
|
||||
};
|
||||
Spliterator<T> spliterator = Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED);
|
||||
return StreamSupport.stream(spliterator, false)
|
||||
.onClose(() -> {
|
||||
try {
|
||||
rs.getStatement().close();
|
||||
} catch (SQLException ignored) {
|
||||
// Exception is ignored.
|
||||
}
|
||||
try {
|
||||
rs.close();
|
||||
} catch (SQLException ignored) {
|
||||
// Exception is ignored.
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public <R> R withStream(Function<Stream<T>, R> handler) throws SQLException {
|
||||
try (Stream<T> stream = stream()) {
|
||||
return handler.apply(stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Function with three parameters and no result which can throw an SQLException
|
||||
*
|
||||
* @param <T> type of first parameter
|
||||
* @param <U> type of second parameter
|
||||
* @param <V> type of third parameter
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TriSqlFunction<T, U, V> {
|
||||
|
||||
/**
|
||||
* Calls the thre parameter function.
|
||||
*
|
||||
* @param t first parameter.
|
||||
* @param u second parameter.
|
||||
* @param v third parameter.
|
||||
* @throws SQLException Function could throw an SQLException.
|
||||
*/
|
||||
void apply(T t, U u, V v) throws SQLException;
|
||||
}
|
||||
|
||||
/**
|
||||
* ParameterSetter is a function Interface that could be used to alter a PreparedStatement.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ParameterSetter {
|
||||
|
||||
/**
|
||||
* Does the required work on PreparedStatement.
|
||||
*
|
||||
* @param ps PreparedStatement to work on.
|
||||
* @throws SQLException Could be thrown when working on PreparedStatement.
|
||||
*/
|
||||
void apply(PreparedStatement ps) throws SQLException;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,50 @@
|
||||
package de.neitzel.core.inject;
|
||||
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.SuperClass;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.TestComponent1_1;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_1;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.sub.TestComponent1_2;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class InjectableComponentScannerTest {
|
||||
|
||||
/**
|
||||
* Tests loading of multiple FXMLComponents including sub packages.
|
||||
*/
|
||||
@Test
|
||||
void testLoadComponents() {
|
||||
InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.core.inject.testcomponents.test1ok");
|
||||
var instantiableComponents = scanner.getInstantiableComponents();
|
||||
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
||||
|
||||
assertAll(
|
||||
() -> assertNotNull(instantiableComponents),
|
||||
() -> assertEquals(3, instantiableComponents.size()),
|
||||
() -> assertTrue(instantiableComponents.containsKey(TestComponent1_1.class)),
|
||||
() -> assertTrue(instantiableComponents.containsKey(TestComponent1_2.class)),
|
||||
() -> assertTrue(instantiableComponents.containsKey(TestInterface1_1.class)),
|
||||
() -> assertTrue(nonUniqueTypes.contains(SuperClass.class)),
|
||||
() -> assertTrue(nonUniqueTypes.contains(TestInterface1_2.class)),
|
||||
() -> assertTrue(scanner.getErrors().isEmpty())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests failing to load a FXMLComponent which has an unknwown parameter.
|
||||
*/
|
||||
@Test
|
||||
void testComponentsFailWithUnknownParameters() {
|
||||
InjectableComponentScanner scanner = new InjectableComponentScanner("de.neitzel.core.inject.testcomponents.test2fail");
|
||||
var instantiableComponents = scanner.getInstantiableComponents();
|
||||
var nonUniqueTypes = scanner.getNotUniqueTypes();
|
||||
|
||||
assertAll(
|
||||
() -> assertNotNull(instantiableComponents),
|
||||
() -> assertEquals(0, instantiableComponents.size()),
|
||||
() -> assertFalse(scanner.getErrors().isEmpty())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package de.neitzel.core.inject.testcomponents.test1ok;
|
||||
|
||||
public class SuperClass {
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.neitzel.core.inject.testcomponents.test1ok;
|
||||
|
||||
import de.neitzel.core.inject.annotation.Component;
|
||||
|
||||
@Component
|
||||
public class TestComponent1_1 extends SuperClass implements TestInterface1_2 {
|
||||
public TestComponent1_1() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package de.neitzel.core.inject.testcomponents.test1ok;
|
||||
|
||||
public interface TestInterface1_1 {
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package de.neitzel.core.inject.testcomponents.test1ok;
|
||||
|
||||
public interface TestInterface1_2 {
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package de.neitzel.core.inject.testcomponents.test1ok.sub;
|
||||
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.SuperClass;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_1;
|
||||
import de.neitzel.core.inject.testcomponents.test1ok.TestInterface1_2;
|
||||
import de.neitzel.core.inject.annotation.Component;
|
||||
|
||||
@Component
|
||||
public class TestComponent1_2 extends SuperClass implements TestInterface1_1, TestInterface1_2 {
|
||||
public TestComponent1_2() {
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
package de.neitzel.core.inject.testcomponents.test2fail;
|
||||
|
||||
import de.neitzel.core.inject.annotation.Component;
|
||||
|
||||
/**
|
||||
* TestComponent1 that should fail.
|
||||
*/
|
||||
@Component
|
||||
public class TestComponent2_1 {
|
||||
public TestComponent2_1(String test) {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user