Class AutowireImplRuleTest
Impl-typed reference in production code.
When Spring's AOP machinery proxies a bean (the most common cause being
a @Transactional method, but @Cacheable, @Async,
@PreAuthorize and similar advice all do the same), the resulting
proxy implements the bean's interfaces but is not a subclass of
the underlying Impl. A field declared with the Impl type
cannot be satisfied at injection time and Spring fails with
BeanNotOfRequiredTypeException (JDK dynamic proxy) or causes
subtle aliasing bugs under CGLIB. The cure is to declare the field with
the interface type — Spring then injects the proxy transparently.
This rule was prompted by the AspectJ deeper recce
(commit b16450a5e8 on the unmerged worktree-aspectj-deeper
branch), which identified one historical instance of the pattern
(EeWriteServiceImpl.persisterHelper) that has since been
remediated. The rule locks the codebase down against re-introduction.
Two complementary rules ship together:
autowired_fields_must_not_be_impl_typed— field injection via@Autowired.constructor_parameters_must_not_be_impl_typed— constructor injection (the preferred DI style for Java-config Spring 6).
Scope: production classes only (tests excluded via
ImportOption.DoNotIncludeTests). Mocks and test doubles
legitimately need concrete types.
-
Field Summary
FieldsModifier and TypeFieldDescriptionstatic final com.tngtech.archunit.lang.ArchRuleIntentionally vacuous when no@AutowiredImpl-typed fields exist — that is the goal state of the migration.static final com.tngtech.archunit.lang.ArchRuleIntentionally vacuous when no constructor-injected Impl-typed parameters exist — that is the goal state, subject to the same caveat as above: seeclasses_are_actually_imported(JavaClasses). -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionstatic voidclasses_are_actually_imported(com.tngtech.archunit.core.domain.JavaClasses classes) 🛑 The guard against a guard that checks nothing.
-
Field Details
-
autowired_fields_must_not_be_impl_typed
public static final com.tngtech.archunit.lang.ArchRule autowired_fields_must_not_be_impl_typedIntentionally vacuous when no@AutowiredImpl-typed fields exist — that is the goal state of the migration. Vacancy only means that onceclasses_are_actually_imported(JavaClasses)has confirmed there were classes to check.allowEmptyShould(true)suppresses ArchUnit's default "rule matched no classes" failure so the guard keeps locking the codebase down without flagging the clean state. -
constructor_parameters_must_not_be_impl_typed
public static final com.tngtech.archunit.lang.ArchRule constructor_parameters_must_not_be_impl_typedIntentionally vacuous when no constructor-injected Impl-typed parameters exist — that is the goal state, subject to the same caveat as above: seeclasses_are_actually_imported(JavaClasses).allowEmptyShould(true)suppresses ArchUnit's default "rule matched no classes" failure.
-
-
Constructor Details
-
AutowireImplRuleTest
public AutowireImplRuleTest()
-
-
Method Details
-
classes_are_actually_imported
public static void classes_are_actually_imported(com.tngtech.archunit.core.domain.JavaClasses classes) 🛑 The guard against a guard that checks nothing.Both rules below are
allowEmptyShould( true )because a clean codebase legitimately matches no fields — which means neither can distinguish "clean" from "ArchUnit imported nothing". That distinction mattered: archunit 1.3.0's bundled ASM could not read Java 25 bytecode (major version 69) and imported zero classes silently, so this test passed while checking nothing from the day it was written until 2026-08-26, when the same failure was found in a sibling rule. Fixed by archunit 1.4.1.This sentinel states the expectation the other two cannot: an empty import is a broken toolchain.
-