JSTL: Шаблоны для разработки веб-приложений в java. Часть 1
Эта статья является логическим развитием материалов посвященных средствам отображения информации (слой View в ставшей уже классической связке Model-View-Controller). Чтобы вы понимали место, которое занимают JSTL и Velocity нужно рассказать о Страшной Ошибке постигшей разработчиков jsp. Как вы наверняка слышали, много-много лет назад java-программисты хотевшие создать веб-приложение не имели в своем распоряжении ничего кроме сервлетов. Сервлет это был класс со специальным методом (doGet или doPost), который вызывался из браузера и должен был сгенерировать html-страницу. Очевидно, что сначала нужно было на основании пришедших от клиента данных (параметры ссылки или содержимое полей формы) выполнить расчет какой-то информации, подготовить набор переменных, массивов, списков и, второй шаг, каким-то образом надо это визуализировать, т.е. перемещать html-теги и те самые подготовленные данные. И было это ужасно:
OutputStream out = response.getOutputStream();
out.println("<html>");
out.println("<body>");
out.println("<h1> Привет, " + vasyanoFIO + "</h1>");
out.println( "</body>");
out.println("</html>");
Потом придумали jsp. Сделали его, похоже, для того, чтобы пишущие на asp (тогда еще не было asp.net) или php, не могли тыкать в java-программистов пальцами и тихо хихикать в кулачок. Действительно типовая страница характеризуется значительным превышением количества html-кода над динамической информацией (взятой из файлов, баз данных, вычисленных веб-приложением). Поэтому логично было не встраивать html-код внутрь java-кода, а наоборот встроить java-код внутрь html-шаблона.
<%@ page contentType="text/html;charset=UTF-8" language="java"%> <body> <% String vasuanoFIO = "Бла-бла-бла"; if ( 1 < 2) vasuanoFIO = "Бум-бам-тарарам"; %> <h1><%= vasuanoFIO %></h1> </body>
Выглядит ужасно. Конечно, если нужно сделать одну страничку и быстро, то это наилучший выход. Вот только “пихать” логику (в примере сравнение, расчет чему же будет равна переменная vasyanoFIO) это не кошерно. По мере роста количества расчетов и усложнения верстки код превращается в винегрет. Большой код не читаем. Код, где и html и логика, не читаем вдвойне. Очень скоро будет продублирована часть логики, и размазана по тысяче файлов, так что понять, откуда что взялось не возможно. Где-то тут еще “повесился” дизайнер, когда понял то, сколько ему нужно потратить здоровья, для того чтобы заменить размер шрифта заголовка h1 на h2. Вообще-то в стандарте jsp рекомендуемой практикой является вынос логики обработки запроса в объект bean. Например, задачу с приветствиями можно оформить в виде отдельного класса, добавить к нему поля (управляющие расчетом итогового значения).
public class HelloMachineBean {
public String fio;
public String age;
public String sex;
public String getFio() {
return fio;
}
public void setFio(String fio) {
this.fio = fio;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getResult (){
if ("male".equalsIgnoreCase(sex))
return "Это дядя " + fio + " его возраст "+age;
return "Это тетя "+fio+ " ее возраст "+age;
}
}
Обратите внимание на метод getResult – именно он содержит логику расчета сообщения, которое будет показано пользователю. Теперь нужно создать jsp-файл, в котором создается объект bean, наполняются его свойства значениями и в нужном месте извлекается расчет значения (getResult).
// создаем объект Логики, указываем его имя и java-класс <jsp:useBean id="helloMachine" class="t sti.HelloMachineBean" /> // теперь начинаем заполнять значениями поля этого класса-бина, синтаксис // property=”*” означает, что все пришедшие параметры запроса должны быть помещены внутрь bean-а. <jsp:setProperty name="helloMachine" property="*" /> // а вот еще вариант, когда указывается конкретное имя свойства, которое нужно заполнить информацией и имя поля из http-запроса <jsp:setProperty name="helloMachine" property="fio" param="fio" /> // в конце-концов, можно присвоить атрибуту значение в виде константы <jsp:setProperty name="helloMachine" property="sex" value="female" /> // а теперь использование, как будто бы в составе класса helloMachine есть свойство result. // на самом деле для обращения к свойствам используются методы getter-ы, // так что фактическое существование поля класса с именем result не существенно. <h2> Hello <jsp:getProperty name="helloMachine" property="result" /> </h2>
Такой подход имеет слишком много подводных камней (я не про то, что инициализация всех нужных полей должна быть выполнена до использования bean-а, или то, что поступающие на вход данные нужно проверить, экранировать и прочая и прочая – все это решается без проблем. Например, если создать jsp-страницу как наследник от некоторого класса сервлета, который выполняет столь нужные но рутинные действия. Основных проблем две: программисты могут (а значат, будут) смешивать java-код и html, если бы были запрещены такие теги как:
<% If ( bla-bla) … Else … %>
То у нас не было бы никакого выбора, и мы должны были бы уже писать так код, чтобы четко разделить логику (bean-ы) и внешний вид (jsp). Но тут подкралась другая проблема: реальный сайт оперирует данными не только скалярными (переменная со строковым значением fio), но и списками, массивами. Так что для их обработки нам нужны циклы, условные конструкции (если сумма денег на счету меньше чем 100 баков, то вывести ее красной, иначе зеленой). Можно было бы перенести циклы и условия внутрь bean-ов, но фактически это означает, что мы должны будем внутрь этих bean-ов перетащить также и генерацию html-кода (а к чему приводит такое решение, мы уже видели на примере сервлетов). В общем, как ни крутись, но хоть какая-то примитивная обработка условий и циклов на странице должна быть, иначе мы получим гораздо большее количество проблем.
<% String vasuanoFIO = "Нет. Ты не сможешь победить силы Зла"; if ( 1 < 2) vasuanoFIO = "Твоя борьба против каши в JSP обречена на провал, Ха-ха-ха"; %>
На далеком диком западе, в стране непуганых дизайнеров, существует страшное поверье, что если дизайнер увидит код java в странице jsp, то он сойдет с ума и ему не поможет даже волшебное зелье “Гамбургер с кока-колой”. Поэтому придумали пользовательские теги. Т.е. вы создаете java-класс, который умеет делать что-то, и внедряете его в страницу, примерно, так (детали как создавать теги, описывать их с помощью tld-файлов я пропускаю, они есть в любом уважающем себя учебнике):
<showcalendar /> <mailsend to="vasyano@mail.ru" />
Это очень удобно, ведь теперь дизайнер не пугается тегов, он знает, что что-то заключенное в угловые скобочки никак не может ему навредить (собственно, он может не отличать тег H1 от придуманного вами тега <mailsend>). И началось время, когда все стали создавать собственные библиотеки “очень нужных” тегов. Признаюсь, я сам на начальных этапах занимался изобретением тегов вида:
<my:if test=”bla == bla”> Bla-bla-bla </my:if>
В любом случае такой подход не способствовал накоплению знаний, библиотек тегов было слишком много, их делали все кому ни лень, страдало качество. Так что появление каких-то стандартов, принятых наборов тегов и их качественная реализация с последующим клеймением позором тех, кто продолжал использовать собственные “уникально-неповторимые” поделки была предопределена. И так появились JSTL – тема сегодняшнего материала.
1. Загрузить с сайта архив с библиотекой jstl (я использую версию 1.2).
2. Добавить эту библиотеку в папку WEB-INF/lib вашего веб-приложения.
3. Подключить в начале jsp-файла с помощью специальных директив taglib те библиотеки тегов, которые хотите использовать:
Основные теги позволяющие делать циклы, условия, выводить информацию на экран:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
Теги для работы с xml-документами:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/xml" %>
Теги для работы с базами данных (Ну зачем вы это сюда вставили …):
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/sql" %>
Теги для форматирования информации перед выводом и работы с i10n и i18n:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/fmt" %>
Сначала разберем теги из библиотеки http://java.sun.com/jsp/jstl/core. Начнем мы с самого простого: нужно вывести на экран браузера некоторый текст.
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head><title>Simple jsp page</title></head> <body> <c:out value="hello, Vasuano" /> </body> </html>
Вот, правда, смысла в такой конструкции мало, ведь простой, статический текст мы можем выводить и без использования тегов jstl. А вот если нужно вывести на экран какие-то динамически рассчитываемые величины, то c:out уже нужен:
<c:out value="12+56*2" />
Запустили, проверили, что получилось? Да ничего не получилось: на экран вывелся сам текст формулы, а не результат вычисления 12+56*2. Дело в том, что если мы хотим вывести некоторую информацию вычисляемую “на-лету”, то эту часть значения атрибута value тега c:out нужно поместить внутрь фигурных скобок.
<c:out value="${12+56*2}" />
Уже лучше: на экране мы увидели цифру 124. Однако вывод на экран просто статических формул не слишком частое занятие, а вот вывести значение полученной от пользователя или html-формы переменной уже лучше: <c:out value="${param.fio}" /> Теперь если я введу в адресную строку браузера нечто вроде: http://center:8080/velo/a2.jsp?fio=%C2%E0%F1%FF (Эти кракозюбры – слово ВАСЯ) И увижу на страницу … какую-то белиберду.
Aany
Собственно, другого не ожидалось: для того чтобы заставить tomcat нормально раскодировать входящие переменные нужно еще постараться. Как, что, и почему я писал в другой своей статье посвященной Русским буквами и java. Внимание, перед тем как написать имя переменной (fio) я должен указать контекст (место, где нужно искать эту переменную). Есть список предопределенных контекстов:
Контекст | Комментарий |
---|---|
pageScope | Контекст страницы (т.е. переменные объявленные на этой странице и доступные только для этой страницы). |
requestScope | Доступ к таким переменным имеют все страницы, сервлеты обслуживающие один, текущий, вот этот самый, запрос пользователя. |
sessionScope | Доступ к переменным сохраняется на протяжении всего сеанса пользователя (пока не закроет браузер или не истечет предельное время бездействия). |
applicationScope | Доступ к переменным сохраняется изо всех страниц размещенных внутри веб-приложения (самый глобальный контекст). |
param | В этом контексте находятся все переменные, полученные страницей от пользователя либо как параметры адресной строки, либо как поля html-формы. |
paramValues | Список значений тех переменных, которые были переданы в страницу пользователем, правда, формат отличен от предыдущего случая. Если там param фактически имел тип HashMap<String, String>, то здесь HashMap<String, String [] >. |
header | В этом объекте хранится информация об http-заголовках которые были переданы от браузера клиента вашему веб-серверу. |
headerValues | Список значений http-заголовков. |
initParam | Конфигурационные параметры, указанные для вашей страницы, сервлета в файле web.xml |
cookie | Список переменных помещенных внутрь cookie. |
pageContext | Ссылка на объект pageContext (см. описание служебных объектов автоматически создаваемых внутри jsp-страницы). |
Для того чтобы обратиться к переменной находящейся внутри какого-нибудь из контекстов нужно написать имя контекста, затем символ точки, и наконец имя переменной. Возможен и альтернативный синтаксис:
<c:out value="${param.fio}" /> <c:out value="${param['fio']}" />
У тега c:out есть еще несколько атрибутов:
<c:out value="${param.fio}" default="NO DATA" escapeXml="true" />
В этом примере, в случае если на вход странице не была подана переменная fio, то будет выведена фраза “NO DATA”. Атрибут же escapeXML служит для того чтобы экранировать (заменить специальные символы xml: знаки больше, меньше, кавычки …) на их entities (т.е. < > …)
Научившись выводить переменные самое время задуматься над тем как эти переменные создавать. Помимо очевидных вариантов: из параметров запроса, из объекта java bean с логикой, из сессии есть еще способ самим внутри страницы положить какую-нибудь переменную внутрь одного и указанных выше контекстов. Для этого мы используем тег c:set. В качестве его атрибутов указывается название контекста, куда мы хотим положить переменную, имя переменной и значение:
<c:set var="vasyano" scope="session" value="Вася Тапкин" /> <c:set var="petyano" scope="page"> Петька Козлов на странице </c:set> <c:set var="petyano" scope="request"> Петька Козлов в запросе </c:set> <c:set var="petyano" scope="session"> Петька Козлов в сессии </c:set> <c:set var="petyano" scope="application"> Петька Козлов в приложении </c:set> vasyano: <c:out value="${sessionScope.vasyano}" /> <br /> petyano: <c:out value="${petyano}" />
Здесь я показываю несколько хитрых моментов в работе jstl. Во-первых, обратите внимание, как отличаются имена контекстов при операции положить переменную (set) и операции извлечь переменную (out). В первом случае я должен писать слово session, а во втором случае – sessionScope. Также посмотрите, что я создал переменную petyan сразу во всех четырех контекстах. Когда же я вывожу эту переменную на экран, то название контекста, к которому принадлежит переменная, я не указал. В этом случае работает правило поиска нужного контекста: ищем переменную внутри pageScope, если не найдено внутри requestScope, если опять не найдено внутри sessionScope и, наконец, внутри applicationScope.
Есть еще один вариант синтакиса оператора c:set, когда нужно установить значение свойства некоторого java-bean внедренного на страницу.
<c:set target="${helloMachine}" property="fio" value="Ленка Слонова" /> <c:out value="${helloMachine.fio}" />
Для удаления переменных (на самом деле простого присвоения имя значения null) используется оператор remove, например, так:
<c:set var="fio" scope="session" value="Vasyano Petrovno" /> fio1 = <c:out value="${fio}" /> <c:remove var="fio" /> fio2 = <c:out value="${fio}" />
Вывод переменных без предварительного их анализа, без условных операторов малополезен. Так что нам нужны операторы if и switch. Оператор if не слишком похож на своих больших братьев в java и других языках: нельзя задать ни ветку else, ни ветку elseif (elif), можно только проверить некоторое условие и если оно истинно, то сделать что-то:
<c:if test="${param.age gt 12}"> Возраст более 12 лет </c:if> <c:if test="${param.age lt 25}"> Возраст менее 25 лет </c:if>
Обратите внимание на то, что я в условии использую не знаки “<” или “>”, а слова “gt” и “lt”, это необходимо т.к. в противном случае мы нарушим правила записи xml-документов. Еще есть такие слова-операторы:
eq – проверка на равенство
ne – проверка на неравенство
lt – строго менее чем
gt – строго более чем
le – меньше либо равно чему-то
ge – больше или равно чему-то
У тега if есть несколько необязательных атрибутов, которые возможно пригодятся вам, чтобы не записывать повторяющиеся выражения. Так если указан атрибут var, то в эту переменную будет записан результат вычисления условия (атрибута test). Куда именно будет положена эта переменная (в какой контекст) задается атрибутом scope.
<c:if test="${param.age gt 12}" var="if_less_12"> Возраст более 12 лет </c:if> <c:if test="${if_less_12}"> Еще раз повторяю: Возраст более 12 лет </c:if>
В случае если нам нужно проверить несколько условий, то оператор if – не самое лучшее решение, здесь нужно использовать тег choose -> when -> otherwise:
<c:choose> <c:when test="${param.age lt 10}"> Возраст менее 10 лет </c:when> <c:when test="${param.age lt 20}"> Возраст в отрезке от 10 до 20 лет </c:when> <c:otherwise> Срочно пройдите на процедуру усыпления </c:otherwise> </c:choose>
Теперь разберемся как работать в JSTL с циклами. Есть две разновидности циклов: для прохода по элементам некоторого списка (для прохода всех чисел в отрезке ОТ и ДО). И вторая разновидность служит для прохода по списку token-ов (частей, на которые была разбита строка на основании некоторого символа разделителя). Первый тег “c:ForEach” имеет целых 6 атрибутов управляющих его работой, но среди них нет ни одного обязательного. Все дело в том, что c:ForEach предназначен и для прохода по элементам некоторого списка, например, так:
Прежде всего, я ввел в состав описанного выше класса HelloMachineBean несколько новых методов (как-бы-настоящий свойств) возвращающих массив элементов и список элементов.
public List< String> getFriendsAsList (){
return Arrays.asList(getFriendsAsArray ());
}
public String[] getFriendsAsArray(){
return new String[]{"Васька", "Петька", "Ленка"};
}
Теперь пример использования этих данных внутри цикла:
<c:set var="friends" value="${helloMachine.friendsAsArray}" /> <c:set var="friends2" value="${helloMachine.friendsAsList}" /> <c:forEach items="${friends}" var="friend"> <h2> <c:out value="${friend}"/> </h2> </c:forEach> <c:forEach items="${friends2}" var="friend"> <h3> <c:out value="${friend}"/> </h3> </c:forEach>
Как видите, мне пришлось задействовать два атрибута тега ForEach – это items играющий роль источника данных и var – переменная, в которую будет последовательно помещаться элементы массива/списка.
Второй вариант цикла ForEach предназначен для прохода по целым числам в отрезке от X до Y, например, так:
<c:forEach var="friend_i" begin="0" end="2"> <h5> <c:out value="${friend_i}"/> = <c:out value="${friends[friend_i]}"/> </h5> <h4> <c:out value="${friend_i}"/> = <c:out value="${friends2[friend_i]}"/> </h4> </c:forEach>
Обратите внимание на то, что я могу с помощью индекса (квадратных скобок) обращаться к элементами не только массива, но и списка.
Еще один атрибут для тега forEach – это step. Его назначение управлять величиной шага, с которым выполняется проход по элементам массива. Обратите внимание, что в следующем примере атрибут step умеет корректно работать не только, когда цикл перебирает цифры в отрезке от X до Y, но и когда перебирается содержимое некоторой коллекции элементов.
<c:forEach items="${friends}" var="friend" step="2"> <h2> <c:out value="${friend}"/> </h2> </c:forEach> <c:forEach var="friend_i" begin="0" end="2" step="2"> <h5> <c:out value="${friend_i}"/> = <c:out value="${friends[friend_i]}"/> </h5> <h4> <c:out value="${friend_i}"/> = <c:out value="${friends2[friend_i]}"/> </h4> </c:forEach>
И, наконец, последний атрибут для цикла For – varStatus. В эту переменную будет помещена информация о выполняемом цикле и текущем его шаге. Значение переменной разнится в зависимости от того, какая разновидность цикла нами используется, например, когда у нас обычный цикл от X до Y, то значением этой переменной будет текущий индекс:
<c:forEach var="friend_i" begin="0" end="2" step="2" varStatus=”friendStatus”> <h5> friend_i = <c:out value="${friendStatus}"/>* <c:out value="${friend_i}"/> = <c:out value="${friends[friend_i]}"/> </h5> </c:forEach>
Если же цикл выполняется по элементам коллекции, то поведение varStatus меняется. Теперь это не число, а сложный объект с информацией о начальном и конечном шаге цикла, об том первый элемент коллекции перебирается или нет, например:
<c:forEach items="${friends}" var="friend" step="1" varStatus="friendStatus"> Status: index=<c:out value="${friendStatus.index}"/><br /> count=<c:out value="${friendStatus.count}"/><br /> first=<c:out value="${friendStatus.first}"/><br /> last=<c:out value="${friendStatus.last}"/><br /> step=<c:out value="${friendStatus.step}"/><br /> <h2> <c:out value="${friend}"/> </h2> </c:forEach>
Разобравшись с одной разновидностью цикла (наиболее часто встречающейся и наиболее полезной), перейдем ко второй – forTokens. Этот цикл похож на ранее приведенный forEach: совпадают многие атрибуты тега. Но ключевое отличие в том, что цикл идет по списку лексем, на которые была разбита строка:
<c:set var="str" value="Гравитон Фотон Бозон Мюон" /> <c:forTokens items="${str}" delims=" " var="token" begin="1" varStatus="tokenStatus" step="1"> index=<c:out value="${tokenStatus.index}"/><br /> count=<c:out value="${tokenStatus.count}"/><br /> first=<c:out value="${tokenStatus.first}"/><br /> last=<c:out value="${tokenStatus.last}"/><br /> step=<c:out value="${tokenStatus.step}"/><br /> <h2> <c:out value="${token}"/> </h2> </c:forTokens>
Итак: строка “Гравитон Фотон Бозон Мюон” была разбита на отдельные подстроки с помощью разделителя delims (пробела). Цикл был начат не с первого элемента “Гравитона”, а со второго “Фотона”. На каждой итерации цикла также выводились сведения о номере этой итерации, признаке первая ли это итерация, а может последняя. Неприятность только в том, что значение переменной count использовать нельзя, эта величина не равна “4”, как ожидалось, а растет по мере изменения цикла (похоже, цикл идет по мере разбора строки, а не после его завершения). Значением атрибута tokens может быть не отдельный символ, а некоторое их количество, например, следующий пример идентичен ранее приведенному (значение delims теперь пробел, точка и запятая):
<c:set var="str" value="Гравитон,Фотон.Бозон Мюон" /> <c:forTokens items="${str}" delims=" ,." var="token" begin="0" varStatus="tokenStatus" step="1"> <h2> <c:out value="${token}"/> </h2> </c:forTokens>
Очевидно, что написать хороший код страницы в одном файле, без разбиения его на части тяжело. Поэтому в состав JSTL входит тег, позволяющий в один jsp-файл включить другой jsp-файл, и называется этот тег import. Тег этот очень гибкий, в самом простом случае в страницу можно включить статический кусочек текста в виде html/txt-блока:
<c:import url="heading.html" />
Однако настоящей гибкости мы можем добиться лишь, когда включаемое содержимое является динамическим. Проверим, умеет ли import “включать” в jsp-страницу результат работы другой jsp-страницы:
Включаемая страница (footer.jsp):
<%@ page import="java.util.Date" %> <%@ page contentType="text/html;charset=UTF-8" language="java"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <% out.write("Сложные расчеты по внедрению информации внутрь ... " + new Date()); %> <h1> <c:out value="${externalVar}" /> </h1>
Обратите внимание, что здесь я обращаюсь и хочу вывести на экран переменную externalVar. Откуда же она взялась?
А теперь пример кода главной страницы (обратите внимание на то, что я указал область действия переменной externalVar – значит, что она будет доступна всем страницам обслуживающим данный запрос):
<c:set var="externalVar" value="Hello from Outer page" scope="request"/> <c:import url="footer.jsp" />
Аналогичным образом переменные можно передавать и из вложенной страницы во внешнюю (не забывайте про контекст переменных). Естественно, что на включаемой странице будут доступны и те переменные, которые были переданы главному скрипту из html-формы. Количество атрибутов управляющих поведением тега import гораздо больше, чем один адрес включаемого документа. Начнем с попытки включить как вложенную, страницу содержащую русские буквы. И конечно же мы увидели кракозюбры. Дело в том, что кодировка включаемого документа по-умолчанию рассматривается как ISO8859-1. Для того чтобы явно указать кодировку делайте так:
<c:import url="heading.html" charEncoding="utf-8" />
Содержимое включаемой страницы можно даже не посылать сразу в браузер клиенту, а накопить в какой-то переменной, для последующей обработки, например, так:
<c:import url="heading.html" charEncoding="utf-8" var="inner_c" scope="request" /> <c:out value="${requestScope.inner_c}" escapeXml="true" />
Последний атрибут для импорта – это varReader. В случае если мы укажем его, то результат вставки страницы не будет ни выведен на экран, ни помещен в строковую переменную var, а будет доступен только при чтении потока varReader.
Вторым наиболее часто используемым приемом сборки странички из кусочков, является перенаправление на другой адрес, для этого используем тег redirect, в качестве атрибута которого укажем url (есть еще атрибут var и scope, но глубокого смысла в их существовании я не нашел).
<c:redirect url="heading.html"> <c:param name="fio" value="${helloMachine.fio}" /> </c:redirect>
Тег param можно использовать и при внедрении в страницу “подстраницы”:
<c:import url="heading.jsp" charEncoding="utf-8"> <c:param name="fio" value="${helloMachine.fio}" /> </c:import>
Еще один малополезный тег = c:url, он служит для декорирования некоторого адреса, так если я запишу:
<c:url value="/a3.jsp" />
То на страницу будет вставлен адрес включая контекст приложения (собственно говоря, идея контекстов специфична для java, а браузер не может отличить контекст от просто имени папки), например, так:
/Имя-Моего-Контекста/a3.jsp
Источник — http://black-zorro.com/mediawiki/JSTL:_Шаблоны_для_разработки_веб-приложений_в_java._Часть_1