Introduction
Technologies utilisées
Analyse des exigences
Modèle de données
Architecture d'application
Couche de données
Contrôleurs
Gestionnaires d'action
Vues
Filtres
Résultats
Une application Web basée sur Java pour les activités TODO. Grâce à cette application Web, on peut créer, lire et mettre à jour ses todos via un navigateur Web moderne. L'application implémente également AAA, ce qui signifie que chaque utilisateur a son propre compte et sa liste de todo, etc. sont privés pour eux.
Ce document n'est pas pour les débutants. Vous devez posséder une bonne connaissance des technologies ci-dessous pour comprendre ce document et l'application connexe:
Java
Servlet, jsp
Apache Tomcat
Mysql
Html
Apache Netbeans IDE
Incendier
Il s'agit entièrement d'un projet back-end. Ainsi, les technologies frontales comme CSS, JavaScript ne sont pas utilisées. Le but du projet est d'apprendre et de montrer efficacement comment les différentes pièces de l'API Java Servlet fonctionnent ensemble.
Nous développerons l'application Web à partir de l'analyse des exigences. Ensuite, nous passerons à la conception de la base de données. Les données sont au cœur de toute application Web. Presque tous les cas d'utilisation traitent des données. Une fois que le modèle de données de l'application Web est prêt, nous allons ensuite passer à la conception de l'architecture de l'application. Dans cette phase, nous verrons comment notre application se comporte à différentes actions HTTP. Parce que toutes les actions effectuées par les utilisateurs de l'application sont via HTTP. Nous penserons à toutes les actions des utilisateurs possibles et les définirons clairement. Ensuite, nous allons passer à la conception des interfaces et des classes.
Pour notre application, nous commençons par définir ce qu'un Todo est pour nous. Un TODO est une tâche qui doit être accomplie. Nous créons une liste de telles tâches pour nous aider à vivre une vie prdocutive. Nous continuons à suivre la liste car nous effectuons les tâches l'une après l'autre. Un élément de TODO pour nous a des propriétés ci-dessous:
Initialement, une tâche aura le statut «TODO». Lorsque nous commençons à y travailler, nous le changeons en «en cours». Une fois la tâche accomplie, nous marquons son statut de «fait».
Nous voulons que notre application prenne également en charge plusieurs utilisateurs. Et chaque utilisateur aura sa propre liste privée. Ainsi, les utilisateurs ne peuvent pas voir la liste TODO des autres. Un utilisateur doit être identifié par son nom d'utilisateur, qui est son adresse e-mail valide pour nous. Les utilisateurs reçoivent des comptes sur notre application. Ainsi, un compte a des propriétés ci-dessous:
Nous voulons qu'un compte «administrateur» ne gére que les comptes. Le compte administrateur doit utiliser le nom d'utilisateur «admin». L'utilisateur administrateur peut:
Les deux derniers articles méritent d'être observés. Habituellement, on pense qu'un utilisateur avec des droits d'administration a accès aux informations de chacun. Nous n'en voulons pas. De plus, nous avons déjà défini que le compte «administrateur» pour nous est uniquement de gérer les comptes. Il ne s'agit pas de gérer des listes de TODo d'utilisateurs. Le compte d'utilisateur «admin» n'est pas souvent utilisé. Il est destiné uniquement à des fins spéciales. Pour notre application, nous nous attendons à ce qu'un compte d'utilisateur gère également le compte «administrateur». Donc, ce sera la même personne qui se connecte à utiliser des informations d'identification «admin» uniquement en cas de besoin. Parce qu'il s'agit d'un compte utilisateur existant utilisant le compte «admin» uniquement pour gérer tous les comptes, nous ne voulons pas de liste de tâches distincte pour le compte d'administration. Cela ne sert à aucun but.
Nous voulons que les listes de tâches persistent toujours. Ce qui signifie qu'une fois qu'un élément TODO est créé avec succès par un utilisateur, il ne peut jamais être supprimé. De même, nous ne voulons pas non plus supprimer un compte d'utilisateur. En conclusion, nous ne voulons pas soutenir les opérations «supprimer» dans notre application. Ainsi, nous ne soutenons que CRU à partir de crud.
Parce que nous voulons que notre application conserve des listes de tâches privées, nous voulons que l'application fournit des mécanismes de connexion et de déconnexion. C'est ce qu'on appelle «l'authentification». Chaque utilisateur, y compris «administrateur», devrait d'abord s'authentifier. Après une authentification réussie, un utilisateur sera redirigé vers son espace de travail. Parce que nous discutons de deux types d'utilisateurs (un administrateur et l'autre normal), nous aurons deux types d'espaces de travail sur notre application. Un utilisateur d'administration ne doit travailler qu'avec un espace de travail de gestion des comptes d'utilisateurs. Un utilisateur normal ne doit travailler qu'avec Todo List Management Workspace. Les deux sont exclusifs. Un utilisateur normal ne peut pas voir l'espace de travail de l'administrateur. Et l'utilisateur d'administration ne peut pas voir l'espace de travail de l'utilisateur normal. C'est ce qu'on appelle «l'autorisation».
En plus des exigences ci-dessus, nous voulons que notre application stocke les détails des horodatages de connexion et de déconnexion des utilisateurs. Grâce à cela, nous suivons l'activité des utilisateurs sur notre application. Ce n'est pas exactement la «comptabilité» de l'AAA, mais pour notre application, cela a le but de l'appeler comme «comptabilité».
Sur la base des exigences que nous avons collectées jusqu'à présent, nous comprenons que nous devons stocker des données pour des entités de l'application ci-dessous:
Exemple de données pour quelques comptes:
| ID de compte | Nom d'utilisateur | Prénom | Nom de famille | Mot de passe | Créé à | Statut |
|---|---|---|---|---|---|---|
| 1 | administrer | Administrateur | Utilisateur | mot de passe | 2020-05-06 17:34:04 | activé |
| 2 | [email protected] | John | Johnsson | un mot | 2020-05-07 12:34:04 | désactivé |
| 3 | [email protected] | Eric | Éricson | twoword | 2020-05-08 13:34:04 | activé |
| 4 | [email protected] | Ana | Marie | trois mots | 2020-05-09 11:34:04 | activé |
Nous voyons que les statuts de compte sont répétés dans tout le tableau. Ainsi, dans le cadre de la normalisation de la base de données, il est préférable de mettre les données répétées dans un tableau séparé. Il y a une bonne raison derrière. Disons que nous avons 100 utilisateurs. Et nous voulons remplacer les mots activés et désactivés par 1 et 2 respectivement. Nous devons modifier la colonne d'état de toutes les lignes de la table. Imaginez à quel point ce sera lourde de faire une telle modification pour une table avec des milliers de lignes! Normalisation de la base de données à Rescue, heureusement!
Après la normalisation, nous aurons deux tables - account_statures et comptes:
Account_status
| IDENTIFIANT | Statut |
|---|---|
| 1 | activé |
| 2 | désactivé |
COMPTES
| ID de compte | Nom d'utilisateur | Prénom | Nom de famille | Mot de passe | ID de statut |
|---|---|---|---|---|---|
| 1 | administrer | Administrateur | Utilisateur | mot de passe | 1 |
| 2 | [email protected] | John | Johnsson | un mot | 2 |
| 3 | [email protected] | Eric | Éricson | twoword | 1 |
| 4 | [email protected] | Ana | Marie | trois mots | 2 |
De même, nous aurons trois tables pour les tâches - task_status, task_priorités et tâches:
Tâche_statures
| IDENTIFIANT | Statut |
|---|---|
| 1 | faire |
| 2 | en cours |
| 3 | fait |
Task_priorités
| IDENTIFIANT | Priorité |
|---|---|
| 1 | important et urgent |
| 2 | important mais pas urgent |
| 3 | pas important mais urgent |
| 4 | pas important et pas urgent |
Tâches
| ID de tâche | ID de compte | Détails | Créé à | Date limite | Dernière mise à jour | ID de statut | ID de priorité |
|---|---|---|---|---|---|---|---|
| 1 | 2 | Acheter des crayons. | 2019-05-06 17:40:03 | 2019-05-07 17:40:03 | 2 | 1 | |
| 2 | 3 | Achetez des livres. | 2019-05-07 7:40:03 | 2019-05-07 17:40:03 | 2019-05-07 23:40:03 | 2 | 1 |
Enfin, nous avons également une autre obligation pour stocker les données de session de compte. Nous le stockons comme indiqué dans le tableau ci-dessous:
Account_essions
| ID de session | ID de compte | Session créée | Fin de session |
|---|---|---|---|
| ASD1GH | 1 | 2019-05-06 17:40:03 | 2019-05-06 18:00:03 |
Normalement, dans les applications d'entreprise, les ID ne sont pas stockés comme des entiers. Parce qu'il sera plus facile pour quelqu'un d'interroger les informations sur les autres en utilisant simplement un entier! Dans les applications du monde réel, les ID ne sont pas numériques mais alphanumériques, avec jusqu'à 100 caractères. Ainsi, empêchant quelqu'un de deviner une autre pièce d'identité!
Nous appellerons notre base de données comme «TODO» dans MySQL. Et voici le modèle de données construit sur la base des informations ci-dessus:

