jqassistant-archive / jqa-spring-plugin Goto Github PK
View Code? Open in Web Editor NEWHome Page: http://jqassistant.org/
License: GNU General Public License v3.0
Home Page: http://jqassistant.org/
License: GNU General Public License v3.0
When you have a Service which uses a MapStruct Mapper where one enitity will be mapped to a new format you got this message, becauce MapStruct generated @Component
Don't show this Error on Components
Create a Service which uses MapStruct to Map from one format to the other and scan this Service. Or just checkout
https://github.com/TheSasch/jqa_spring_example
to see a demo application which shows the problem within the analysis. Use
mvnw clean verify -P jqassistant
to see the problem.
It's possible to hold Injectables as a field in classes, which are not maneged by Spring. With that, classes that are manually injected hold classes that are managed by Spring.
For that I propose to add a new (strict) constraint spring-injection:InjectablesMustOnlyBeHeldInInjectables
Create a bean producer returning any JDK class. If the constraint fails, the ticket is done.
In my projects, I'm getting InjectablesMustNotBeInstantiated violations for a pattern we use frequently:
@Configuration
public class JacksonConfig {
@Bean
public ObjectMapper objectMapper() {
return ObjectMapperFactory.createObjectMapper();
}
}
public class ObjectMapperFactory {
public static ObjectMapper createObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModules(new JavaTimeModule());
return objectMapper;
}
}
The static method allows us to use the same ObjectMapper
definition both in Spring and non-Spring contexts (where non-Spring is not just test code - it is not unusual to have some other frameworks or libs within a Spring application which do not work with Spring beans).
Moving the constructor call to the producer method and then calling the producer method from createObjectMapper()
would avoid the violation, but not really improve the code by introducing a dependency of the ObjectMapperFactory
on Spring.
So my proposal is to rewrite the violation condition along the lines of "If the constructor of an Injectable is called from another Injectable, then the caller must be a BeanProducer".
MATCH
(injectable:Injectable:Type)-[:DECLARES]->(constructor:Constructor),
(type:Injectable:Type)-[:DECLARES]->(method:Method)-[newInstance:INVOKES*1..]->(constructor)
WHERE NOT (method:Spring:BeanProducer)
Given I have test configuration in my spock test class
@TestConfiguration
static class SpockDetachedMockConfig {
def mockFactory = new DetachedMockFactory()
@Bean
Consumer<CacheUpdate> cacheUpdateNotificator() {
return mockFactory.Mock(Consumer)
}
}
it is reported as
[ERROR] Constraint: spring-injection:BeanProducerMustBeDeclaredInConfigurationComponent
[ERROR] Severity: MAJOR
[ERROR] Bean producer methods must be declared in configuration components.
First: Thanks a lot for providing jQAssistant.
The constraint spring-injection:InjectablesMustOnlyBeHeldInInjectables detects non-static inner classes of injectables, such inner classes always hold a reference to its owning class.
Please modify this constraint.
Example 1:
@Configuration
class ConfigurationWithInnerClass {
@Bean
HttpComponentsClientHttpRequestFactory httpRequestFactoryUsingInnerClass() {
try {
return new HttpComponentsClientHttpRequestFactory(HttpClientBuilder.create().useSystemProperties()
.setSSLContext(new SSLContextBuilder().loadTrustMaterial(new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build()).build());
} catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) {
throw new RuntimeException(e);
}
}
}
results in NonInjectableHavingInjectablesAsField=ConfigurationWithInnerClass$1, Fields=ConfigurationWithInnerClass
Example 2:
@Component
public class ComponentWithInnerClass {
private class InnerClassUsingSomeMethodFromOuterClass {
}
}
results in NonInjectableHavingInjectablesAsField=ComponentWithInnerClass$InnerClassUsingSomeMethodFromOuterClass, Fields=ComponentWithInnerClass
[[structure:TransactionMethodMustNotBeCalledDirectly]]
.Transaktionale Methoden dürfen innerhalb einer Klasse nicht von anderen Methoden aufgerufen werden
[source,cypher,role=constraint,requiresConcepts="spring:TransactionMethod"]
[[structure:NoFieldInjectionInConfiguration]]
.Mit @configuration annotierte Klassen sollten keine Field Injection enthalten
[source,cypher,role=constraint,requiresConcepts="spring:Configuration,spring:InjectedField"]
[[structure:NoFieldInjectionInComponent]]
.Mit @component annotierte Klassen sollten keine Field Injection enthalten
[source,cypher,role=constraint,requiresConcepts="spring:Component,spring:InjectedField"]
[[spring:InjectablePackageProtected]]
.Injectables müssen package-protecte sein, wenn sie ein (nicht-Spring) Interface implementieren
[source,cypher,role=constraint,requiresConcepts="spring:Injectable,spring:BusinesInterface"]
[[structure:ComponentsMustNoImplementSpringInterface]]
.Application Componenten sollten keine Framework spezifischen interfaces implementieren
[source,cypher,role=constraint,requiresConcepts="spring:Component,spring:FrameworkInterface"]
Given I have:
@Configuration
public class LoggingInterceptorConfig {
@Bean
public CustomizableTraceInterceptor methodTraceLoggingInterceptor() {
return new MethodTraceLoggingInterceptor();
}
}
and
public class MethodTraceLoggingInterceptor extends CustomizableTraceInterceptor {
MethodTraceLoggingInterceptor() {
setEnterMessage("> [ENTRY] " + PLACEHOLDER_METHOD_NAME + "(" + PLACEHOLDER_ARGUMENTS + ")");
setExitMessage("< [EXIT] " + PLACEHOLDER_METHOD_NAME + " @ " + PLACEHOLDER_INVOCATION_TIME + " ms: " + PLACEHOLDER_RETURN_VALUE);
setExceptionMessage("< [THROW] " + PLACEHOLDER_METHOD_NAME + " : " + PLACEHOLDER_EXCEPTION);
setUseDynamicLogger(true);
}
@Override
protected boolean isLogEnabled(Log logger) {
return logger.isDebugEnabled();
}
@Override
protected void writeToLog(Log logger, String message, Throwable ex) {
if (ex != null) {
logger.warn(message, ex);
} else {
logger.debug(message);
}
}
}
it is reported as
[ERROR] Type=foo.MethodTraceLoggingInterceptor, Method=foo.MethodTraceLoggingInterceptor#void <init>(), Injectable=org.springframework.aop.interceptor.CustomizableTraceInterceptor, LineNumber=8
even though I don't have any init method in MethodTraceLoggingInterceptor
[[structure:ImplementationDependencies]]
.Ein Controller referenziert entweder einen Service oder das entsprechende Repository, niemals beides
[source,cypher,role=constraint,requiresConcepts="spring:Controller,spring:Service,spring:Repository"]
[[structure:ConstructorFieldsMustNotBeManipulated]]
.Felder die vom Konstruktor gesetzt werden, dürfen durch keine andere Methode überschrieben werden
[source,cypher,role=constraint,requiresConcepts="spring:ConstructorField"]
[[spring:InjectablesMustNotBeInstantiated]]
.Newables dürfen keine Injectables erzeugen
[source,cypher,role=constraint,requiresConcepts="spring:Injectable"]
When I copy & paste jqassistant-maven-plugin configuration from https://101.jqassistant.org/getting-started-spring-boot-maven/readme.html#MavenPlugin and run mvn clean install
, I get [ERROR] Failed to execute goal com.buschmais.jqassistant:jqassistant-maven-plugin:1.4.0:analyze (default-cli) on project mer-web: Analysis failed.: Group 'package:Default' is not defined. Concept package:Default not found. -> [Help 1]
Why is that?
I have to comment out <group>package:Default</group>
, so that it returns constraint violations.
Most of the constraints provided by the plugin only make sense for production code, therefore test code (i.e. test artifacts) should be ignored.
Making relationships to classes (beans) / modules and to find missing relationships
Given I have:
@ConfigurationProperties("security")
@Getter
@Setter
public class SecurityProperties {
private boolean enabled = true;
}
When I run jqassistant-maven-plugin:1.4.0:analyze (default-cli)
, then it reports:
[ERROR] --[ Constraint Violation ]-----------------------------------------
[ERROR] Constraint: spring-injection:FieldsOfInjectablesMustNotBeManipulated
[ERROR] Severity: MAJOR
[ERROR] Fields of injectable types must not be manipulated, except from constructors.
[ERROR] Message=...SecurityProperties.setEnabled(?) writes field 'enabled' at line 13, Injectable=...SecurityProperties, Method=...SecurityProperties#void setEnabled(boolean), Field=...SecurityProperties#boolean enabled, LineNumber=13
The reason I have setters is missing support for immutable @ConfigurationProperties
.
The following three constraints report violations as a a collected list. However, this is impractical as tracking changes (both positive and negative) is made more difficult as the rule "one row - one violation" doesn't hold anymore Each dependency shoud be considered as a violation.
So the following return statement:
RETURN repository as Repository, collect(other) as InvalidDependencies
should be changed to
RETURN repository as Repository, other as InvalidDependency
Given I have:
@SpringBootTest
class MerchantImportExportSpec extends spock.lang.Specification {
@Autowired
MerchantService merchantService
...
}
plugin reports it as [ERROR] Message=null, Injectable=merchant.MerchantImportExportSpec, Method=merchant.MerchantImportExportSpec#void setMerchantService(merchant.MerchantService), Field=merchant.MerchantImportExportSpec#merchant.MerchantService merchantService, LineNumber=null
This is test, so I'd expect I can inject using @Autowired
.
Is it possible to tweak the rule so that it treats Spock specifications as tests as well?
The constraint reports the method and the field but from the Asciidoc report the containing type is not visible and should be added
As core developer of jQAssistant, I want this project to be build on various plattforms with different JDK versions on each commit, so that I know if the change done my the commit does not break the build and the software is fully functional.
The following acceptance criterias must be met:
mvn -DskipTests -Djqassistant.skip clean install
must passmvn -Djqassistant.skip clean install
must passmvn -P IT clean install
must passmvn -DskipTests -Djqassistant.skip=false clean install
must passConstraint spring-injection:InjectablesMustNotBeInstantiated
builds Cartesian product when evaluating.
Constraint is violated if constructor of Injectable
is called in call chain. If a Generated
type is involved in the call chain, rule should not trigger.
Go over all documents of the documentation and unify the Asciidoctor markup.
It is possible to implement a Bean Producer that returns a JDK class.
With the concept spring-injection:BeanProducer
, those return types will be marked as :Spring:Injectable
.
In my opinion it is an anti-pattern to have Bean Producers return JDK classes and not use-case specific (custom) implementations.
Besides that, following rules will lead to lots of false-positives since JDK classes which are marked as Injectables will of course be used across the code in a different context.
spring-injection:InjectablesMustNotBeInstantiated
spring-injection:InjectablesMustNotBeHeldInStaticVariables
spring-injection:InjectablesMustNotBeAccessedStatically
For that I propose to add a new constraint spring-injection:JdkClassesMustNotBeInjectables
Create a bean producer returning any JDK class. If the constraint fails, the ticket is done.
Migrate the existing documentation of the Spring Plugin to the new reference manul.
I have a custom annotation annotated with @RestController
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@RestController
public @interface MyCustomAnnotation {
}
which gets labelled with Spring
, RestController
, Controller
, Component
, Injectable
by the spring-mvc:RestController
concept. This leads to false positives with the spring-component:ControllerMustOnlyDependOnServicesRepositories
constraint as annotation class is recognised as a controller, but actually is not.
spring-mvc:RestController
concept should exclude annotations from being labelled. The same applies for the following concepts: spring-component:Controller
, spring-component:Service
, spring-component:Component
, spring-component:Configuration
, spring-data:AnnotatedRepository
.
Current workaround is the following concept:
<concept id="project:spring:AnnotationAnnotatedWithRestController">
<providesConcept refId="spring-component:Component"/>
<providesConcept refId="spring-component:Controller"/>
<providesConcept refId="spring-injection:Injectable"/>
<requiresConcept refId="spring-mvc:RestController"/>
<description>
Removes all Spring labels from annotations annotated with @RestController.
</description>
<cypher><![CDATA[
MATCH (annotation:Annotation)-[:ANNOTATED_BY]->()-[:OF_TYPE]->(annotationType:Type)
WHERE annotationType.fqn = "org.springframework.web.bind.annotation.RestController"
REMOVE annotation:Spring:RestController:Controller:Component:Injectable
RETURN annotation as Annotation
]]></cypher>
</concept>
When bulding the project with mvn clean install -PIT
, no IT tests are executed (the same can be seen in the build logs of Jenkins). Example log:
[INFO] Running com.buschmais.jqassistant.plugin.spring.test.constraint.SpringInterfacesIT
[main] INFO com.buschmais.jqassistant.core.plugin.impl.PluginConfigurationReaderImpl - Scanning for jQAssistant plugins...
[main] INFO com.buschmais.jqassistant.core.plugin.impl.PluginConfigurationReaderImpl - [Common, Common Test, Core Analysis, Core Report, Java, Spring, XML].
[INFO] Tests run: 0, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.328 s - in com.buschmais.jqassistant.plugin.spring.test.constraint.SpringInterfacesIT
In my project, I'm using spring-boot:Default
in combination with dependency:PackageCycles
.
The analyzer reports a package cycle between two packages service
and config
that actually do not have a cycle.
Package service
does depend on package config
. A class in package config
autowires List<FooService>
where all beans implementing FooService
are located in Package service
. These beans are treated as a virtual dependency which results in the inverse relation
config->[d:DEPENDS_ON]->service with d.virtual = true
Since the PackageCycles
rule is not aware of the virtual
flag, it will report a cycle between service
and config
.
Express virtual dependencies by a separate relation.
org.springframework.web.bind.annotation.RestController
annotation is itself annotated with @Controller
[[structure:ApplicationMustBeInBasePackage]]
.SpringApplication Klasse muss im Basispackage liegen
[source,cypher,role=constraint,requiresConcepts="spring-boot:Application"]
If a Spring bean exposes a canonical constructor, that constructor does not need to be annotated with an injection annotation like @Autowired
or @Inject
. We could report that the annotation can be removed.
With my rudimentary Cypher skills I came up with this one but it looks like I can't count over the occurrences of constructors like this.
MATCH
(injectable:Injectable)-[:DECLARES]->(constructor:Constructor),
(constructor:Constructor)-[:ANNOTATED_BY]->(annotation:Annotation)
WHERE
count(constructor) > 1 AND annotation.fqn in [
"org.springframework.beans.factory.annotation.Autowired",
"javax.inject.Inject"
]
RETURN
"Unique constructor of type " + injectable.fqn + " does not need to be annotated with " + annotation.fqn as Message,
injectable as Injectable, constructor as Constructor, annotation as Annotation
When I create groovy class which is injectable, plugin reports FieldsOfInjectablesMustNotBeManipulated
because of some getMetaClass()
and setMetaClass()
which is not in my control
[ERROR] Message=null, Injectable=foo.WiremockLauncher$WiremockRunner, Method=foo.WiremockLauncher$WiremockRunner#groovy.lang.MetaClass getMetaClass(), Field=foo.WiremockLauncher$WiremockRunner#groovy.lang.MetaClass metaClass, LineNumber=null
[ERROR] Message=null, Injectable=foo.WiremockLauncher$WiremockRunner, Method=foo.WiremockLauncher$WiremockRunner#void setMetaClass(groovy.lang.MetaClass), Field=foo.WiremockLauncher$WiremockRunner#groovy.lang.MetaClass metaClass, LineNumber=
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.