This is the part 2 of the implementation of an injector, it reuses the code written for the part 1 (only in the tests).
The idea of the classpath scanning is to find all classes of a package annotated with some pre-registered annotation types, and auto-automatically execute the action corresponding to the annotation on those classes.
Here is an example, let suppose we have an annotation Component
defined like this
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
Then we have two classes Service
and Dependency
annotated with @Component
defined as such
@Component
public class Service {
private final Dependency dependency;
@Inject
public Service(Dependency dependency) {
this.dependency = Objects.requireNonNull(dependency);
}
public Dependency getDependency() {
return dependency;
}
}
@Component
public class Dependency {
}
An annotation scanner is a class that will scan all the classes of the package, here all the classes of the package
containing the class Application
and for each class annotated with @Component
execute the action, here,
register the class as provider class in the registry.
So when calling lookupInstance
on the registry, the registry knows how to create a Service
and a Dependency
.
public class Application {
public static void main(String[] args) {
var registry = new InjectorRegistry();
var scanner = new AnnotationScanner();
scanner.addAction(Component.class, registry::registerProviderClass);
scanner.scanClassPathPackageForAnnotations(Application.class);
var service = registry.lookupInstance(Service.class);
}
}
For the implementation,
ClassLoader.getResources()
with as parameter a package name with the dots ('.') replaced by slash ('/') returns all the URL
s of the folders
containing the classes (you can have more than one folder, by example one folder in src and one folder in test).
Class.forName(name, /initialize=/ false, classloader)
loads a Class
without initializing the class (without running its static block).
Those two methods are already available in the class Utils2.java with the exceptions correctly managed.
-
First, we want to implement a method
findAllJavaFilesInFolder(path)
(not public) that takes aPath
and returns the name of all the Java class in the folder. A Java class is file with a filename that ends with ".class". The name of a Java class is the name of the file without the extension ".class". Implement the methodfindAllJavaFilesInFolder
and check that the tests in the nested class "Q1" all pass. -
Then we want to implement a method
findAllClasses(packageName, classloader)
(not public) that return a list of the classes (theClass<?>
) contained in the packagepackageName
loaded by theclassloader
taken as argument. Implement the methodfindAllClasses
and check that the tests in the nested class "Q2" all pass.Note:
Utils2.getResources(packageName.replace('.', '/'), classLoader)
returns the URLs of the folders that contains the class files of a package. From a URL, you can get a URI and you can construct a Path from a URI.Utils2.loadClass(packageName + '.' + className, classLoader)
loads a class from the qualified name of a class (the name with '.' in it). -
We now want to add the method
addAction(annotationClass, action)
that takeannotationClass
the class of an annotation andaction
a function that takes a class and returnvoid
.addAction
register the action for the annotation class and only one action can be registered for an annotation class Implement the methodaddAction
and check that the tests in the nested class "Q3" all pass. -
We want to implement the method
scanClassPathPackageForAnnotations(class)
that takes a class as parameter, find the corresponding package, load all the class from the folders corresponding to the package, find all annotations of the classes and run the action each time corresponding to the annotation classes. Implement the methodscanClassPathPackageForAnnotations
and check that the tests in the nested class "Q4" all pass.Note: there is a method getPackageName() to find the name of a package of a class and a method getClassLoader to get the classloader of a class.