Nous développerons cette application en suivant le célèbre modèle MVC 2 DESGIN MVC 2. La figure ci-dessous montre comment nous allons implémenter MVC pour notre application: 
Notre application sera basée sur l'action. Lorsqu'un utilisateur envoie une demande HTTP à notre application, nous le traduisons en action correspondante sur notre application. Les actions que nous prenons en charge sont la création, la lecture et la mise à jour (CRU). Notre application est essentiellement axée sur les données. Il facilite les actions sur la base de données. Il aide les utilisateurs à stocker et à gérer leurs données sur une base de données distante en toute sécurité et en toute sécurité à l'aide des mécanismes d'authentification et d'autorisation. Il agit comme une interface HTML et HTTP à la base de données.
Lorsqu'un utilisateur fait une demande HTTP à notre application, nous renvoyons les données demandées sous forme de HTML. HTML prend en charge les liens et les formulaires pour aider les utilisateurs à interagir avec les applications Web. Les liens sont utilisés pour récupérer / obtenir (HTTP GET) des informations, tandis que les formulaires sont utilisés pour publier des données (HTTP Post) sur l'application Web.
Alors, voici comment nous allons traduire les demandes HTTP aux actions:
| Élément html | Méthode HTTP | Action de demande |
|---|---|---|
| Lien hypertexte | Http | Lire les détails |
| Formulaire | HTTP Post | Créer ou mettre à jour |
HTTP Get envoie les données sous forme de paramètres de requête à l'URL. Tandis que le post HTTP envoie les données dans le corps de la demande HTTP. HTTP Post ne révèle pas les données via l'URL, alors que Http Get fait. Ainsi, Http Get ne convient pas pour l'envoi d'identification de connexion. Personne ne veut voir son nom d'utilisateur et son mot de passe ajoutés à l'URL de demande HTTP! Nous utiliserons la publication HTTP pour envoyer des informations d'identification de l'utilisateur lors de la connexion.
Donc, loin, nous avons décidé comment nous allons utiliser HTTP, HTML et une base de données. Il y a un autre concept de HTTP qui est essentiel pour nous de comprendre pour décider de l'architecture de notre application basée sur HTTP. C'est URL - Locator de ressources uniformes. Voici un exemple URL d'une application Web appelée «WebApp» hébergée sur Example.com Server:
http://www.example.com/webapp/details?id=12
Dans l'exemple ci-dessus URL, «http» est le protocole, www.example.com est le nom de domaine ou le nom du serveur, «WebApp» est le contexte d'application déployé sur le serveur et «Détails» est l'application pour laquelle nous envoyons notre demande HTTP. «Id» est le paramètre de requête que nous passons aux «détails» avec une valeur de «12». Les paramètres de requête nous fournissent un mécanisme pour transmettre des paramètres à l'application Web et recevoir du contenu connexe en réponse de l'application Web. Par exemple, imaginez une application météo fonctionnant sur un serveur Web. Au lieu de nous fournir la liste des rapports météorologiques de tous les emplacements, nous pouvons envoyer notre choix d'emplacement en tant que paramètre de requête à l'application Web. La demande enverrait ensuite en réponse les détails de notre choix de l'emplacement.
Une application Web s'exécute sur le serveur Web, contrairement aux applications exécutées sur nos PC. Une application Java exécutée sur un serveur Web est appelée servlet. Les servlets imitent les applications Web. Ils courent à l'intérieur d'un conteneur. Apache Tomcat est un exemple populaire d'un tel conteneur. Le logiciel de conteneur traduit les demandes et réponses HTTP brutes en objets Java et les fournit pour les servlets. Un site Web statique sert le même contenu pour chaque demande HTTP. Mais, un servlet peut générer un contenu dynamique distinct pour chaque demande HTTP faite.
L'application Web TODO que nous construisons contiendra plusieurs pièces - servlets, filtres, fichiers JSP, classes de base de données, Pojos, etc. Nous les assemblerons tous (comme les regrouper) dans un contexte d'application (ou un environnement d'application) sur le conteneur (Tomcat). Nous appellerons ce contexte d'application comme «TODO». Ainsi, si nous exécutons Tomcat sur notre PC au port 8080, notre contexte d'application «Todo» est accessible via l'URL:
http://localhost:8080/todo/
Notre couche de données consiste en une implémentation du modèle DAO et du modèle d'usine en plus de JDBC DataSource. Nous choisissons JDBC DataSource au lieu de DriverManager car nous voulons profiter des avantages de la mise en commun des connexions.
Nous avons juste un seul servlet en tant que contrôleur , appelé «Main». La demande HTTP d'un utilisateur est une action pour nous. Ainsi, le but de notre servlet de contrôleur est de simplement choisir une action appropriée pour la demande HTTP faite. Le servlet du contrôleur choisit un gestionnaire d'action et le remet sur la demande faite par l'utilisateur. Nous n'écrivons pas les étapes d'exécution de l'action dans notre contrôleur. Nous le gardons propre et maigre. Son but est de «choisir» un gestionnaire d'action. Ne pas «exécuter» l'action par elle-même. Une fois que le gestionnaire d'action a exécuté l'action demandée, le contrôleur reçoit «la prochaine étape» à effectuer en réponse du gestionnaire d'action. Le travail du contrôleur consiste simplement à choisir la ressource qui effectue la réponse demandée. En conclusion, nous éloignons notre contrôleur de toute la logique commerciale.
Nous mapperons notre servlet de contrôleur au modèle URI /app/* . Ainsi, notre servlet de contrôleur gérera chaque URI qui suit le modèle /app/ .
Une action demandée par un utilisateur est la logique métier de notre application. Les gestionnaires d'action sont le modèle de notre implémentation MVC. Chaque action doit implémenter l'interface d'action:
public interface Action {
/*
An action is supposed to execute and return results. ActionResponse represents the response.
*/
public abstract ActionResponse execute ( HttpServletRequest request , HttpServletResponse response )
throws Exception ;
}Une action est censée exécuter et renvoyer les résultats. Nous créons une classe spéciale pour un tel résultat - ActionResponse. Un gestionnaire d'action peut choisir de «transmettre» ou de «rediriger».
public class ActionResponse {
private String method ;
private String viewPath ;
public ActionResponse () {
this . method = "" ;
this . viewPath = "" ;
}
public void setMethod ( String method ) {
this . method = method ;
}
public String getMethod () {
return this . method ;
}
public void setViewPath ( String viewPath ) {
this . viewPath = viewPath ;
}
public String getViewPath () {
return this . viewPath ;
}
@ Override
public String toString () {
return this . getClass (). getName ()+ "[" + this . method + ":" + this . viewPath + "]" ;
}
}Nous mettons en œuvre le modèle de conception d'usine. Nous créons une classe d'usine - ActionFactory - pour nous donner la classe de gestionnaire d'action dont nous avons besoin:
public class ActionFactory {
private static Map < String , Action > actions = new HashMap < String , Action >() {
{
put ( new String ( "POST/login" ), new LoginAction ());
put ( new String ( "GET/login" ), new LoginAction ());
put ( new String ( "GET/logout" ), new LogoutAction ());
put ( new String ( "GET/admin/accounts/dashboard" ), new AdminAccountsDashboardAction ());
put ( new String ( "GET/admin/accounts/new" ), new AdminNewAccountFormAction ());
put ( new String ( "POST/admin/accounts/create" ), new AdminCreateAccountAction ());
put ( new String ( "GET/admin/accounts/details" ), new AdminReadAccountDetailsAction ());
put ( new String ( "POST/admin/accounts/update" ), new AdminUpdateAccountAction ());
put ( new String ( "GET/tasks/dashboard" ), new UserTasksDashboardAction ());
put ( new String ( "GET/tasks/new" ), new UserNewTaskFormAction ());
put ( new String ( "GET/tasks/details" ), new UserReadTaskDetailsAction ());
put ( new String ( "POST/tasks/create" ), new UserCreateTaskAction ());
put ( new String ( "POST/tasks/update" ), new UserUpdateTaskAction ());
put ( new String ( "GET/users/profile" ), new UserReadProfileAction ());
put ( new String ( "POST/users/update" ), new UserUpdateProfileAction ());
}
;
};
public static Action getAction ( HttpServletRequest request ) {
Action action = actions . get ( request . getMethod () + request . getPathInfo ());
if ( action == null ) {
return new UnknownAction ();
} else {
return action ;
}
}
}Le but de notre servlet de contrôleur est de:
protected void processRequest ( HttpServletRequest request , HttpServletResponse response )
throws ServletException , IOException {
Action action = ActionFactory . getAction ( request );
try {
ActionResponse actionResponse = action . execute ( request , response );
if ( actionResponse . getMethod (). equalsIgnoreCase ( "forward" )) {
System . out . println ( this . getClass (). getCanonicalName () + ":forward:" + actionResponse );
this . getServletContext (). getRequestDispatcher ( actionResponse . getViewPath ()). forward ( request , response );
} else if ( actionResponse . getMethod (). equalsIgnoreCase ( "redirect" )) {
System . out . println ( this . getClass (). getCanonicalName () + ":redirect:" + actionResponse );
if ( actionResponse . getViewPath (). equals ( request . getContextPath ())) {
response . setHeader ( "Cache-Control" , "no-cache, no-store, must-revalidate" );
response . setHeader ( "Pragma" , "no-cache" );
response . setDateHeader ( "Expires" , 0 );
}
response . sendRedirect ( actionResponse . getViewPath ());
} else if ( actionResponse . getMethod (). equalsIgnoreCase ( "error" )) {
System . out . println ( this . getClass (). getCanonicalName () + ":error:" + actionResponse );
response . sendError ( 401 );
} else {
System . out . println ( this . getClass (). getCanonicalName () + ":" + actionResponse );
response . sendRedirect ( request . getContextPath ());
}
} catch ( Exception e ) {
e . printStackTrace ();
}
} Le tableau ci-dessous montre la liste des demandes HTTP à laquelle notre application répond et leurs gestionnaires d'action associés:
| Action prévue de l'utilisateur | Http demande uri | Gestionnaire d'action |
|---|---|---|
| Soumettre les informations d'identification de connexion vides | GET /app/login | Connexion |
| Soumettre les informations d'identification de connexion | POST /app/login | Connexion |
| Obtenez le tableau de bord des comptes | GET /app/admin/accounts/dashboard | Administration |
| Obtenez un nouveau formulaire de compte | GET /app/admin/accounts/new | AdministrawAccountFormAction |
| Soumettre les nouveaux détails du compte | POST /app/admin/accounts/create | Administration d'administration |
| Obtenez les détails d'un compte | GET /app/admin/accounts/details?id=xx | AdminReadAccountDetailSaction |
| Mettre à jour les détails d'un compte | POST /app/admin/accounts/update | AdminupdateAccountAction |
| Obtenez le tableau de bord des tâches | GET /app/tasks/dashboard | Userasksdashboardaction |
| Obtenez un nouveau formulaire de tâche | GET /app/tasks/new | UserNewTaskFormaction |
| Soumettre les nouveaux détails de la tâche | POST /app/tasks/create | UserCreatEtaskAction |
| Obtenez les détails d'une tâche | GET /app/tasks/details?id=xx | Userreadtaskdetailsaction |
| Mettre à jour les détails d'une tâche | POST /app/tasks/update | UserUpdateTaskAction |
| Obtenez des détails de mon profil | GET /app/users/profile | UserreadprofileAction |
| Mettre à jour les détails de mon profil | POST /app/users/update | UserUpdateProfileAction |
| Déconnexion | GET /app/logout | Déconnexion |
Le travail d'un gestionnaire d'action consiste à exécuter la logique métier et à choisir un composant de vue approprié en réponse à la demande faite par un utilisateur. Le tableau ci-dessous montre tous les gestionnaires d'action et leurs composants de vue:
| Gestionnaire d'action | Afficher le composant |
|---|---|
| Connexion | /WEB-INF/pages/admin/accounts/dashboard.jsp/WEB-INF/pages/tasks/dashboard.jsp |
| Administration | /WEB-INF/pages/admin/accounts/dashboard.jsp |
| AdministrawAccountFormAction | /WEB-INF/pages/admin/accounts/newAccount.jsp |
| Administration d'administration | /WEB-INF/pages/admin/accounts/createAccountResult.jsp |
| AdminReadAccountDetailSaction | /WEB-INF/pages/admin/accounts/accountDetails.jsp |
| AdminupdateAccountAction | /WEB-INF/pages/admin/accounts/updateAccountResult.jsp |
| Userasksdashboardaction | /WEB-INF/pages/tasks/dashboard.jsp |
| UserNewTaskFormaction | /WEB-INF/pages/tasks/newTask.jsp |
| UserCreatEtaskAction | /WEB-INF/pages/tasks/createTaskResult.jsp |
| Userreadtaskdetailsaction | /WEB-INF/pages/tasks/taskDetails.jsp |
| UserUpdateTaskAction | /WEB-INF/pages/tasks/updateTaskResult.jsp |
| UserreadprofileAction | /WEB-INF/pages/users/viewProfile.jsp |
| UserUpdateProfileAction | /WEB-INF/pages/users/updateProfileResult.jsp |
| Inconnue | /WEB-INF/pages/users/unknownAction.jsp |
Le composant View construit la réponse HTML requise qui sera envoyée à l'utilisateur. Afficher le composant lit les messages définis par le gestionnaire d'action et l'affiche à l'utilisateur.
Nous utilisons des filtres pour intercepter les demandes HTTP entrantes. Tous les filtres seront utilisés avant que la demande ne soit transmise au servlet du contrôleur. Toute demande HTTP entrante sera d'abord gérée par le filtre d'authentification. Grâce à ce filtre, nous vérifions si l'utilisateur est déjà connecté ou non. S'il n'est pas connecté, nous redirigeons l'utilisateur vers la page de connexion. Après avoir réussi à passer par le filtre d'authentification, la demande HTTP sera interceptée par deux filtres supplémentaires. Dans ces filtres, nous vérifions le chemin URI et l'utilisateur est «admin» ou utilisateur normal. Si un utilisateur normal essaie d'accéder aux chemins URI «Admin», nous empêchons un tel accès. Si l'utilisateur «admin» essaie d'accéder aux trajets URI liés aux tâches, nous empêchons un tel accès.
Seul «admin» peut accéder à l'uris en commençant par /app/admin/* et seul l'utilisateur normal peut accéder à l'uris en commençant par /app/tasks/* . D'autres URI /app/login , /app/logout , /app/users/* peuvent être accessibles par les deux.
Nous n'interceptons pas les réponses que nous envoyons.
Alors, voici à quoi ressemble notre application. J'ai gardé l'interface utilisateur pour la simplicité. Il n'y a ni CSS ni JavaScript.










