Вся суть паттерна Front Controller в том, что все запросы обрабатываются централизовано через одно место.
То есть любой запрос сначала идет в Front Controller, далее Front Controller определяет в какой другой обработчик или View перенаправить запрос и, собственно, перенаправляет.
Приведем пример сервлета, который будет играть роль Front Controller-а, который принимает запросы и форвардит через RequestDispatcher на какую-то вьюху.
import java.util.*;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;;
@WebServlet(“/FrontControllerServlet”)
public class FrontControllerServlet extends HttpServlet {
protected void doPost(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
//Пусть Front Controller проверяет присланные
//POST запросом в него параметры и если они
//соответствуют тому что в equals то Front Controller
//перенаправляет отправившего запрос
//на страницу SomePasswordValue, также и сам запрос(req)
//туда перенаправляться с помощью forward.
if (req.getParameter(“password”).equals(“JohnSmith”) &&
req.getParameter(“login”).equals(“SomePasswordValue”)) {
RequestDispatcher rd=
req.getRequestDispatcher(“/welcomePage.jsp”);
rd.forward(req, resp);
}
//в ином случае на страницу errorPage
else {
RequestDispatcher rd=
req.getRequestDispatcher(“/errorPage.jsp”);
rd.forward(req, resp);
}
}
}
Data Access Object. Нужен чтобы отделить низкоуровневый доступ к данным от высокоуровневых сервисов.
Низкоуровневый доступ – это, например, взаимодействие с базой с помощью средств JDBC. Мы можем сокрыть прямое использование средств JDBC (то есть, например, подключение к базе, извлечение данных из базы с помощью каких-то сложных цепочек JDBC методов, или сокрыть некоторые SQL запросы) в DAO объекте и уже в высокоуровневых сервисах (в main, например) взаимодействовать с данными базы через методы DAO объекта.
То есть всё низкоуровневое взаимодействие с БД с помощью JDBC происходит в методах DAO объекта, а высокоуровневые сервисы просто используют эти методы.
Для примера в программе ниже лучше пусть ресурсом будет не база, а List коллекция, так будет проще.
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
//Создадим простейший класс машины.
//Экземпляры класса Car будут храниться
//в List.
class Car {
private String brand;
private int speed;
Car(String brand, int speed){
this.brand = brand;
this.speed = speed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
@Override
public String toString() {
return “Car{” +
“brand=” + brand +
“, speed=” + speed +
‘}’;
}
}
//Создадим интерфейс на основе которого
//будет создаваться DAO класс на основе
//которого будет создаваться DAO объект.
interface DAOInterface {
public List getAllCars();
public Car getCar(int carNum);
public void updateCar(Car car, int carNum);
public void deleteCar(Car car);
}
//Создаем этот DAO класс
//реализуя интерфейс выше.
class DAOImpl implements DAOInterface {
//Вместо сокрытия в DAO классе низкоуровневого
//взаимодействия с БД будем скрывать
//низкоуровневое взаимодействие
//со списком машин listOfCars.
List listOfCars;
public DAOImpl(){
//Пусть при создании DAO объекта
//список инициализируется данными.
listOfCars = new ArrayList(Arrays.asList(
new Car(“Mazda”,200),
new Car(“chevrole”,150)));
}
// В методах ниже скрываем взаимодействие с List
// То есть как можно увидеть ниже все методы
// класса List для низкоуровневого взаимодействия
// с объектом List (например remove(), get())
// сокрыты в методах DAO класса (в методах
// deleteCar(), getCar(), updateCar()) которыми
// будут пользоваться высокоуровневые сервисы.
@Override
public void deleteCar(Car car) {
listOfCars.remove(car.getSpeed());
}
@Override
public List getAllCars() {
return listOfCars;
}
@Override
public Car getCar(int carNum) {
return listOfCars.get(carNum);
}
@Override
public void updateCar(Car car, int carNum) {
Car carfromlist = listOfCars.get(carNum);
carfromlist = car;
}
}
//Воспользуемся уже DAO объектом
//в высокоуровневом сервисе (в мейн).
public class DAOExample {
public static void main(String args[]) {
//Через DAO объект dao будем
//взаимодействовать с ресурсом(списком).
DAOInterface dao = new DAOImpl();
//Как видим все непосредственное
//взаимодействие со списком сокрыто
//в DAO объекте, а в мейн мы
//взаимодействуем со списком посредством
//методов DAO объекта.
Car car = dao.getCar(1);
System.out.println(car);
car.setBrand(“Toyota”);
dao.updateCar(car, 1);
System.out.println(dao.getCar(1));
}
}
Скомпилируем java файл и запустим программу.
Как видим, объект машины в списке был изменен с помощью методов DAO объекта, а не напрямую.
О DAO шаблоне будет еще много говориться в разделе посвященном spring.
Singleton – класс, который гарантировано имеет только один экземпляр.
Используется, как некий глобальный ресурс доступ к которому есть в любой точке приложения.
//Создадим класс синглтона. В нем сразу же создается
//и объект этого класса и никогда больше объект этого
//класса не создается и не может быть создан в принципе.
//В этом вся суть синглтона.
class Singleton {
String str = “Singleton String!!!”;
//Тем что конструктор private достигается
//невозможность создания экземпляра этого класса
//где-либо вне определения класса Singleton.
//Как мы помним private элементами класса
//можно пользоваться исключительно в пределах
//определения класса поэтому конструктором вне
//класса воспользоваться нельзя, а значит
//и создать объект этого класса нельзя так как при
//создании объекта всегда вызываеться конструктор
private Singleton() { }
//В пределах определения класса как уже было
//сказано private элементы доступны.
//Поэтому можем создать экземпляр здесь.
private static Singleton singleton = new Singleton();
//Чтобы получить этот единственный экземпляр
//у синглтона должен быть публичный метод,
//который возвращает его.
public static Singleton getInstance() {
return singleton;
}
}
public class SingletonExample {
public static void main(String[] args) {
//Во время работы приложения в любой его точке
//может быть извлечен и использован единственный
//экземпляр синглтона созданный внутри класса
//синглтона.
//Создадим две ссылки с именами singleton
//и singleton1. Обе они ссылаются на один и тот
//же единственный возможный объект синглтона.
Singleton singleton = Singleton.getInstance();
Singleton singleton1 = Singleton.getInstance();
//Чтобы доказать что singleton и singleton1
//ссылаются на один и тот же объект
//давайте изменим str в объекте синглтона через
//ссылку singleton.
singleton.str = “New singleton string!!!!!!!”;
//и выведем эту измененную строку в объекте
//через ссылку singleton1.
System.out.println(singleton1.str);
}
}
Скомпилируем java файл и запустим программу.
Как видим, строка в единственном объекте синглтона успешно была изменена через одну ссылку, а через другую была выведена на консоль. То есть работать можем с единственным объектом синглтона через разные ссылки в приложении.
Singleton vs Статический класс
Часто у многих возникает вопрос – “Зачем нужен синглтон если есть статический класс, который тоже можно использовать как какой-то глобальный ресурс?”
Дело в том, что сиенглтон является экземпляром, когда статический класс нет, поэтому синглтоном можно пользоваться, как нормальным объектом, например, передать как объект куда-то.
Также синглтон в отличие от стат. класса может реализовать или наследовать что-то.
При проектировании того или иного программного продукта разработчик сталкивается с большим количеством проблем. И многие из этих проблем бывают ТИПИЧНЫМИ при разработке.
Так вот, чтобы разработчик сам не сидел и не выдумывал как ему решить какую-то типичную при разработке многих программных продуктов проблему, существует уже готовое решение этой проблемы, которое этот разработчик может использовать.
То есть существует шаблон, следуя которому разработчик может решить типичную проблему.
Сами подумайте, знание существующего, проверенного временем шаблона для решения типичной при построении приложения проблемы может существенно упростить разработчику жизнь. Без знания шаблонов разработчик может намудрить что-то такое, что скажется и на продуктивности, и на читабельности кода, и на другом.
Шаблон Factory
Первый паттерн, который мы рассмотрим называется Factory(фабрика).
Factory довольно простой паттерн.
Часто в приложении может понадобиться вспомогательный объект с помощью которого можно создавать другие объекты. То есть это объект, который является фабрикой других объектов.
Важное уточнение, что фабрика создает экземпляры ОДНОТИПНЫХ классов.
В примере ниже фабрика книг производит разные книги (экземпляры этих книг).
//Тип создаваемых объектом фабрики объектов
//это книга. Очевидно что все реализующие
//этот интерфейс классы будут тоже книгами
//только уже конкретными книгами.
interface Book {
void read();
}
//Реализуем этот интерфейс
//таким образом создавая конкретную
//книгу “Властелин колец”
class LordOfTheRings implements Book {
public void read() {
System.out.println(
“Reading Lord of The Rings!”);
}
}
//Реализуем этот интерфейс еще раз
//таким образом создавая вторую
//конкретную книгу “На западном фронте
//без перемен.”.
class AllQuietOnTheWesternFront
implements Book {
public void read(){
System.out.println(
“Reading All Quiet On The ”
+ “Western Front!”);
}
}
//Создаем класс фабрики книг.
//Объект класса фабрики книг должен
//уметь создавать объект любого
//определенного выше класса книги.
class BooksFactory {
//Метод объекта фабрики ниже как раз будет
//создавать объект конкретной книги.
//Как можно увидеть аргументом этому методу
//передается название книги объект которой
//хочет создать тот кто вызывает этот метод.
public Book createBook(String bookName) {
switch (bookName) {
//То есть как видим если аргументом методу
//была передана строка “Lord Of The Rings”
case “Lord Of The Rings”:
//то будет создан и возвращен объект
//класса LordOfTheRings.
return new LordOfTheRings();
//Если аргументом методу была передана
//строка “All Quiet On The Western Front”
case “All Quiet On The Western Front”:
//то будет создан и возвращен объект
//класса AllQuietOnTheWesternFront.
return new AllQuietOnTheWesternFront();
default:
return null;
}
}
}
//Воспользуемся созданными классами в main методе.
public class FactoryExample {
public static void main(String[] args) {
//Создадим объект книги “Властелин колец”
//используя объект фабрики книг.
Book lordOfTheRings =
new BooksFactory()
.createBook(“Lord Of The Rings”);
//Создадим объект книги “На западном фронте
//без перемен” используя объект фабрики книг.
Book allQuietOnTheWestFr =
new BooksFactory()
.createBook(“All Quiet On The ”
+ “Western Front”);
//Проверим успешно ли нам создала фабрика
//объектов разных книг вызвав методы этих
//этих книг.
lordOfTheRings.read();
allQuietOnTheWestFr.read();
}
}
Скомпилируем java файл и запустим программу.
Как видим, read у созданных объектов вывел на консоль разные строки. Значит BooksFactory умеет создавать РАЗНЫЕ ОДНОТИПНЫЕ объекты.
Когда применяется Factory?
Данный шаблон применяется по двум основным причинам:
1. Все объекты книг создаются одним методом.
Представьте себе приложение, которое содержит ранее написанные нами классы книг и класс фабрики книг, и которое также имеет пользовательский интерфейс.
Пусть этот интерфейс содержит поле для ввода названия книги и представьте, что то, что введет пользователь в это поле будет передано аргументом в метод фабрики createBook.
Метод фабрики createBook создаст объект той книги, которую хочет пользователь по названию, которое он ввел.
Суть в том, что тому, кто пишет программу заранее не известно, что пользователь напишет в поле для ввода в пользовательском интерфейсе, объект какой именно книги пользователь захочет создать.
То есть благодаря тому, что все объекты книг создаются одним и тем же методом, в программе создается именно тот объект, который хочет пользователь, а не программист или кто либо еще.
2. Второй важный момент, это то, что программист может добавить дополнительный функционал в класс фабрики, который может быть применен к любому создающемуся фабрикой объекту.
JSTL — это теговая альтернатива стандартному Java-коду на JSP. То есть все стандартные конструкции типа создания переменных, циклов, условий, исключений,… можно делать тегами.
Нужен за тем же, зачем используются usebean, EL – чтобы легче страницу было разрабатывать дизайнеру.
Подключение JSTL
Для начала нужно подключить JSTL библиотеку. Для этого нужно скачать файл jstl-1.2.jar. Его довольно легко найти в интернете.
И после этого нужно поместить его в папку lib в папке WEB-INF.
Теперь нужно подключить библиотеку в самом jsp файлике с помощью <%@%> тега.
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
EL JSP
Простейшие теги JSTL
Для примера приведем тег циклаc:forEach. Аналог Java циклов for, while. foreach.
Как видим, JSTL теги используются в паре с EL выражениями.
Вывод:
Как видим, первый цикл вывел все элементы списка в аттрибуте, а второй цикл вывел числа от 1 до 5.
Другие простейшие теги типа c:if(условие), c:choose, c:when, c:otherwise (аналог if else или switch), c:catch (перехват ошибок) рассматривать не будем они очень просты и легко гугляться.
Далее рассмотрим лишь самые интересные.
Тег c:out
Тег c:outиспользуется для вывода информации на страницу.
Он выполняет ту же функцию, что и ранее изученные способы:
<%=”<h2>Hello!!</h2>”%>,
resp.getWriter().write(“<h2>Hello!!</h2>”)
или \${<h2>Hello!!</h2>}.
Но в случае c c:out тег h2 не воспримется (выведет на странице <h2>Hello!!</h2>). c:out более безопасен и перед выводом на страницу интерпретирует html теги в специальные символы. Поэтому можно без страха через него выводить данные введенные пользователем, не боясь, что он ввел какой-то html код, что может сломать страницу.
Пример программы:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
EL JSP
<%="
Hello!!
“%>
<% response.getWriter().write("
Hello!!
“); %>
${‘
Hello!!
‘}
Видим, что при выводе с помощью c:out теги h2 воспринялись как часть строки. То есть html, при выводе с помощью c:out, не работает.
Тег c:import
Подобен include в сервлетах, но он также может добавлять на страницу не только данные на нашем сервере, а и вставить в нашу jsp сторонний url.
Пример программы:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
JSTL JSP
before import
after import
То есть сначала выведется всё, что было выше строки с import потом главная страница twitch, потом код после этой строки.
Видим, что сначала вывелось before import, потом страница twitch, потом after import.
Создание, удаление переменных.
С помощью JSTL можно создавать, удалять переменные.
Видим, что при выключенных куках JSESSIONIDуспешно добавился и параметр со значением Carl тоже успешно добавился.
Тег c:forTokens.
С помощью c:forTokens можно разбить строку по какому-то символу и над каждым элементом разбитой строки выполнить какие-то действия. То есть очевидно, что это такой вид цикла.
извлекать атрибуты скоупов можно просто используя Expression language.
Изменим View из предыдущего урока вот таким образом:
EL JSP
${someuser.name}
Запускаем контроллер:
Он, опять таки, создает атрибут с именем someuser, в котором находиться объект класса User, извлеченный контроллером через модель.
Переходим на View и видим, что Expression Language сработал и вывелось имя из объекта в аттрибуте someuser:
Конкретный скоуп
По дефолту (как было выше) проверяются все атрибуты всех скоупов, пока не найдет someuser, но можно задать скоуп, в котором хотим искать. В нашем случае скоуп контекста (applicationScope)
Пример программы:
EL JSP
${applicationScope.someuser.name}
Вывод такой же:
Другие возможности EL
Также с помощью EL можно делать много чего другого. Смотри пример.
Изменим контроллер:
import com.someclasses.*;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
import java.util.*;
@WebServlet(“/MVC_Controller”)
public class MVC_Controller extends HttpServlet {
MVC_Model model = new MVC_Model();
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
//Создадим список
List arrlist = new ArrayList<>();
//Добавим туда пару значений
arrlist.add(“FirstStr”);
arrlist.add(“
Hello!!
“);
//Добавляем список в атрибут контекста
//и он будет доступен на View. С этим списком
//можно будет работать с помощью EL
req.getServletContext().setAttribute(“arrlist”,arrlist);
Cookie cokie = new Cookie(“name”,”Jonny”);
//Этот cookie извлечен на view с помощью EL
resp.addCookie(cokie);
}
}
С помощью <%@ page isELIgnored="false"%> можно отключить все EL выражения на странице.
Поставив перед конкретным EL выражением "\\" можно отключить какое либо конкретное EL выражение
Пример программы:
<%@ page isELIgnored="false"%>
EL JSP
\${arrlist[1]}
Ясное дело, если мы откроем эту страницу, на ней ничего не будет.
Выводы:
После изучения MVC теперь можно лучше понять зачем нужны jsp.
Java код на jsp страницах использовать крайне не рекомендуется.
Java код лучше пусть используется в контроллере.
На View используются только экшн теги, EL и еще стандартная библиотека тегов jsp, о них позже. Эти инструменты лучше понятны html разработчику, который делает View, а java код разработчику контроллера
CONTROLLER – сервлет для взаимодействия между клиентом, MODEL и VIEW.
VIEW – страница, которая видна пользователю – jsp.
MODEL – класс для извлечения информации из ресурса (БД например) или добавления в него информации (иногда этот класс отождествляют с ресурсом)).
Клиент может передавать какие либо данные в CONTROLLER через VIEW, то есть через страницу, которую клиент видит.
Также клиент может получать данные на VIEW из CONTROLLER. То есть всё, что клиент видит на VIEW, получено из CONTROLLER.
Откуда же CONTROLLER берет данные, которые он отсылает на VIEW и куда CONTROLLER отсылает данные присланные в него клиентом?
CONTROLLER получает данные из ресурса c помощью MODEL и отправляет их клиенту на VIEW, а когда клиент отсылает данные в CONTROLLER, то CONTROLLER с помощью MODEL отправляет их в ресурс для сохранения их там.
Давайте создадим класс модели, класс контроллера и jsp страницу вью. В качестве ресурса для упрощения пусть будет простой объект User, а не БД или что либо еще.
Model
Для начала, создадим небольшой класс User, объект которого будет ресурсом:
package com.someclasses;
public class User{
private String name;
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
}
Код класса модели, который извлекает данные из ресурса:
package com.someclasses;
public class User{
private String name;
public void setName(String name){
this.name=name;
}
public String getName(){
return name;
}
}
Видим, что Model возвращает user, то есть объект-ресурс.
Controller
Создадим контроллер, который будет через модель извлекать объект-ресурс user и отправлять его на View клиенту.
Пример программы:
import com.someclasses.*;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.servlet.annotation.*;
@WebServlet(“/MVC_Controller”)
public class MVC_Controller extends HttpServlet {
MVC_Model model = new MVC_Model();
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
req.getServletContext().setAttribute(
“someuser”, model.getUser());
}
//С помощью бина можем на View извлеч только
//что созданный аттребут вот таким образом:
//
}
Теперь поскольку мы положили данные ресурса в аттрибут уровня контекста, мы можем извлечь их на View.
View
Теперь на View, то есть странице, которую видит клиент с помощью actionBeanизвлекаем данные ресурса положенные в контекст в контроллере:
MVC JSP
Проверка работы созданной MVC архитектуры.
Теперь запустим контроллер:
Он через модель извлечет данные ресурса и поместит их в аттрибут контекста, чтобы вью мог их отобразить
Откроем теперь вью:
Очевидно созданная архитектура работает корректно.