Aujourd'hui, nous entendons souvent des mots comme «cloud natif» et «microservices» dans la communauté des applications Web. Le développement Java utilise souvent des ** frameworks ** tels que Spring Boot pour implémenter ces architectures.
Cependant, quand je n'étais pas si familier avec le comportement de Java et que j'ai soudainement commencé à développer en utilisant un tel ** framework **, "Pourquoi ça marche de cette façon?" Vous pouvez vous retrouver dans une situation comme "N'est-ce pas?"
Dans cet article, afin de dissiper ces doutes, j'espère pouvoir créer moi-même un framework simple super ~~ similaire ~~ et comprendre autant que possible le mécanisme interne du framework Java. Je vais.
Démarrage rapide de Spring Boot En référence à https://projects.spring.io/spring-boot/#quick-start, les programmes clients qui utilisent le framework réalisé cette fois sont les suivants.
src/main/java/hello/SampleController.java
package hello;
...<importation omise>
@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);
}
}
Exécutez ce programme client et attendez-vous à ce qu'il affiche "Hello World!" Lorsqu'il est appelé depuis un client http (tel que curl).
$ curl http://localhost:8080/hello
Hello World!
Ceux qui peuvent comprendre les éléments suivants en regardant l'opération jusqu'à présent ont déjà compris le niveau décrit dans cet article, il est donc possible qu'il n'y ait pas de nouvelles découvertes même si vous continuez à lire tel quel.
Imaginez comment différentes annotations sont gérées par l'API de réflexion
Comprendre pourquoi service.hello () réussit sans obtenir une NullPointerException même si le champ de service n'est pas instancié avec new
Vous pouvez voir pourquoi "/ hello" appelle la méthode home () même si la méthode principale exécute simplement run
Remarque: cet exemple n'a pas d'autres fonctions et n'est pas implémenté selon les spécifications HTTP, il n'est donc pas pratique du tout en tant que framework **. Comme mentionné précédemment, l'objectif est d'avoir un peu de compréhension de ce qui se passe à l'intérieur du cadre.
(Notes complémentaires)
https://github.com/hatimiti/easyframework Suivez la procédure ci-dessous pour placer le code source dans votre environnement local.
$ cd ~
$ mkdir java
$ git clone https://github.com/hatimiti/easyframework.git
$ cd easyframework
#Courez avec Gradle. Cela prend du temps à la première exécution.
$ ./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
Si la situation ci-dessus se produit, lancez une autre console et vérifiez avec curl ou wget.
$ curl http://localhost:8080/hello
Hello World!
* Si l'exécution réussit, quittez la console d'origine avec ** ctrl + C **. </ font>
La structure du code source est la suivante.
~/java/easyframework/src
|--main
| |--java
| | |--easyframework
| | | |--Component.java
| | | |--Controller.java
| | | |--EasyApplication.java
| | | |--RequestMapping.java
| | |--hello
| | | |--SampleController.java
| | | |--SampleService.java
| | | |--impl
| | | | |--SampleServiceImpl.java
L'échantillon est divisé entre le côté client (utilisateur) et le côté Framework (fournisseur). En lisant, sachez toujours à quelle couche vous faites référence.
** Les annotations ** sont des fonctionnalités introduites dans Java SE 5. Ce que vous voyez habituellement est "@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) »et« [@FunctionalInterface](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/FunctionalInterface] ajoutés dans Java 8 N'est-ce pas autour de ".html)"? Ce sont aussi des annotations.
Les annotations sont également appelées «types d'annotations» et, comme leur nom l'indique, sont des types qui ajoutent (signifient) des annotations au code source. Par exemple, "@Override" signifie que la méthode est "remplacée par la super classe" et "@Deprecated" signifie que la méthode ou le type est "obsolète". Peut être exprimé dans le code source.
Comme les annotations sont traitées comme des types, pour créer les vôtres, définissez-les en utilisant le mot-clé " @ interface </ font>" ainsi que "class" et "interface".
public @interface Hoge {
}
Les annotations peuvent également définir des informations d'attribut.
public @interface Fuga {
String value() default "";
}
En ayant des attributs, vous pouvez donner des informations supplémentaires lors de l'utilisation d'annotations.
@Fuga(value = "/hello")
public void x() {
}
Si l'attribut est nommé valeur et que seule la valeur est spécifiée, le nom d'attribut peut être omis.
@Fuga("/hello")
public void x() {
}
Si vous définissez la valeur par défaut avec la valeur par défaut au moment de la définition, vous pouvez omettre la spécification.
@Fuga
public void x() {
}
Voici les définitions d'annotations requises dans cet 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 "";
}
Alors, qu'est-ce qui est utile avec ces annotations? Certainement dans l'exemple de programme
@Controller
public class SampleController {
...
Comme, le @Controller créé précédemment est ajouté en haut de la définition de classe normale.
** Annotez le code source **, mais c'est tout ce que vous avez à faire pour commenter en utilisant Javadoc. Oui, contrairement à Javadoc, l'annotation fait partie du code source, pas un commentaire </ font>. Puisqu'il fait partie du code source, vous pouvez définir vous-même comment l'annotation cible fonctionne à l'aide de l'API de réflexion décrite plus loin </ font>. Devenir. Inversement, ajouter simplement une annotation ne fait rien, et elle est traitée de la même manière que (ou moins) Javadoc.
Ici, si vous souhaitez gérer des annotations uniques , vous devez utiliser l'API de réflexion </ font>.
Dans la définition d'annotation ci-dessus, une annotation a été ajoutée à la définition d'annotation elle-même. L'annotation attachée à une telle annotation est appelée ** méta-annotation **.
# | Nom | attribut | Aperçu |
---|---|---|---|
1 | @Inherited | - | Indique que les informations d'annotation sont héritées par les classes enfants de la classe à laquelle cette annotation est ajoutée. |
2 | @Target | ElementType | ElementType indique où sur la source cette annotation peut être ajoutée. |
3 | @Retention | RetentionPolicy | RetentionPolicy indique combien de temps ces informations d'annotation seront conservées. |
# | attribut | valeur | Aperçu |
---|---|---|---|
1 | ElementType | ANNOTATION_TYPE | Type annoté |
2 | CONSTRUCTOR | constructeur | |
3 | FIELD | champ | |
4 | LOCAL_VARIABLE | local | |
5 | METHOD | Méthode | |
6 | PACKAGE | paquet | |
7 | PARAMETER | argument | |
8 | TYPE | Classe, interface, énumération | |
9 | TYPE_PARAMETER | Génériques(Paramètres de type) | |
10 | TYPE_USE | Partout où vous utilisez le moule | |
11 | RetentionPolicy | SOURCE | Au moment de la compilation: ○ fichier de classe: × Durée: × |
12 | CLASS | Au moment de la compilation: ○ fichier de classe: ○ Durée: × |
|
13 | RUNTIME | Au moment de la compilation: ○ fichier de classe: ○ Durée: ○ |
Concernant RetentionPolicy, lors de la lecture de l'API de réflexion, il est nécessaire de le paramétrer sur "RUNTIME", donc je pense que le nombre de spécifications RUNTIME augmentera naturellement. Toutes les annotations créées cette fois spécifient également RUNTIME.
Dans les exemples suivants, ** l'API de réflexion ** (ci-après appelée réflexion) est utilisée, je vais donc l'introduire avant de commencer l'explication de la partie framework. Reflection est une API pour réaliser une "métaprogrammation" en Java, et peut gérer des méta-informations dans un programme. Les méta-informations sont principalement le nom de la classe, le type de la classe, le package dans lequel elle est définie, le type de champ dont elle dispose et le type dont elle dispose, le type de méthode défini et la valeur de retour. Informations pouvant être lues à partir de la définition de classe (.class), telles que. Reflection vous permet également de définir des valeurs dans des champs ** (y compris privés) ** et d'exécuter des méthodes. Par conséquent, la réflexion vous permet de contrôler votre programme Java, pour le meilleur ou pour le pire, en ignorant la syntaxe et l'encapsulation Java. Le cadre est conçu pour que les utilisateurs (clients) puissent le développer facilement en utilisant cette réflexion dans une large mesure.
Voyons maintenant un exemple qui utilise la réflexion. (J'utilise jshell, qui est une REPL Java 9) Voici un exemple de l'appel d'une instance de la classe String et de l'affichage de "Hello, Reflection" dans la sortie standard.
Lorsque vous n'utilisez pas la réflexion
$ jshell
jshell> System.out.println(new String("Hello, Reflection."))
Hello, Reflection.
Lors de l'utilisation de la réflexion
$ 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.
Dans l'exemple qui n'utilise pas de réflexion, une instance de la classe String est créée à l'aide du mot clé new, mais dans l'exemple qui utilise la réflexion, elle est créée en appelant la méthode newInstance () du type Constructor sans utiliser le mot clé new. .. En outre, l'instance du constructeur est basée sur une instance de type Class créée par String.class. Le type de classe est décrit dans la section suivante.
De cette manière, il est possible de créer une instance et d'appeler une méthode basée sur les informations de définition de la classe.
Il existe trois méthodes principales pour obtenir une instance du type Class.
//<Type>.class:Obtenir à partir de la définition de type
jshell> Class<?> c1 = String.class
c1 ==> class java.lang.String
// Class.getClass():Obtenir de l'instance
jshell> Class<?> c2 = "Hello".getClass()
c2 ==> class java.lang.String
// Class#forName(String):Obtenir à partir d'une chaîne
jshell> Class<?> c3 = Class.forName("java.lang.String")
c3 ==> class java.lang.String
//Les informations de classe acquises sont les mêmes
jshell> c1 == c2
$13 ==> true
jshell> c2 == c3
$14 ==> true
jshell> c1.equals(c2)
$15 ==> true
jshell> c2.equals(c3)
$16 ==> true
En particulier, la spécification de "
Logger LOG = LoggerFactory.getLogger(String.class);
En utilisant l'instance de classe obtenue par la méthode ci-dessus, diverses méta-informations sur la classe peuvent être obtenues. Voici les principales méthodes définies dans la classe Class. Celui utilisé dans cet exemple est extrait.
# | Valeur de retour | Méthode | static | Aperçu |
---|---|---|---|---|
1 | Class<?> | forName(String className) | ○ | Renvoie un objet Class associé à une classe ou une interface avec le nom de chaîne spécifié. |
2 | Annotation[] | getAnnotations() | Renvoie les annotations présentes sur cet élément. | |
3 | <A extends Annotation> A | getAnnotation(Class<A> annotationClass) | Renvoie une annotation du type spécifié pour cet élément, s'il existe, ou null dans le cas contraire. | |
4 | Field[] | getDeclaredFields() | Renvoie un tableau d'objets Field qui reflètent tous les champs déclarés par la classe ou l'interface représentée par cet objet Class. | |
5 | Field[] | getFields() | Renvoie un tableau contenant un objet Field qui reflète tous les champs publics accessibles de la classe ou de l'interface représentée par cet objet Class. | |
6 | Method[] | getDeclaredMethods() | Renvoie un tableau contenant un objet Method qui reflète toutes les méthodes déclarées de la classe ou de l'interface représentée par cet objet Class. Cela inclut public, protégé, par défaut(package)Inclut les méthodes d'accès et privées, mais exclut les méthodes héritées. | |
7 | Method[] | getMethods() | Renvoie un tableau contenant un objet Method qui reflète toutes les méthodes publiques de la classe ou de l'interface représentée par cet objet Class. Cela inclut ceux déclarés dans les classes ou les interfaces, ainsi que ceux hérités des superclasses et des superinterfaces. | |
8 | boolean | isInterface() | Détermine si l'objet Class spécifié représente un type d'interface. | |
9 | boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | Renvoie true si un commentaire du type spécifié existe sur cet élément, false dans le cas contraire. | |
10 | T | newInstance() | Créez une nouvelle instance de la classe représentée par cet objet Class. |
Exemple d'obtention d'une méthode de la classe String
$ jshell
jshell> String.class
$1 ==> class java.lang.String
// getMethods()N'inclut pas les méthodes privées, mais inclut également les définitions héritées de la classe parente
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()Contient des méthodes privées, mais n'inclut pas les définitions héritées de la classe parent
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()N'inclut pas les champs privés, mais inclut également les définitions héritées de la classe parent
jshell> Arrays.asList($1.getFields()).forEach(System.out::println)
public static final java.util.Comparator java.lang.String.CASE_INSENSITIVE_ORDER
// getDeclaredFields()Contient privé, mais n'inclut pas les définitions héritées de la classe parent
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
Les définitions de type de réflexion autres que la classe Class sont principalement ** [java.lang.reflect](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/reflect/package-summary. html) ** Défini sous le package. Ceci est également un extrait de celui utilisé dans cet exemple.
# | classe | Valeur de retour | Méthode | Aperçu |
---|---|---|---|---|
1 | Field | boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | Renvoie true si un commentaire du type spécifié existe sur cet élément, false dans le cas contraire. |
2 | void | setAccessible(boolean flag) | Définit l'indicateur accessible pour cet objet sur la valeur booléenne spécifiée. | |
3 | void | set(Object obj, Object value) | Définit le champ de l'argument d'objet spécifié représenté par cet objet Field sur la nouvelle valeur spécifiée. | |
4 | Method | boolean | isAnnotationPresent(Class<? extends Annotation> annotationClass) | Renvoie true si un commentaire du type spécifié existe sur cet élément, false dans le cas contraire. |
5 | <T extends Annotation> T | getAnnotation(Class<T> annotationClass) | Renvoie une annotation du type spécifié pour cet élément, s'il existe, ou null dans le cas contraire. | |
6 | Object | invoke(Object obj, Object... args) | Appelle la méthode sous-jacente représentée par cet objet Method avec les paramètres spécifiés pour l'objet spécifié. |
Les points à noter sont les suivants.
Ci-après, nous présenterons la configuration d'EasyFramework.java, qui est le cœur du framework. La première est la méthode run (), qui est la seule méthode publique de ce 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();
}
...
}
Il appelle les quatre méthodes suivantes.
Je décrirai les détails de chacun.
Ce framework gère le point d'exécution (Controller) de la requête HTTP décrite plus loin et le groupe instances </ font> requis dans le framework pour réaliser DI. Ici, ces ** instances sous le contrôle du framework sont appelées «composants» **. De même pour Spring Boot, [@ComponentScan](https://github.com/spring-projects/spring-framework/blob/master/spring-context/src/main/java/org/springframework/context/annotation/ComponentScan. En utilisant l'annotation java), il trouvera automatiquement le composant à gérer. À ce moment-là, la classe gérée en tant que composant est déterminée par l'ajout ou non de l'annotation "@Controller" ou "@Component".
Ici, le résultat recherché est enregistré dans le champ des composants d'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) {
//Nom du paquet.Pause/Convertir en délimiteur.
String packagePath = packageName.replace('.', '/');
//Recherchez des ressources dans le chemin cible à partir du chargeur de classe.
ClassLoader cl = Thread.currentThread().getContextClassLoader();
URL root = cl.getResource(packagePath);
// .Obtenez le fichier de classe.
File[] files = new File(root.getFile())
.listFiles((dir, name) -> name.endsWith(".class"));
return Arrays.asList(files).stream()
.map(file -> packageName + "." + file.getName().replaceAll(".class$", ""))
//nom du paquet+En fonction du nom de la classe, récupérez la classe instantanée de la classe et renvoyez-la sous forme de liste.
.map(fullName -> uncheck(() -> Class.forName(fullName)))
.collect(Collectors.toList());
}
src/main/java/easyframework/EasyApplication.java
// @Avec annotation de composant@Traitez le type avec l'annotation Controller comme un composant
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(() -> {
//Créer et enregistrer une instance du type de cible de composant
Object instance = clazz.newInstance();
components.put(clazz, instance);
}));
System.out.println("Registered Components => " + components);
}
DI(Dependency Injection)
La raison pour laquelle je n'ai pas obtenu d'exception NullPointerException lors de l'appel d'une méthode, même si je n'ai pas créé une nouvelle instance de type SampleService dans le premier exemple, est que j'ai utilisé ce mécanisme ** DI **.
src/main/java/hello/SampleController.java
@Resource
private SampleService service; //← Il devrait être nul car ce n'est pas nouveau?
@RequestMapping("/hello")
public String home() {
return service.hello(); //← Pourquoi NullPointerException ne se produit pas! ??
}
Cela peut être traduit par DI = injection de dépendances, mais qu'est-ce que la ** dépendance **?
Pensez-y comme dépendance = objet (instance) ici. Par conséquent, DI est un mécanisme d'injection d'objets, et il est possible de coupler librement entre les classes. FactoryMethod est un modèle de conception pour un couplage lâche entre les classes, mais DI ne nécessite même pas l'utilisation de méthodes d'usine ou du nouveau mot-clé, de sorte qu'il peut être couplé de manière lâche une étape supplémentaire.
Les parties qui sont DI dans ce cadre sont les processus suivants.
src/main/java/easyframework/EasyApplication.java
private static void injectDependencies() {
//Composants pré-numérisés et enregistrés(components)Vérifiez tous les champs.
components.forEach((clazz, component) -> {
Arrays.asList(clazz.getDeclaredFields()).stream()
// @Obtenez uniquement les champs avec l'annotation Resource.
.filter(field -> field.isAnnotationPresent(Resource.class))
.forEach(field -> uncheck(() -> {
//Rendez-le accessible afin qu'il puisse être manipulé même dans des champs privés.
field.setAccessible(true);
//composant(components)Obtenir une instance du type à partir de et injecter(Réglage)Faire.
field.set(component, components.get(field.getType()));
}));
});
}
La définition de SampleService à injecter cette fois est déjà enregistrée en tant que composant car @Component est ajouté comme indiqué ci-dessous.
src/main/java/hello/impl/SampleServiceImpl.java
@Component
public class SampleServiceImpl implements SampleService {
...
Puisque @Resource est ajouté au champ du sampleController à injecter et que le type correspond, l'instance est injectée par la méthode injectDependencies () que nous avons vue précédemment.
src/main/java/hello/SampleController.java
@Controller
public class SampleController {
@Resource
private SampleService service; //← Les instances gérées par le framework sont injectées ici
...
Avec ce mécanisme, l'appel de service.hello () n'entraînera pas de NullPointerException.
Le cadre qui introduit le mécanisme DI est appelé un "conteneur DI". SpringFramework, Google Guice, Seasar2, etc. sont des conteneurs DI.
Les avantages de l'introduction de DI sont les suivants.
Cette section gère le processus d'enregistrement du point d'appel lorsque "http : // localhost: 8080 / hello" est appelé à partir d'un client HTTP. Les parties où le traitement correspondant est effectué dans le cadre sont les suivantes.
Enregistrez le résultat dans le champ des contrôleurs d'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() {
//Vérifiez tous les composants sous la gestion du cadre.
components.entrySet().stream()
//Parmi eux@Limité aux classes avec contrôleur attaché.
.filter(kv -> kv.getKey().isAnnotationPresent(Controller.class))
.forEach(kv ->
//Obtenez la méthode Controller.
Arrays.asList(kv.getKey().getMethods()).stream()
// @Limité aux méthodes avec annotation RequestMapping
.filter(m -> m.isAnnotationPresent(RequestMapping.class))
.forEach(m -> {
//Obtenir des informations sur l'annotation RequestMapping
RequestMapping rm = m.getAnnotation(RequestMapping.class);
//Obtient le champ de valeur d'attribut qui définit le point d'appel HTTP.( rm.value() )
HTTPController c = new HTTPController(rm.value(), kv.getValue(), m);
//Inscrivez-vous en tant que contrôleur.
controllers.put(rm.value(), c);
System.out.println("Registered Controller => " + rm.value() + " - " + c);
})
);
}
L'endroit pour écouter une connexion d'un client HTTP et appeler la méthode du contrôleur correspondant est le suivant.
private static class EasyHttpServer implements Closeable {
...
public void start() {
while (true) {
acceptRequest:
//Attendez une demande d'un 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())) {
//Demande de chemin/Extrayez le contrôleur enregistré en fonction de bonjour.
HTTPController c = controllers.get(path);
if (c == null) {
os.println("404 Not Found (path = " + path + " ).");
break acceptRequest;
}
//Appelez la méthode Controller.( method.invoke )
os.println(c.method.invoke(c.instance));
}
} catch (IOException | IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
}
}
...
}
AOP
AOP signifie Programmation Orientée Aspect et est une méthode de programmation qui se concentre sur des «préoccupations transversales». En orientation objet, les traitements spécifiques à cet objet sont traités ensemble dans des définitions individuelles (classes, etc.), mais des traitements qui les chevauchent (problèmes transversaux) peuvent être nécessaires. Par exemple ...
Etc.
Avec AOP, il est possible d'insérer le traitement à tous les niveaux sans modifier le programme client, qui est la couche de programme métier.
Ici, pour réaliser AOP, "[java.lang.reflect.Proxy](https://docs.oracle.com/javase/jp/8/docs/api/java/" fourni en standard en Java lang / reflect / Proxy.html) "et" java.lang.reflect.InvocationHandler ) "Est introduit.
Si vous spécifiez l'objet source et le processus transversal (InvocationHandler) que vous souhaitez prendre en sandwich dans la méthode Proxy # newProxyInstance (), une nouvelle instance qui incorpore ce processus est renvoyée. Ici, AOP est réalisé en écrasant instantanément l'instance créée pour DI.
Si vous souhaitez utiliser Proxy pour AOP, le type de cible doit implémenter l'interface. Ici, nous voulons intercepter l'interface SampleService.
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(); //← Ajouter cette partie
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 {
/*
*Dans la méthode cible ou la définition de type@Lorsque l'annotation transactionnelle est ajoutée
*Interceptez le processus qui fonctionne comme suit.
* 1.Avant d'exécuter la méthode correspondante"Starts transaction."Est affiché dans la sortie standard.
* 2.Lorsque l'exécution de la méthode correspondante se termine normalement"Commit transaction."Est affiché dans la sortie standard.
* 3.Lorsque l'exécution de la méthode correspondante se termine par une exception"Rollbak transaction."Est affiché dans la sortie standard et l'exception est levée en haut.
*
* ※ @Si Transactional n'est pas ajouté, renvoie uniquement le résultat de l'exécution de la méthode correspondante.
* ※ @L'annotation transactionnelle est facile.Transactional.Créez-en un nouveau en tant que java.
* ※ ./Exécuter avec gradlew clean build run et curl http://localhost:8080/Vérifiez avec bonjour.
*/
return null;
}
La réponse se trouve dans la branche addAOP.
$ git checkout remotes/origin/addAOP
$ less src/main/java/easyframework/Interceptor.java
Pour référence, l'exemple Spring Boot [spring-boot-sample-tomcat](https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample- tomcat) Vérifiez de quel type de bibliothèques le projet se compose.
$ 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] ------------------------------------------------------------------------
Il y en a beaucoup d'autres.
De plus amples détails, tels que ceux mentionnés dans cet article, peuvent être trouvés dans les livres ci-dessous. Si vous êtes intéressé, veuillez le lire.
** <Introduction au développement de framework Java> **
Recommended Posts