I have an annotation that can be added on METHOD and TYPE and is used in thousands of places in our project.
#Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
#Target({METHOD, TYPE})
#Inherited
public #interface RequiredStore{
Store value();
}
Is it possible to make the annotation deprecated only on methods while keeping it non-deprecated on types? I want other developers to be notified by IDE that it should not be used on methods any more, until we'll refactor all existing usages and finally remove the METHOD part.
If it's not possible, is there any Way to handle such case beside creating new annotation only for types and deprecating the old one?
You could use an annotation Processor.
For example, the annotation and its processor would be placed in its own .jar file and added as a dependency of the sources that use the annotation.
The custom .jar would have the following structure:
src/main/
java/com/company/annotations/
RequiredStore.java
RequiredStoreProcessor.java
resources/META-INF/services
javax.annotation.processing.Processor
RequiredStore.java stays as you have it above.
RequiredStoreProcessor.java could look something like this:
package com.company.annotations;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
#SupportedAnnotationTypes("com.company.annotations.RequiredStore")
public class RequiredStoreProcessor extends AbstractProcessor {
#Override
public boolean process(
Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
for (Element element
: roundEnv.getElementsAnnotatedWith(RequiredStore.class)) {
if (element.getKind().equals(ElementKind.METHOD)) {
processingEnv.getMessager().printMessage(
Diagnostic.Kind.WARNING,
"Using #RequiredStore on methods has been deprecated\n"
+ "Class: " + element.getEnclosingElement() + "\n"
+ "Method: " + element.getSimpleName() + "\n");
}
}
// Other processing...
return false;
}
#Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
}
The javax.annotation.processing.Processor file allows javac to pickup the Processor via SPI and simply contains
com.company.annotations.RequiredStoreProcessor
Finally, compile this into a .jar and add it to the classpath where the annotations are being used. Any methods that have the #RequiredStore will produce a compiler warning. For example, for this class,
package com.company.business;
import com.company.annotations.RequiredStore;
#RequiredStore
public interface Business {
#RequiredStore
public void someMethod();
}
The compiler warning would be this:
warning: Using #RequiredStore on methods has been deprecated
Class: com.company.business.Business
Method: someMethod
As for an indication in the IDE, you might have to write a custom inspection and unfortunately this depends on the IDE used.
Notes:
Decent custom annotations reference: Code Generation using Annotation Processors in the Java language
If you are okay about using native aspectj, another option is to use AspectJ's code enforcement policy this way:
public aspect RequiredStoreAnnotationCheck {
declare warning: execution(#RequiredStore * *.*(..)) : "Required store annotation not appropriate for methods..";
}
If the IDE is integrated with AspectJ, this would be flagged as a compile time check.
AspectJ in action book has a good amount of detail on this too.
Here is one of my blog articles for more context: http://www.java-allandsundry.com/2012/03/code-policy-enforcement-using-aspectj.html
Related
I have a problem with JOOQ framework (3.13.4) along with Spring Boot and Java 8.
The problem is that I'm trying to generate domain classes using java code way (instead of using codegen plugin with maven which had some troubles with custom naming strategy provider). So as first let me show You the #Configuration class which contains (at least I believe that it contains) all of the necessary beans:
import com.ormtester.common.base.Measurer;
import com.ormtester.common.utils.enums.OrmType;
import com.ormtester.datasources.config.RouteableDataSource;
import org.jooq.SQLDialect;
import org.jooq.codegen.GenerationTool;
import org.jooq.impl.DataSourceConnectionProvider;
import org.jooq.impl.DefaultConfiguration;
import org.jooq.impl.DefaultDSLContext;
import org.jooq.impl.DefaultExecuteListenerProvider;
import org.jooq.util.xml.jaxb.Schema;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.jooq.meta.jaxb.*;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.PostConstruct;
import java.util.Properties;
#Configuration
#EnableTransactionManagement
public class JooqConfigurator {
private Properties moduleProperties;
private RouteableDataSource routeableDataSource;
public JooqConfigurator(RouteableDataSource routeableDataSource) {
this.routeableDataSource = routeableDataSource;
try {
moduleProperties = new Properties();
moduleProperties.load(JooqConfigurator.class.getClassLoader()
.getResourceAsStream("jooq.properties"));
} catch (Exception ignore) {}
}
#Bean
public DataSourceConnectionProvider connectionProvider() {
return new DataSourceConnectionProvider(routeableDataSource);
}
#Bean
public ExceptionTranslator exceptionTransformer() {
return new ExceptionTranslator();
}
#Bean
public DefaultConfiguration configuration() {
DefaultConfiguration jooqConfiguration = new DefaultConfiguration();
jooqConfiguration.set(connectionProvider());
jooqConfiguration.set(new DefaultExecuteListenerProvider(exceptionTransformer()));
jooqConfiguration.set(SQLDialect.DEFAULT);
return jooqConfiguration;
}
#Bean
public DefaultDSLContext dsl() {
return new DefaultDSLContext(configuration());
}
#PostConstruct
public void generateCode() {
try {
GenerationTool.generate(new org.jooq.meta.jaxb.Configuration()
.withJdbc(new Jdbc()
.withDriver("com.mysql.cj.jdbc.Driver")
.withUrl("jdbc:mysql://localhost:3306/ormtester?useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC")
.withUser("root")
.withPassword("root123"))
.withGenerator(new Generator()
.withName("org.jooq.codegen.JavaGenerator")
.withStrategy(new CustomStrategyProvider())
.withDatabase(new Database()
.withName("org.jooq.meta.mysql.MySQLDatabase")
.withIncludes(".*")
.withExcludes("")
.withSchemata(new SchemaMappingType().withInputSchema("ormtester").withOutputSchema("ormtester"))
.withInputCatalog("ormtester")
.withOutputCatalog("ormtester"))
.withTarget(new Target()
.withPackageName("com.ormtester.jooq.domain")
.withDirectory("jooq/src/main/java"))));
} catch (Exception e) {
System.out.println(e.getLocalizedMessage());
}
}
}
RouteableDataSource is a type that extends AbstractRoutingDataSource because in this case I need to have a possibility to change datasource at runtime. This thing is working well in the other regions of the project (or in another words with tools like Hibernate or MyBatis).
As You can see there is a #PostConstruct method which is used for generating domain classes and the problem is that this method doesn't generate any error or something but the classes are also not generated. I've tried to run it using PostgreSQL and Oracle database (of course changing the driver, database name etc.) and the situation is looking exactly the same.
One interesting thing is that when I'm running this code and package com.ormtester.jooq.domain is present - during the method execution domain package is getting removed.
I'd also like to mention that JOOQ autoconfiguration is disabled by excluding JooqAutoConfiguration class through the #SpringBootApplication annotation located at the project's main (starter) class.
IDE is running in administrator's mode and - what can be also interesting - if I will set the breakpoint in the getJavaClassName() method in my custom naming strategy provided (CustomStrategyProvider which extends DefaultGeneratorStrategy class, the breakpoint is reached everytime this method is used.
So does anyone faced the same problem and/or simply can tell me if I'm doing something wrong or something is missing in the code snippet that I've provieded here? I have this problem since about 4 days and now I'm running out of the ideas what can be wrong. I went through the tons of topics on many forums and nothing helped me, including the tutorials on the author's page (which in my opinion simply lacks of important informations).
I'll be really grateful for every help - thanks in advance!
Code generation is a build task, not a runtime task. I can't think of a reasonable scenario where generating code only at runtime would make sense.
The problem is that I'm trying to generate domain classes using java code way (instead of using codegen plugin with maven which had some troubles with custom naming strategy provider)
You have to create a separate maven module (or project) where you build the custom naming strategy, and then add that as a dependency to the jOOQ code generation plugin. This works the same way as with the JPADatabase, where entities have to be placed in a separate maven module.
With this jdk code in ../java/lang/Override.java,
package java.lang;
import java.lang.annotation.*;
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.SOURCE)
public #interface Override {
}
having just annotation declaration, java compiler is intelligent enough to detect error(compile time):
The method toString123() of type Example must override or implement a supertype method
in the below problem code.
package annotationtype;
public class Example {
#Override public String toString() {
return "Override the toString() of the superclass";
}
#Override public String toString123() {
return "Override the toString123() of the superclass";
}
public static void main(String[] args) {
}
}
Annotation declaration for Override just gets compiled to,
interface java.lang.Override extends java.lang.annotation.Annotation{
}
which is nothing more than an interface.
So,
How does interface java.lang.Override syntax help java compiler to detect above error at compile time?
The implementation that triggers the compile error doesn't lie in the annotation, it lies in the Java compiler.
If you want to write your own similar annotation processor, you would use the annotation processor API: http://docs.oracle.com/javase/7/docs/api/javax/annotation/processing/Processor.html
which is nothing more than an interface.
So,
How does interface java.lang.Override syntax help java compiler to
detect above error at compile time?
That's right. Override is nothing more than an interface. The actual work is done by the java compiler. How the compiler does this is not specified.
Here are some links that explain how to work with an AnnotationProcessor to implement something similar to #Override :
Processor Java doc
Java annotation processing tool
Code generation using AnnotationProcessor
Annotation Processor, generating a compiler error
Source code analysis using Java 6 API
Playing with Java annotation processing
Does spock has any Test event listener like how TestNg has ITestListener. ?
So that I can have access, when the test cases failed etc.
Spock does have listeners. Unfortunately the official documentation, which is otherwise excellent, has "TODO" under Writing Custom Extensions: http://spockframework.github.io/spock/docs/1.0/extensions.html.
Update: The official docs have been updated to include helpful information about custom extensions: http://spockframework.org/spock/docs/1.1/extensions.html. See those for more details.
There are two ways: Annotation-based and Global.
Annotation-based
Three pieces here: the annotation, the extension, and the listener.
The annotation:
import java.lang.annotation.*
import org.spockframework.runtime.extension.ExtensionAnnotation
#Retention(RetentionPolicy.RUNTIME)
#Target([ElementType.TYPE, ElementType.METHOD])
#ExtensionAnnotation(ListenForErrorsExtension)
#interface ListenForErrors {}
The extension (Updated):
import org.spockframework.runtime.extension.AbstractAnnotationDrivenExtension
import org.spockframework.runtime.model.SpecInfo
class ListenForErrorsExtension extends AbstractAnnotationDrivenExtension<ListenForErrors> {
void visitSpec(SpecInfo spec) {
spec.addListener(new ListenForErrorsListener())
}
#Override
void visitSpecAnnotation(ListenForErrors annotation, SpecInfo spec){
println "do whatever you need here if you do. This method will throw an error unless you override it"
}
}
The listener:
import org.spockframework.runtime.AbstractRunListener
import org.spockframework.runtime.model.ErrorInfo
class ListenForErrorsListener extends AbstractRunListener {
void error(ErrorInfo error) {
println "Test failed: ${error.method.name}"
// Do other handling here
}
}
You can then use your new annotation on a Spec class or method:
#ListenForErrors
class MySpec extends Specification {
...
}
Global
This also has three pieces: the extension, the listener, and the registration.
class ListenForErrorsExtension implements IGlobalExtension {
void visitSpec(SpecInfo specInfo) {
specInfo.addListener(new ListenForErrorsListener())
}
}
You can use the same ListenForErrorsListener class as above.
To register the extension, create a file named org.spockframework.runtime.extension.IGlobalExtension in the META-INF/services directory. If using Gradle/Maven, this will be under src/test/resources. This file should contain only the fully qualified class name of your global extension, for example:
com.example.tests.ListenForErrorsExtension
References
For examples, see the Spock built-in extensions here:
https://github.com/spockframework/spock/tree/groovy-1.8/spock-core/src/main/java/spock/lang
https://github.com/spockframework/spock/tree/groovy-1.8/spock-core/src/main/java/org/spockframework/runtime/extension/builtin
Spock has interaction listening via Mock:
def "should send messages to all subscribers"() {
given:
def subscriber = Mock(Subscriber)
when:
publisher.send("hello")
then:
1 * subscriber.receive("hello")
}
See the interaction based testing in the docs
Is that possible to give custom warning message in my own API like below? Is Resource leak:'ois' is never closed message related with Java API or JVM?
It's possible using a compiler API. You have to extend an AbstractProcessor and then make sure compiler knows about it.
Lets say we don't like programmers to swear in the source code. So, when someone defines a field with name "shit", we want to show a warning. Here is a simple implementation:
import java.util.List;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
#SupportedSourceVersion(SourceVersion.RELEASE_7)
#SupportedAnnotationTypes("*")
public class Test extends AbstractProcessor {
public int shit;
public int foo;
#Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> rootElements = roundEnv.getRootElements();
for (Element element : rootElements) {
if (element.getKind() == ElementKind.CLASS) {
List<? extends Element> classElements = element.getEnclosedElements();
for (Element classElement : classElements) {
if (classElement.getKind() == ElementKind.FIELD) {
if (classElement.getSimpleName().contentEquals("shit")) {
processingEnv.getMessager().printMessage(
Kind.WARNING,
"How dare you to swear in the source code?!",
classElement
);
}
}
}
}
}
return false;
}
public static void main(String[] args) {
//
}
}
Now, we want to apply such a processor just for this very class, because there is an ugly bad-called field too.
Using a command line:
javac Test.java
javac -processor Test Test.java
We need to firstly build a processor and then apply it while compiling (in this case to the same file).
And this is the output we get:
Test.java:17: warning: How dare you to swear in the source code?!
public int shit;
^
1 warning
To have the same warning in Eclipse or any other IDE, it's necessary to change compiler settings so it uses this custom processor.
Update: In the comments, kapep sent a link on how to set a custom processor in Eclipse: http://kerebus.com/2011/02/using-java-6-processors-in-eclipse/
Just for the record: Exactly the same warning may be achieved by implementing interface Closeable:
import java.io.Closeable;
import java.io.IOException;
public class Test implements Closeable {
#Override
public void close() throws IOException {
// TODO Auto-generated method stub
}
public static void main(String[] args) {
new Test();
}
}
And you see the same warning:
You can create warnings, notes, errors and other diagnostic messages like this using an annotation processor. It's a compiler plugin api integrated in the JDK. It lets you analyse the outline structure of source code. Despite the name you don't really need to handle any annotation when processing code. Messages are created using the Messager class. If you provide an element, it will be marked and the message will be shown next to it in the source code editor.
You won't be able to show message on elements inside methods or expressions though, only on declarations like types, properties, methods or parameters. It's possible to additionally parse the method body and generate messages based on the content using other tools, but as far as I know you can't show the message on the actual local element then. You could still show the message on the enclosing method or don't specify any element at all and show it in the IDE's log.
The IDE also needs to support this. I know that Eclipse and NetBeans do support messages generated by annotation processors, but there are probably other modern IDE that do so as well. If you need more features like messages on elements inside method bodies or the quick fix feature as shown in the example, I guess you need to create a plugin for the IDE.
I would believe that it is related to the eclipse ide, you could possibly write a plugin which displays warnings like that.
For example, when you use a method which has the annotation '#Deprecated' the ide automatically tells the programmer that the method is deprecated.
i wrote a very simple Aspect with Spring AOP. It works, but i have some problems understanding what is really going on. I don't understand why i have to add the aspectjweaver.jar? The Spring-AOP documentation clearly states that i don't need aspectj compiler or weaver as long as i just use Spring-AOP:
The AOP runtime is still pure Spring AOP though, and there is no dependency on the AspectJ compiler or weaver.
My configuration looks like this:
<aop:aspectj-autoproxy />
#Aspect
#Service
public class RemoteInvocationAspect {
#Before("execution(* at.test.mypackage.*.*(..))")
public void test() {
System.out.println("test");
}
...
I also tried XML configuration, didn't change anything though. Maybe i could just let it go, but i really would like to understand why aspectj-weaver is used? If i don't add the dependency in maven i get java.lang.ClassNotFoundException: org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException
Spring AOP implementation I think is reusing some classes from the aspectj-weaver. It still uses dynamic proxies - doesn't do byte code modification.
The following comment from the spring forum might clarify.
Spring isn't using the AspectJ weaver in this case. It is simply
reusing some of the classes from aspectjweaver.jar.
-Ramnivas
You are using AspectJ style pointcut-expression #Aspect and #Before are part of AspectJ. Check this link.
Regarding the AspectJ-weaver, its actually a bytecode weaver which weaves aspects into classes at load time.
I recently had a similar question Why does spring throw an aspectj error if it does not depend on aspectj?
To use Spring AoP without an AspectJ dependency it must be done in xml. The annotations are a part of AspectJ.
Also, the really cool expression language is only supported by AspectJ. So you have to define explicit point-cuts. See Section 6.3.2. Declaring a pointcut:
http://static.springsource.org/spring/docs/2.0.x/reference/aop.html section
I'm still having trouble finding any elaborate documentation on this technique.
You need the aspectjtools or the aspectjweaver dependencies when you use the AspectJ pointcut expression language.
Please see the following classes:
Foo.java
public interface Foo {
void foo();
void baz();
}
FooImpl.java
public class FooImpl implements Foo {
#Override
public void foo() {
System.out.println("Foo!");
}
#Override
public void baz() {
System.out.println("Baz!");
}
}
MethodBeforeAdviceBarImpl.java
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MethodBeforeAdviceBarImpl implements MethodBeforeAdvice {
#Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("Bar!");
}
}
And please see App.java version - 1
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
public class App {
public static void main(String[] args) {
final MethodBeforeAdvice advice = new MethodBeforeAdviceBarImpl();
final NameMatchMethodPointcutAdvisor nameMatchMethodPointcutAdvisor = new NameMatchMethodPointcutAdvisor();
nameMatchMethodPointcutAdvisor.setMappedName("foo");
nameMatchMethodPointcutAdvisor.setAdvice(advice);
final ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(nameMatchMethodPointcutAdvisor);
final Foo foo = new FooImpl();
proxyFactory.setTarget(foo);
final Foo fooProxy = (Foo) proxyFactory.getProxy();
fooProxy.foo();
fooProxy.baz();
}
}
The output of running this example will be:
Bar!
Foo!
Baz!
I only need the org.springframework:spring-context.jar in my classpath. Now instead of a NameMatchMethodPointcutAdvisor, lets use AspectJExpressionPointcutAdvisor:
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor;
import org.springframework.aop.framework.ProxyFactory;
public class App {
public static void main(String[] args) {
final MethodBeforeAdvice advice = new MethodBeforeAdviceBarImpl();
final AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor = new AspectJExpressionPointcutAdvisor();
aspectJExpressionPointcutAdvisor.setAdvice(advice);
aspectJExpressionPointcutAdvisor.setExpression("execution(void biz.tugay.spashe.Foo.foo())");
final ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addAdvisor(aspectJExpressionPointcutAdvisor);
final Foo foo = new FooImpl();
proxyFactory.setTarget(foo);
final Foo fooProxy = (Foo) proxyFactory.getProxy();
fooProxy.foo();
fooProxy.baz();
}
}
Again, if I only have the spring-context.jar in my classpath, I will get:
An exception occured while executing the Java class. null: InvocationTargetException: org/aspectj/weaver/reflect/ReflectionWorld$ReflectionWorldException: org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException
When you investigate the AspectJExpressionPointcutAdvisor class, you will see that it extends AbstractGenericPointcutAdvisor and which delegates the work to an instance of AspectJExpressionPointcut. And you can see that AspectJExpressionPointcut has the following import statements:
import org.aspectj.weaver.patterns.NamePattern;
import org.aspectj.weaver.reflect.ReflectionWorld.ReflectionWorldException;
import org.aspectj.weaver.reflect.ShadowMatchImpl;
import org.aspectj.weaver.tools.ContextBasedMatcher;
import org.aspectj.weaver.tools.FuzzyBoolean;
import org.aspectj.weaver.tools.JoinPointMatch;
import org.aspectj.weaver.tools.MatchingContext;
import org.aspectj.weaver.tools.PointcutDesignatorHandler;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParameter;
import org.aspectj.weaver.tools.PointcutParser;
import org.aspectj.weaver.tools.PointcutPrimitive;
import org.aspectj.weaver.tools.ShadowMatch;
You will need the aspectjtools dependency in your classpath in runtime so AspectJExpressionPointcut can load the classes it needs.
You can browse spring website and find the answer on page of docs.spring.io
The #AspectJ support can be enabled with XML or Java style configuration. In either case you will also need to ensure that AspectJ’s aspectjweaver.jar library is on the classpath of your application (version 1.6.8 or later). This library is available in the 'lib' directory of an AspectJ distribution or via the Maven Central repository.