Créons un framework Web ultra-simple avec Java

Aperçu

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.

Échantillon de client final

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!

supposition

Techniques et mots à utiliser

(Notes complémentaires)

Code source

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

Essayez de courir

#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>

Structure des fichiers

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

Diagramme de configuration simple

Figure 1: Configuration

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.

Annotation

** 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.

  • Les détails de la définition seront décrits plus loin.

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>.

Méta-annotation

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.

API de réflexion

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.

java.lang.Class type

Comment obtenir une instance de type Class

Il existe trois méthodes principales pour obtenir une instance du type Class.

  1. .class: extrait de la définition de type
  2. Class.getClass (): extrait de l'instance
  3. Classe # forName (String): extrait de string
//<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 " .class" est souvent vue lors de la génération d'un enregistreur au moment de la sortie du journal.

Logger LOG = LoggerFactory.getLogger(String.class);

Classe Principales méthodes définies dans la classe

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.

  • Cité de Javadoc (https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Class.html)
# 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

Définition de type pour la réflexion

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.

  • Vous devez appeler ** setAccessible (true) ** pour accéder aux informations privées.
  • Pour définir une valeur dans un champ, appelez ** field.set (instance cible, valeur que vous souhaitez définir) **.
  • Si vous souhaitez exécuter une méthode, appelez ** method.invoke (instance cible, argument ...) **.

Précautions lors de l'utilisation de la réflexion

  • Utiliser uniquement dans la couche cadre
  • Comme vu ci-dessus, même le contenu privé est réécrit, donc si un développeur général commence à utiliser la réflexion, la conception de la classe et les idées initialement envisagées peuvent être détruites. Dans le développement réel, je pense qu'il vaut mieux demander aux membres qui gèrent la couche cadre et la couche commune d'apporter des corrections.
  • Faites attention aux performances
  • Reflection lit les informations de définition au ** runtime ** et les traite, de sorte que les performances ne sont pas aussi bonnes que le traitement normal. En conséquence, certains frameworks utilisent la compilation comme déclencheur pour générer des .classes et .javas afin de réduire leur impact à l'exécution.
  • Si vous avez encore besoin de l'utiliser, pensez à mettre en cache les informations de champ et de méthode lues par réflexion.
  • Soyez conscient de la sécurité pour ne pas créer de vulnérabilités
  • Étant donné que la réflexion peut être instanciée à partir d'une chaîne de caractères, la transmission des paramètres de requête du client à l'API de réflexion créera des vulnérabilités inattendues, alors faites attention aux accès externes. ..

Cadre

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.

  • scanComponents (clazz) …… ** Rechercher et enregistrer des composants **.
  • injectDependencies () …… Définit le composant enregistré dans le champ correspondant ** DI **.
  • registerHTTPPaths () …… Enregistre les chemins qui seront les points d'appel HTTP parmi les composants enregistrés.
  • startHTTPServer () …… Démarre le serveur et écoute les requêtes.

Je décrirai les détails de chacun.

Analyse des composants

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.

  • Le code de génération d'instance (nouveau mot-clé) de la partie de jointure entre les classes est éliminé et l'instance requise peut être injectée (assignée) au moment voulu.
  • En définissant l'objet à injecter dans un fichier externe (XML, etc.), il peut être un point à étendre ultérieurement.
  • Le framework peut contrôler la portée de l'instance à injecter.
  • La portée est la portée d'une instance.
  • singleton: partagez une instance dans son ensemble.
  • prototype: crée une instance pour chaque injection.
  • session: partagez la même instance au cours de la même session.
  • request: Partager la même instance lors d'une même requête
  • Surtout lorsque singleton est sélectionné, une instance est partagée par plusieurs requêtes (environnement multi-thread), évitez donc d'avoir des champs variables dans les champs des objets gérés par singleton. Faisons le. </ font> Si vous souhaitez conserver un champ variable, vous devez le gérer avec prototype.
  • Il devient plus facile d'appliquer AOP depuis Framework.
  • Le framework gère les instances, ce qui facilite les interventions dans les traitements transversaux.

Requête HTTP

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 ...

  • Sortie de journal
  • Gestion des transactions
  • Processus d'authentification
  • Vérification des jetons
  • Mesure du temps de traitement
  • Contrôle de validation

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 {
...
    }

}
  • Dans Proxy, il est nécessaire de préparer une interface pour la pièce à intercepter, il est donc préférable d'utiliser la bibliothèque suivante dans le programme métier.
    • AspectJ: https://eclipse.org/aspectj/
    • Spring AOP: https://eclipse.org/aspectj/

Mains sur

Ajout d'intercepteur

  1. Créez une annotation easyframework.Transactional.
  2. Ajoutez l'annotation @Transactional à l'interface hello.SampleService.
  • Lorsqu'il est attaché au type lui-même
  • Lorsqu'il est attaché à une méthode publique
  1. Implémentez la méthode d'appel d'easyframework.Interceptor conformément aux spécifications suivantes.
$ 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

Dépendances de Spring Boot

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] ------------------------------------------------------------------------

Contraintes qui ne peuvent être réalisées par ce cadre

  • Tous les composants DI sont d'une tonne
  • DI est une injection de champ uniquement
  • Les composants DI sont générés uniquement avec le constructeur par défaut
  • Le paramètre de demande n'est pas reçu et des données valides ne sont pas possibles
  • La méthode HTTP est GET uniquement, pas POST ou PUT
  • L'AOP complexe n'est pas possible
  • HotDeploy (Hot Reloading) impossible

Il y en a beaucoup d'autres.

Le livre que j'ai utilisé comme référence

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> **

Le site que j'ai utilisé comme référence

  • Créez un serveur HTTP simple: http://qiita.com/opengl-8080/items/ca152658a0e52c786029
  • Différentes façons d'obtenir la liste des classes sous package: http://etc9.hatenablog.com/entry/2015/03/31/001620
  • Annotation TECHSCORE: http://www.techscore.com/tech/Java/JavaSE/JavaLanguage/7/
  • Una's Diary- @ Vérification des opérations héritées: http://unageanu.hatenablog.com/entry/20100712/1278946999

Recommended Posts