Today, we often hear words like "cloud native" and "microservices" in the web application community. In Java development, ** frameworks ** such as Spring Boot are often used to realize these architectures.
However, when I was not so familiar with Java behavior and suddenly started developing using such a ** framework **, "Why does it work in this way?" "Annotation? What is it?" "DI? Nullpo" You may end up in a situation like "Isn't it?"
In this article, in order to dispel such doubts, I hope that I can create a super ~~ similar ~~ simple framework by myself and understand the internal mechanism of the Java framework as much as possible. I will.
Spring Boot quick start With reference to https://projects.spring.io/spring-boot/#quick-start, the client programs that use the framework realized this time are as follows.
src/main/java/hello/SampleController.java
package hello;
...<import omitted>
@Controller
public class SampleController {
@Resource
private SampleService service;
@RequestMapping("/hello")
public String home() {
return service.hello();
}
public static void main(String[] args) {
EasyApplication.run(SampleController.class, args);
}
}
Run this client program and expect it to display "Hello World!" When called from an http client (such as curl).
$ curl http://localhost:8080/hello
Hello World!
Those who can understand the following items by looking at the operation so far have already understood the level described in this article, so there is a possibility that there will be no new discoveries even if you continue reading as it is.
Imagine how various annotations are handled by the reflection API
See why service.hello () succeeds without getting a NullPointerException even though the service field is not instantiating with new
You can see why "/ hello" calls the home () method even though the main method is just running run
Note: This sample has no further functions and is not implemented according to the HTTP specifications, so it is not practical as a framework at all **. As mentioned earlier, the goal is to have a little understanding of what is happening inside the framework.
(Additional notes)
https://github.com/hatimiti/easyframework Please follow the steps below to place the source code in your local environment.
$ cd ~
$ mkdir java
$ git clone https://github.com/hatimiti/easyframework.git
$ cd easyframework
#Run with Gradle. It takes time for the first execution.
$ ./gradlew clean build run
> Task :run
Registered Components => {class hello.SampleServiceImpl=hello.SampleServiceImpl@723279cf, interface hello.SampleService=hello.SampleServiceImpl@723279cf, class hello.SampleController=hello.SampleController@10f87f48}
Registered Controller => /hello - { "path": "/hello", "instance": hello.SampleController@10f87f48", "method": public java.lang.String hello.SampleController.home()" }
<============-> 93% EXECUTING [16s]
> :run
If the above situation occurs, launch another console and check with curl or wget.
$ curl http://localhost:8080/hello
Hello World!
* If the execution is successful, exit the original console with ** ctrl + C **. </ font>
The structure of the source code is as follows.
~/java/easyframework/src
|--main
| |--java
| | |--easyframework
| | | |--Component.java
| | | |--Controller.java
| | | |--EasyApplication.java
| | | |--RequestMapping.java
| | |--hello
| | | |--SampleController.java
| | | |--SampleService.java
| | | |--impl
| | | | |--SampleServiceImpl.java
The sample is divided into the Client (user) side and the Framework (provider) side. As you read, always be aware of which layer you are referring to.
** Annotations ** are features introduced in Java SE 5. What you usually see is "@Override" "[@Deprecated](https: / /docs.oracle.com/javase/jp/8/docs/api/java/lang/Deprecated.html) "" [@SuppressWarnings](https://docs.oracle.com/javase/jp/8/docs/ api / java / lang / SuppressWarnings.html) ”and“ [@FunctionalInterface](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/FunctionalInterface] added in Java 8 Isn't it around ".html)"? These are also annotations.
Annotations are also called "annotation types" and, as the name implies, are types that add (meaning) annotations to source code. For example, "@Override" means that the method is "overridden from the super class", and "@Deprecated" means that the method or type is "deprecated". Can be expressed in the source code.
Annotations are treated as types, so to create your own, define them using the keyword " @ interface </ font>" as well as "class" and "interface".
public @interface Hoge {
}
Annotations can also define attribute information.
public @interface Fuga {
String value() default "";
}
By having attributes, you can give additional information when using annotations.
@Fuga(value = "/hello")
public void x() {
}
If the attribute is named value and you specify only value, you can omit the attribute name.
@Fuga("/hello")
public void x() {
}
If you set the default value with default at the time of definition, you can omit the specification.
@Fuga
public void x() {
}
The following are the annotation definitions required in this article.
src/main/java/easyframework/Controller.java
package easyframework;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Inherited;
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}
src/main/java/easyframework/Component.java
package easyframework;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Inherited;
@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
}
src/main/java/easyframework/RequestMapping.java
package easyframework;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.Inherited;
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
String value() default "";
}
So what's useful with these annotations? Certainly in the sample program
@Controller
public class SampleController {
...
Like, @Controller created earlier is added to the top of the normal class definition.
** Annotate the source code **, but that's all you need to do to comment using Javadoc. Yes, unlike Javadoc, the annotations are part of the source code, not comments </ font>. Since it is part of the source code, you can define how the target annotation works by using the reflection API described later </ font>. Become. Conversely, simply annotating it doesn't do anything, and it's treated the same as (or less than) Javadoc.
Here, if you want to handle unique annotations, you need to use the reflection API </ font>.
In the above annotation definition, the annotation was added to the annotation definition itself. Annotations attached to such annotations are called ** meta annotations **.
# | name | attribute | Overview |
---|---|---|---|
1 | @Inherited | - | Indicates that annotation information is inherited by the child classes of the class to which this annotation is added. |
2 | @Target | ElementType | ElementType indicates where this annotation can be added on the source. |
3 | @Retention | RetentionPolicy | RetentionPolicy indicates how long this annotation information should be retained. |
# | attribute | value | Overview |
---|---|---|---|
1 | ElementType | ANNOTATION_TYPE | Annotation type |
2 | CONSTRUCTOR | constructor | |
3 | FIELD | field | |
4 | LOCAL_VARIABLE | local | |
5 | METHOD | Method | |
6 | PACKAGE | package | |
7 | PARAMETER | argument | |
8 | TYPE | Class, interface, enum | |
9 | TYPE_PARAMETER | Generics(Type parameters) | |
10 | TYPE_USE | Everywhere you use the mold | |
11 | RetentionPolicy | SOURCE | At compile time: ○ class file: × runtime: × |
12 | CLASS | At compile time: ○ class file: ○ runtime: × |
|
13 | RUNTIME | At compile time: ○ class file: ○ runtime: ○ |
Regarding RetentionPolicy, when reading from the reflection API, it is necessary to set it to "RUNTIME", so I think that the number of RUNTIME specifications will naturally increase. All annotations created this time also specify RUNTIME.
In the following samples, ** Reflection API ** (hereinafter referred to as Reflection) is used, so I will introduce it before starting the explanation of the framework part. Reflection is an API for realizing "metaprogramming" in Java, and can handle meta information in a program. The meta information is mainly the class name, the type of the class, what package it is defined in, what fields it has and what type it has, what methods are defined and what the return value is. Information that can be read from the class definition (.class), such as. Reflection also allows you to set values in ** (including private) ** fields and execute methods. As a result, reflection allows you to control your Java program, for better or for worse, by ignoring Java syntax and encapsulation. The framework is designed so that users (clients) can easily develop it by using this reflection in no small measure.
Now let's take a look at a sample that uses reflection. (I'm using jshell, a Java 9 REPL) This is an example of calling an instance of the String class and displaying "Hello, Reflection." On the standard output.
When not using reflection
$ jshell
jshell> System.out.println(new String("Hello, Reflection."))
Hello, Reflection.
When using reflection
$ jshell
jshell> Class<String> strClass = String.class
strClass ==> class java.lang.String
jshell> java.lang.reflect.Constructor c = strClass.getConstructor(String.class)
c ==> public java.lang.String(java.lang.String)
jshell> System.out.println(c.newInstance("Hello, Reflection."))
Hello, Reflection.
In the example that does not use reflection, an instance of the String class is created using the new keyword, but in the example that uses reflection, it is created by calling the newInstance () method of the Constructor type without using the new keyword. .. Also, the base of the Constructor instance is an instance of type Class created by String.class. The Class type is explained in the next section.
In this way, it is possible to create an instance and call a method based on the class definition information.
There are three main ways to get an instance of Class type.
//<Type>.class:Get from type definition
jshell> Class<?> c1 = String.class
c1 ==> class java.lang.String
// Class.getClass():Get from instance
jshell> Class<?> c2 = "Hello".getClass()
c2 ==> class java.lang.String
// Class#forName(String):Get from a string
jshell> Class<?> c3 = Class.forName("java.lang.String")
c3 ==> class java.lang.String
//The acquired class information is the same
jshell> c1 == c2
$13 ==> true
jshell> c2 == c3
$14 ==> true
jshell> c1.equals(c2)
$15 ==> true
jshell> c2.equals(c3)
$16 ==> true
In particular, the specification of "
Logger LOG = LoggerFactory.getLogger(String.class);
You can get various meta information about the class by using the Class instance obtained by the above method. Below are the main methods defined in the Class class. The one used in this sample is excerpted.
# | Return value | Method | static | Overview |
---|---|---|---|---|
1 | Class<?> | forName(String className) | ○ | Returns a Class object associated with a class or interface with the specified string name. |
2 | Annotation[] | getAnnotations() | Returns the annotations present on this element. | |
3 | <A extends Annotation> A | getAnnotation(Class<A> annotationClass) | If it exists, it returns an annotation of the specified type for this element, otherwise it returns null. | |
4 | Field[] | getDeclaredFields() | Returns an array of Field objects that reflect all the fields declared by the class or interface represented by this Class object. | |
5 | Field[] | getFields() | Returns an array holding a Field object that reflects all accessible public fields of the class or interface represented by this Class object. | |
6 | Method[] | getDeclaredMethods() | Returns an array containing a Method object that reflects all the declared methods of the class or interface represented by this Class object. This includes public, protected, and default(package)Includes access and private methods, but excludes inherited methods. | |
7 | Method[] | getMethods() | Returns an array containing a Method object that reflects all public methods of the class or interface represented by this Class object. This includes those declared in the class or interface, as well as those inherited from the superclass or superinterface. | |
8 | boolean | isInterface() | Determines if the specified Class object represents an interface type. | |
9 | boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | Returns true if an annotation of the specified type exists for this element, false otherwise. | |
10 | T | newInstance() | Create a new instance of the class represented by this Class object. |
Example of getting a method of String class
$ jshell
jshell> String.class
$1 ==> class java.lang.String
// getMethods()Does not include private methods, but also includes definitions inherited from the parent class
jshell> Arrays.asList($1.getMethods()).forEach(System.out::println)
public boolean java.lang.String.equals(java.lang.Object)
public int java.lang.String.length()
public java.lang.String java.lang.String.toString()
public int java.lang.String.hashCode()
public void java.lang.String.getChars(int,int,char[],int)
public int java.lang.String.compareTo(java.lang.Object)
public int java.lang.String.compareTo(java.lang.String)
public int java.lang.String.indexOf(int)
public int java.lang.String.indexOf(java.lang.String)
public int java.lang.String.indexOf(java.lang.String,int)
public int java.lang.String.indexOf(int,int)
public static java.lang.String java.lang.String.valueOf(int)
public static java.lang.String java.lang.String.valueOf(char)
public static java.lang.String java.lang.String.valueOf(boolean)
public static java.lang.String java.lang.String.valueOf(float)
public static java.lang.String java.lang.String.valueOf(double)
public static java.lang.String java.lang.String.valueOf(java.lang.Object)
public static java.lang.String java.lang.String.valueOf(long)
public static java.lang.String java.lang.String.valueOf(char[])
public static java.lang.String java.lang.String.valueOf(char[],int,int)
public java.util.stream.IntStream java.lang.String.codePoints()
public boolean java.lang.String.isEmpty()
public char java.lang.String.charAt(int)
public int java.lang.String.codePointAt(int)
public int java.lang.String.codePointBefore(int)
public int java.lang.String.codePointCount(int,int)
public int java.lang.String.offsetByCodePoints(int,int)
public byte[] java.lang.String.getBytes(java.nio.charset.Charset)
public byte[] java.lang.String.getBytes()
public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException
public void java.lang.String.getBytes(int,int,byte[],int)
public boolean java.lang.String.contentEquals(java.lang.CharSequence)
public boolean java.lang.String.contentEquals(java.lang.StringBuffer)
public boolean java.lang.String.equalsIgnoreCase(java.lang.String)
public int java.lang.String.compareToIgnoreCase(java.lang.String)
public boolean java.lang.String.regionMatches(int,java.lang.String,int,int)
public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int)
public boolean java.lang.String.startsWith(java.lang.String)
public boolean java.lang.String.startsWith(java.lang.String,int)
public boolean java.lang.String.endsWith(java.lang.String)
public int java.lang.String.lastIndexOf(java.lang.String,int)
public int java.lang.String.lastIndexOf(java.lang.String)
public int java.lang.String.lastIndexOf(int,int)
public int java.lang.String.lastIndexOf(int)
public java.lang.String java.lang.String.substring(int,int)
public java.lang.String java.lang.String.substring(int)
public java.lang.CharSequence java.lang.String.subSequence(int,int)
public java.lang.String java.lang.String.concat(java.lang.String)
public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence)
public java.lang.String java.lang.String.replace(char,char)
public boolean java.lang.String.matches(java.lang.String)
public boolean java.lang.String.contains(java.lang.CharSequence)
public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String)
public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String,int)
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[])
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable)
public java.lang.String java.lang.String.toLowerCase(java.util.Locale)
public java.lang.String java.lang.String.toLowerCase()
public java.lang.String java.lang.String.toUpperCase(java.util.Locale)
public java.lang.String java.lang.String.toUpperCase()
public java.lang.String java.lang.String.trim()
public java.util.stream.IntStream java.lang.String.chars()
public char[] java.lang.String.toCharArray()
public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[])
public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[])
public static java.lang.String java.lang.String.copyValueOf(char[])
public static java.lang.String java.lang.String.copyValueOf(char[],int,int)
public native java.lang.String java.lang.String.intern()
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
// getDeclaredMethods()Includes private methods, but does not include definitions inherited from the parent class
jshell> Arrays.asList($1.getDeclaredMethods()).forEach(System.out::println)
byte java.lang.String.coder()
public boolean java.lang.String.equals(java.lang.Object)
public int java.lang.String.length()
public java.lang.String java.lang.String.toString()
public int java.lang.String.hashCode()
public void java.lang.String.getChars(int,int,char[],int)
public int java.lang.String.compareTo(java.lang.Object)
public int java.lang.String.compareTo(java.lang.String)
public int java.lang.String.indexOf(int)
static int java.lang.String.indexOf(byte[],byte,int,java.lang.String,int)
public int java.lang.String.indexOf(java.lang.String)
public int java.lang.String.indexOf(java.lang.String,int)
public int java.lang.String.indexOf(int,int)
static void java.lang.String.checkIndex(int,int)
public static java.lang.String java.lang.String.valueOf(int)
public static java.lang.String java.lang.String.valueOf(char)
public static java.lang.String java.lang.String.valueOf(boolean)
public static java.lang.String java.lang.String.valueOf(float)
public static java.lang.String java.lang.String.valueOf(double)
public static java.lang.String java.lang.String.valueOf(java.lang.Object)
public static java.lang.String java.lang.String.valueOf(long)
public static java.lang.String java.lang.String.valueOf(char[])
public static java.lang.String java.lang.String.valueOf(char[],int,int)
private static java.lang.Void java.lang.String.rangeCheck(char[],int,int)
public java.util.stream.IntStream java.lang.String.codePoints()
public boolean java.lang.String.isEmpty()
public char java.lang.String.charAt(int)
public int java.lang.String.codePointAt(int)
public int java.lang.String.codePointBefore(int)
public int java.lang.String.codePointCount(int,int)
public int java.lang.String.offsetByCodePoints(int,int)
public byte[] java.lang.String.getBytes(java.nio.charset.Charset)
void java.lang.String.getBytes(byte[],int,byte)
public byte[] java.lang.String.getBytes()
public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException
public void java.lang.String.getBytes(int,int,byte[],int)
public boolean java.lang.String.contentEquals(java.lang.CharSequence)
public boolean java.lang.String.contentEquals(java.lang.StringBuffer)
private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder)
public boolean java.lang.String.equalsIgnoreCase(java.lang.String)
public int java.lang.String.compareToIgnoreCase(java.lang.String)
public boolean java.lang.String.regionMatches(int,java.lang.String,int,int)
public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int)
public boolean java.lang.String.startsWith(java.lang.String)
public boolean java.lang.String.startsWith(java.lang.String,int)
public boolean java.lang.String.endsWith(java.lang.String)
public int java.lang.String.lastIndexOf(java.lang.String,int)
public int java.lang.String.lastIndexOf(java.lang.String)
public int java.lang.String.lastIndexOf(int,int)
public int java.lang.String.lastIndexOf(int)
static int java.lang.String.lastIndexOf(byte[],byte,int,java.lang.String,int)
public java.lang.String java.lang.String.substring(int,int)
public java.lang.String java.lang.String.substring(int)
public java.lang.CharSequence java.lang.String.subSequence(int,int)
public java.lang.String java.lang.String.concat(java.lang.String)
public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence)
public java.lang.String java.lang.String.replace(char,char)
public boolean java.lang.String.matches(java.lang.String)
public boolean java.lang.String.contains(java.lang.CharSequence)
public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String)
public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String,int)
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[])
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable)
public java.lang.String java.lang.String.toLowerCase(java.util.Locale)
public java.lang.String java.lang.String.toLowerCase()
public java.lang.String java.lang.String.toUpperCase(java.util.Locale)
public java.lang.String java.lang.String.toUpperCase()
public java.lang.String java.lang.String.trim()
public java.util.stream.IntStream java.lang.String.chars()
public char[] java.lang.String.toCharArray()
public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[])
public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[])
public static java.lang.String java.lang.String.copyValueOf(char[])
public static java.lang.String java.lang.String.copyValueOf(char[],int,int)
public native java.lang.String java.lang.String.intern()
private boolean java.lang.String.isLatin1()
static void java.lang.String.checkOffset(int,int)
static void java.lang.String.checkBoundsOffCount(int,int,int)
static void java.lang.String.checkBoundsBeginEnd(int,int,int)
static byte[] java.lang.String.access$100(java.lang.String)
static boolean java.lang.String.access$200(java.lang.String)
// getFields()Does not include private fields, but also includes definitions inherited from the parent class
jshell> Arrays.asList($1.getFields()).forEach(System.out::println)
public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER
// getDeclaredFields()Contains private, but does not include definitions inherited from the parent class
jshell> Arrays.asList($1.getDeclaredFields()).forEach(System.out::println)
private final byte[] java.lang.String.value
private final byte java.lang.String.coder
private int java.lang.String.hash
private static final long java.lang.String.serialVersionUID
static final boolean java.lang.String.COMPACT_STRINGS
private static final java.io.ObjectStreamField[] java.lang.String.serialPersistentFields
public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER
static final byte java.lang.String.LATIN1
static final byte java.lang.String.UTF16
Reflection type definitions other than Class class are mainly ** [java.lang.reflect](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/reflect/package-summary. html) ** Defined under the package. This is also an excerpt of the one used in this sample.
# | class | Return value | Method | Overview |
---|---|---|---|---|
1 | Field | boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | Returns true if an annotation of the specified type exists for this element, false otherwise. |
2 | void | setAccessible(boolean flag) | Sets the accessible flag for this object to the specified boolean value. | |
3 | void | set(Object obj, Object value) | Sets the field of the specified object argument represented by this Field object to the new value specified. | |
4 | Method | boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | Returns true if an annotation of the specified type exists for this element, false otherwise. |
5 | <T extends Annotation> T | getAnnotation(Class<T> annotationClass) | If it exists, it returns an annotation of the specified type for this element, otherwise it returns null. | |
6 | Object | invoke(Object obj, Object... args) | Calls the underlying method represented by this Method object with the specified parameters for the specified object. |
The points to be noted are as follows.
From now on, I will introduce the structure of EasyFramework.java, which is the core of the framework. The first is the run () method, which is the only public method of this framework.
src/main/java/easyframework/EasyApplication.java
package easyframework;
public class EasyApplication {
...
public static void run(Class<?> clazz, String... args) {
scanComponents(clazz);
injectDependencies();
registerHTTPPaths();
startHTTPServer();
}
...
}
It calls the following four methods.
I will describe the details of each.
This framework manages the execution point (Controller) of HTTP request described later and the instance </ font> group required inside the framework to realize DI. Here, these ** instances under the control of the framework are referred to as "components" **. Similarly for Spring Boot, [@ComponentScan](https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan. By using java) annotation, it will automatically find the component to manage. At that time, which class is managed as a component is determined by whether the "@Controller" or "@Component" annotation is added.
Here, the search result is registered in the components field of EasyApplication.
src/main/java/easyframework/EasyApplication.java
public class EasyApplication {
/** KEY=the class of the component, VALUE= A instance of the component */
private final static Map<Class<?>, Object> components = new HashMap<>();
...
src/main/java/easyframework/EasyApplication.java
private static List<Class<?>> scanClassesUnder(String packageName) {
//Package name.Break/Convert to delimiter.
String packagePath = packageName.replace('.', '/');
//Search the resource of the target path from the class loader.
ClassLoader cl = Thread.currentThread().getContextClassLoader();
URL root = cl.getResource(packagePath);
// .Get the class file.
File[] files = new File(root.getFile())
.listFiles((dir, name) -> name.endsWith(".class"));
return Arrays.asList(files).stream()
.map(file -> packageName + "." + file.getName().replaceAll(".class$", ""))
//package name+Based on the class name, get the Class class instant and return it as a List.
.map(fullName -> uncheck(() -> Class.forName(fullName)))
.collect(Collectors.toList());
}
src/main/java/easyframework/EasyApplication.java
// @With Component annotation@Treat the type with Controller annotation as a component
private static boolean hasComponentAnnotation(Class<?> clazz) {
return clazz.isAnnotationPresent(Component.class)
|| clazz.isAnnotationPresent(Controller.class);
}
private static void scanComponents(Class<?> source) {
List<Class<?>> classes = scanClassesUnder(source.getPackage().getName());
classes.stream()
.filter(clazz -> hasComponentAnnotation(clazz))
.forEach(clazz -> uncheck(() -> {
//Create and register an instance of the component target type
Object instance = clazz.newInstance();
components.put(clazz, instance);
}));
System.out.println("Registered Components => " + components);
}
DI(Dependency Injection)
The reason I didn't get a NullPointerException when calling the method, even though I didn't create a new instance of type SampleService in the first sample, is because I used this ** DI ** mechanism.
src/main/java/hello/SampleController.java
@Resource
private SampleService service; //← Shouldn't it be null because it's not new?
@RequestMapping("/hello")
public String home() {
return service.hello(); //← Why NullPointerException does not occur! ??
}
It can be translated as DI = Dependency Injection, but what is ** Dependency **?
Think of it as dependency = object (instance) here. Therefore, DI is a mechanism for injecting objects, and it is possible to loosely couple classes. FactoryMethod is a design pattern for loosely coupling between classes, but DI doesn't even need to use factory methods or the new keyword, so it can be loosely coupled one step further.
The part that is DI in this framework is the following processing.
src/main/java/easyframework/EasyApplication.java
private static void injectDependencies() {
//Pre-scanned and registered components(components)Check all fields.
components.forEach((clazz, component) -> {
Arrays.asList(clazz.getDeclaredFields()).stream()
// @Get only the fields with Resource annotation.
.filter(field -> field.isAnnotationPresent(Resource.class))
.forEach(field -> uncheck(() -> {
//Make it acccessible so that it can be handled even in private fields.
field.setAccessible(true);
//component(components)Get an instance of the type from and inject(Setting)To do.
field.set(component, components.get(field.getType()));
}));
});
}
The definition of SampleService to be injected this time is already registered as a component because @Component is added as shown below.
src/main/java/hello/impl/SampleServiceImpl.java
@Component
public class SampleServiceImpl implements SampleService {
...
Since @Resource is added to the field of SampleController that is the injection destination and the type matches, the instance is injected by the injectDependencies () method that we saw earlier.
src/main/java/hello/SampleController.java
@Controller
public class SampleController {
@Resource
private SampleService service; //← Instances under framework management are injected here
...
With this mechanism, calling service.hello () will not result in a NullPointerException.
The framework that introduces the DI mechanism is called a "DI container". SpringFramework, Google Guice, Seasar2, etc. are DI containers.
The benefits of introducing DI are as follows.
This section handles the callpoint registration process when "http : // localhost: 8080 / hello" is called from an HTTP client. The part where the corresponding processing is performed in the framework is as follows.
Register the result in the controllers field of EasyApplication.
src/main/java/easyframework/EasyApplication.java
public class EasyApplication {
...
/** KEY=path, VALUE= A instance and methods */
private final static Map<String, HTTPController> controllers = new HashMap<>();
...
src/main/java/easyframework/EasyApplication.java
private static void registerHTTPPaths() {
//Check all components under framework management.
components.entrySet().stream()
//Among them@Limited to classes with Controller attached.
.filter(kv -> kv.getKey().isAnnotationPresent(Controller.class))
.forEach(kv ->
//Get the method of Controller.
Arrays.asList(kv.getKey().getMethods()).stream()
// @Limited to methods with RequestMapping annotation
.filter(m -> m.isAnnotationPresent(RequestMapping.class))
.forEach(m -> {
//Get information of RequestMapping annotation
RequestMapping rm = m.getAnnotation(RequestMapping.class);
//Gets the attribute value field that sets the HTTP call point.( rm.value() )
HTTPController c = new HTTPController(rm.value(), kv.getValue(), m);
//Register as Controller.
controllers.put(rm.value(), c);
System.out.println("Registered Controller => " + rm.value() + " - " + c);
})
);
}
The part that listens for a connection from an HTTP client and calls the method of the corresponding Controller is as follows.
private static class EasyHttpServer implements Closeable {
...
public void start() {
while (true) {
acceptRequest:
//Wait for a request from a client.( server.accept() )
try (Socket socket = server.accept();
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream(), "UTF-8"))) {
// br.readLine() => GET /hello HTTP/1.1
String path = br.readLine().split(" ")[1];
try (PrintStream os = new PrintStream(socket.getOutputStream())) {
//Requesting path/Extract the registered Controller based on hello.
HTTPController c = controllers.get(path);
if (c == null) {
os.println("404 Not Found (path = " + path + " ).");
break acceptRequest;
}
//Call the Controller method.( method.invoke )
os.println(c.method.invoke(c.instance));
}
} catch (IOException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
}
...
}
AOP
AOP stands for Aspect Oriented Programming and is a programming method that focuses on "cross-cutting concerns". In object-oriented programming, the processing specific to this object is handled together in individual definitions (classes, etc.), but processing that straddles them (cross-cutting concern) may be necessary. For example ...
Etc.
By using AOP, it is possible to insert processing across the board without editing the client program, which is the business program layer.
Here, in order to realize AOP, "[java.lang.reflect.Proxy](https://docs.oracle.com/javase/jp/8/docs/api/java/" provided as standard in Java lang / reflect / Proxy.html) "and" java.lang.reflect.InvocationHandler ) ”Is introduced.
If you specify the source object and the cross-cutting process (InvocationHandler) that you want to insert in the Proxy # newProxyInstance () method, a new instance that incorporates that process will be returned. Here, AOP is realized by instantly overwriting the created instance for DI.
When using Proxy for AOP, the target type must implement the interface. Here we want to intercept the SampleService interface.
src/main/java/easyframework/EasyApplication.java
private static void registerAOP() {
components.entrySet().stream()
.filter(kv -> kv.getKey().isInterface())
.forEach(kv -> components.put(kv.getKey(),
Interceptor.createProxiedTarget(kv.getValue())));
System.out.println("Registered AOP => " + components);
}
src/main/java/easyframework/EasyApplication.java
package easyframework;
public class EasyApplication {
...
public static void run(Class<?> clazz, String... args) {
scanComponents(clazz);
registerAOP(); //← Add this part
injectDependencies();
registerHTTPPaths();
startHTTPServer();
}
...
}
src/main/java/easyframework/Interceptor.java
package easyframework;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
class Interceptor implements InvocationHandler {
private final Object target;
public static Object createProxiedTarget(Object target) {
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new Interceptor(target));
}
private Interceptor(Object obj) {
this.target = obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
}
}
$ git checkout -b handsonAOP remotes/origin/handsonAOP
src/main/java/easyframework/Interceptor.java
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(target, args);
}
return invoke(method, args);
}
private Object invoke(Method method, Object[] args) throws Throwable {
/*
*In the target method or type definition@When Transactional annotation is added
*Intercept the process that operates as follows.
* 1.Before executing the corresponding method"Starts transaction."Is displayed on the standard output.
* 2.When the corresponding method execution ends normally"Commit transaction."Is displayed on the standard output.
* 3.When the corresponding method execution ends with an exception"Rollbak transaction."Is displayed on the standard output and the exception is thrown at the top.
*
* ※ @If Transactional is not added, return only the execution result of the corresponding method.
* ※ @Transactional annotation is easyframework.Transactional.Create a new one as java.
* ※ ./Run with gradlew clean build run and curl http://localhost:8080/Check with hello.
*/
return null;
}
The answer is in the addAOP branch.
$ git checkout remotes/origin/addAOP
$ less src/main/java/easyframework/Interceptor.java
For reference, the Spring Boot sample [spring-boot-sample-tomcat](https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample- tomcat) Check what kind of libraries the project consists of.
$ git clone https://github.com/spring-projects/spring-boot.git
$ cd spring-boot/spring-boot-samples/spring-boot-sample-tomcat/
$ mvn dependency:tree
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Spring Boot Tomcat Sample 2.0.0.BUILD-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:2.10:tree (default-cli) @ spring-boot-sample-tomcat ---
[INFO] org.springframework.boot:spring-boot-sample-tomcat:jar:2.0.0.BUILD-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter:jar:2.0.0.BUILD-SNAPSHOT:compile
[INFO] | +- org.springframework.boot:spring-boot:jar:2.0.0.BUILD-SNAPSHOT:compile
[INFO] | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.0.0.BUILD-SNAPSHOT:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-logging:jar:2.0.0.BUILD-SNAPSHOT:compile
[INFO] | | +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] | | | \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] | | +- org.slf4j:jul-to-slf4j:jar:1.7.25:compile
[INFO] | | \- org.slf4j:log4j-over-slf4j:jar:1.7.25:compile
[INFO] | +- org.springframework:spring-core:jar:5.0.0.RC3:compile
[INFO] | | \- org.springframework:spring-jcl:jar:5.0.0.RC3:compile
[INFO] | \- org.yaml:snakeyaml:jar:1.18:runtime
[INFO] +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.0.0.BUILD-SNAPSHOT:compile
[INFO] | +- org.apache.tomcat.embed:tomcat-embed-core:jar:8.5.16:compile
[INFO] | +- org.apache.tomcat.embed:tomcat-embed-el:jar:8.5.16:compile
[INFO] | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:8.5.16:compile
[INFO] +- org.springframework:spring-webmvc:jar:5.0.0.RC3:compile
[INFO] | +- org.springframework:spring-aop:jar:5.0.0.RC3:compile
[INFO] | +- org.springframework:spring-beans:jar:5.0.0.RC3:compile
[INFO] | +- org.springframework:spring-context:jar:5.0.0.RC3:compile
[INFO] | +- org.springframework:spring-expression:jar:5.0.0.RC3:compile
[INFO] | \- org.springframework:spring-web:jar:5.0.0.RC3:compile
[INFO] \- org.springframework.boot:spring-boot-starter-test:jar:2.0.0.BUILD-SNAPSHOT:test
[INFO] +- org.springframework.boot:spring-boot-test:jar:2.0.0.BUILD-SNAPSHOT:test
[INFO] +- org.springframework.boot:spring-boot-test-autoconfigure:jar:2.0.0.BUILD-SNAPSHOT:test
[INFO] +- com.jayway.jsonpath:json-path:jar:2.4.0:test
[INFO] | +- net.minidev:json-smart:jar:2.3:test
[INFO] | | \- net.minidev:accessors-smart:jar:1.2:test
[INFO] | | \- org.ow2.asm:asm:jar:5.0.4:test
[INFO] | \- org.slf4j:slf4j-api:jar:1.7.25:compile
[INFO] +- junit:junit:jar:4.12:test
[INFO] +- org.assertj:assertj-core:jar:3.8.0:test
[INFO] +- org.mockito:mockito-core:jar:2.8.47:test
[INFO] | +- net.bytebuddy:byte-buddy:jar:1.6.14:test
[INFO] | +- net.bytebuddy:byte-buddy-agent:jar:1.6.14:test
[INFO] | \- org.objenesis:objenesis:jar:2.5:test
[INFO] +- org.hamcrest:hamcrest-core:jar:1.3:test
[INFO] +- org.hamcrest:hamcrest-library:jar:1.3:test
[INFO] +- org.skyscreamer:jsonassert:jar:1.5.0:test
[INFO] | \- com.vaadin.external.google:android-json:jar:0.0.20131108.vaadin1:test
[INFO] \- org.springframework:spring-test:jar:5.0.0.RC3:test
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.507 s
[INFO] Finished at: 2017-08-20T17:50:33+09:00
[INFO] Final Memory: 21M/309M
[INFO] ------------------------------------------------------------------------
There are many others.
Further details, such as those mentioned in this article, can be found in the books below. If you are interested, please read it.
Recommended Posts