Познаём Java. Вторая чашка: собираем классы в кучки. Пробуем апплеты

    Чем обусловлена структура Java?


    Как вы уже слышали, Java проектировалась с рассчётом на совместимость со всем, чем только можно. Такое ограничение вынудило разработчиков Java сделать её такой, чтобы максимально упростить развёртывание приложений, при этом обеспечив логическую стройность языка.

    Как происходит загрузка классов?


    Для того, чтобы найти класс по имени когда вы его вызываете, в Java существует стандартный загрузчик классов. Он оперирует понятием classpath. Список classpath — это набор путей, где следует искать файлы классов. Каждый classpath может указывать как на директорию, так и на так называемый jar-файл (зазипованная директория со скомпилированными .class'ами и разрешением .jar, типа «йА java-archive!»). По умолчанию в classpath входят файлы стандартных библиотек и директория, из которой вы вызвали саму Java. Именно таким образом был найден класс HelloWorld — java нашла файл HelloWorld.class и запустила в нём метод main. Собственно, так и работают большинство программ, даже самых сложных. Всё начинается с одного main'а…

    Пакеты классов


    Пакет (package) представляют собой набор классов, объединённых по смыслу. Пакеты обычно вкладываются друг в друга, образуя собо иерархию, дающую понять, что зачем. На файловой системе такая иерархия выглядит в виде вложенных друг в друга директорий с исходниками. Так, исходники пакета a лежат в папке a, исходники пакета a.b — в папке a/b и так далее. Типичный путь к пакету выглядит примерно так:
    org.apache.commons.collectons. Видите, сразу ясно зачем он нужен. Чтобы использовать какой-то класс в коде другого класса, вы должны импортировать его, написав до объявления класса строку
    import путь.к.классу.ИмяКласса;
    Кроме того, если вы используете классы одного пакета часто, вы можете импортировать весь пакет:
    import путь.к.классу.*;
    Это относится ко всем пакетам, кроме java.lang — он импортирован по умолчанию, именно из него были в прошлом примере взяты классы System и String. На самом деле они лежат в некоем jar'е, в каталоге java/lang.

    Что ж, теперь вы знаете как работает загрузчик классов. В реальных проектов количество classpath измеряется десятками, а то и сотнями.

    Организация кода


    Если вы пишете свои первые маленькие примерчики и вам лень создавать иерархию классов — пусть, это ваше право. Но помните, в серьёзном проекте вы всегда должны будете разложить свои классы по пакетам. Обычно корневые пакеты создаются такими, чтобы ясно давать понять кто автор кода, и к чему он относится.
    Например:
    ru.vasiapupkin.photomaker выбирается корневым пакетом
    ru.vasiapupkin.photomaker.core сюда мы пишем классы отвечающие за логику
    ru.vasiapupkin.photomaker.visual сюда, допустим, все наши окошки приложения

    и так далее.
    Чтобы создать класс
    ru.vasiapupkin.photomaker.Starter
    вы должны:
    создать файл Starter.java в папке ru/vasiapupkin/photomaker/
    прописать в нём первой строчкой (точнее говоря, до импортов)
    package ru.vasiapupkin.photomaker;

    Коллижн


    «А что будет, если у нас будет два класса с одним именем?», — спросите вы. «Смотрите», — отвечу я.
    Допустим вы решили что вы умнее джавы и создали свой класс строки — String. Но вот проблема, у нас же уже есть такой!
    Значит, вам придётся положить свой класс в пакет, скажем ru.vp.stuff и обращаться к нему так: ru.vp.stuff.String.
    Именно поэтому не рекомендуется класть классы прямо в корень classpath — таким образом вы роете себе дорогу к несовместимости, ведь Java требует, чтобы каждый класс определялся однозначно. Именно поэтому нельзя написать так:
    import ru.vp.SuperClass;
    import ru.mashka.SuperClass;

    За это вас накажет компилятор, потому что он не будет знать, какой из них использовать.
    Мораль: правильно выбирайте и имя класс и имя пакета.

    Погоняем?


    Давайте улучшим первое приложение. Эх, классика интернета… Создадим апплет.

    Эх, может быть апплетами...


    import java.applet.Applet;
    import java.awt.Graphics;

    public class HelloWorld extends Applet{

        public void paint(Graphics g){
            g.drawString("Hello World",15,15);
        }

    }

    * This source code was highlighted with Source Code Highlighter.


    Так, что у нас тут? Импортировано 2 класса, один из них — стандартный пустой апплет, который мы будем расширять. Второй — Graphics. Graphics — это понятие из библиотеки AWT. Кстати, небольшой экскурс. AWT (Abstract Window Toolkit) входил ещё в первую Java и был предназначен для многих задач, связанных в основном с отображением.
    Так вот, объект типа Graphics позволяет нам рисовать на себе всякую муру типа строк, линий, кружочков и прочего. В данном примере мы написали строчку с отступом.
    Метод paint здесь написан не от балды — он перекрывает аналогичный метод класса Applet, и когда java будет перерисовывать этот конкретный апплет, она вызовет этот метод.

    Посмотреть на наш апплет достаточно просто — пишем небольшой HTML:
    <body>
    <applet code="HelloWorld.class" codebase="file:///home/devgru/" width="150" height="30"></applet>
    </body>

    * This source code was highlighted with Source Code Highlighter.


    … а может приложением...


    Давайте попробуем сделать HelloWorld в standalone-приложении.

    import java.awt.*;
    import javax.swing.*;
    public class HelloWorld extends JFrame{

        public static void main(String[] args){
            new HelloWorld();
        }

        {
            add(new JLabel("Hello world"));
            setSize(200,200);
            setVisible(true);
            setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        }
    }       


    * This source code was highlighted with Source Code Highlighter.

    Здесь мы полностью импортируем основные классы пакетов Swing и AWT. Swing — более поздняя чем AWT библиотека, сейчас именно она обеспечивает отображение основной части графического интерфейса для Java-приложений.
    Итак, в main мы просто создаём экземпляр класса HelloWorld.
    Сейчас наш класс наследуется от класса JFrame. Это класс из Swing, он представляет собой окно, которое отображается пока не будет закрыто.
    Блок {...} — это «общий конструктор». Он добавляется к любому конструктору нашего класса. Так как у нас нет ни одного — он добавляется к пустому конструктору без параметров, который создаётся на лету, если у класса нет ни одного.
    Мы добавляем на окно новый объект типа JLabel (т.е. надпись), затем устанавливаем окну размеры и отображаем его. Последняя строчка нужна, чтобы выполнение приложения закончилось, когда будет закрыто окно. Таким образом, вы можете быть уверены что после закрытия окна у вас в памяти не останется висеть ваше приложение.
    Запускать его нужно точно так же как и прошлое: пишем, компилируем, запускаем.

    А может и сервлетами? Наверное, потом.


    В этих двух статьях я постарался дать вам начальное представление о возможностях Java в общем. За рамками сегодняшней статьи (наверное, будут в завтрашней) остались сервлеты и прочие радости серверной части типа JSP-страниц, а также МИДлеты — приложения для мобилок. Я бы мог рассмотреть и то и то, но хотел бы знать, чего больше хотят читатели. Кроме того, возможно, нужно рассказать о самых основах языка. Примерно на том же уровне подробности, что и начало этой статьи. Напишите в комментариях, какую статью вы хотели бы видеть в следующий раз:
    — классы и интерфейсы: ООП в джаве;
    — буквы-цифры-строчки: работа с базовыми типами;
    — создание оконных приложений с помощью Swing;
    — от мала до велика: сервлеты, мидлеты и 2 слова о портлетах.

    Когда отпишетесь — станет ясно, куда копать дальше. Всем спасибо за внимание.

    Ссылка для тех, кому лень ждать завтра: основы языка (eng.).
    Поделиться публикацией
    Комментарии 70
      +2
      Хотелось бы видеть вариант «от мала до велика: сервлеты, мидлеты и 2 слова о портлетах»: так курс будет иметь более практическую направленность, а работу с базовыми типами можно рассмотреть по ходу дела.
      Спасибо за труд!
        +2
        Боюсь, ближе к ночи напишу сначала про базовые типы — а то читатели курса будут на собственном опыте колоться об ==.
          +1
          Вы активно взялись за дело — это не может не радовать!
            0
            а может слишком активно? Два урока за одни сутки. Или это потому что выходные? Просто в будние дни изучать материал в таком объеме не представляется возможным. Хотя всегда можно добавить в избранное и почитать потом. Но для меня такое стремительное изучение не подходит :)
              0
              Ну, на мой вгляд, не слишком. Главное, чтобы было, что изучать ;) Так что, если статьи полежат некоторое время непрочитанными — это не страшно, главное их наличие.
                0
                Единственное плохо, что потом такого активного обсуждения не будет :)
            0
            Если вы хотите осветить все из того что написали, то логично наверное начать с ООП, потом типы, потом swing, потом сервлеты, а так лично для меня интересны сервлеты, но со всем остальным я просто уже разобрался.
          +1
          Вот по пакетам сразу вопрос. Столкнулся я с проблемой.
          У меня в path в винде написано так: .;C:\Program Files\Java\jre1.6.0_07\lib\ext\QTJava.zip;D:\Dev\Java\3rdPartyComponents\
          Последнее — это куда я кладу всякие библиотеки, которые мне нужны.
          Запускаю NetBeans, в проекте подключаю к либам org.apache.commons.httpclient (commons-httpclient-3.1.jar лежит как раз в той папке).
          Пишу import org.apache.commons.httpclient;

          NetBeans ругается, говорит package org.apache.commons does not exist. Хотя когда я делал импорт там во всплывающем списке был этот пакет.

          Как быть?

          P.S.: пакет качал с оф.сайта.
            +1
            в Path в винде написано? а с чего это NetBeans должен лезть именно в Path, чтобы найти библиотеки?
            допишите lib в classpath вашего проекта в NetBeans-е
              +1
              > а с чего это NetBeans должен лезть именно в Path, чтобы найти библиотеки?
              Ну вероятно с того, что компилятор ява вроде как должен так делать.
              > допишите lib в classpath вашего проекта в NetBeans-е
              Добавил в свойствах проекта эту папку где только можно — не помогло.
              Попутно обнаружил, что яве не нравится сам апачевский пакет. Залез в один из классов, обнаружил, что на объявление package org.apache.commons.httpclient; НетБинс тоже ругается: Incorrect Package

              Я вообще в непонятках…
                +1
                Ну вероятно с того, что компилятор ява вроде как должен так делать.
                нет, для загрузки своих классов java использует classpath, не path, сосбсно об этом говорилось в начале статьи.

                какие библиотеки вы используете еще в проекте? возможно нетбинс уже добавил какой-то httpclient (достаточно стандартный класс для jee приложений) и они просто конфликтуют…
                  +1
                  Ну естесственно я имел в виду, что в виндовых переменных среды дописал к classpath'у свой путь. А path сказал потому, что он там же прописан, где и classpath.

                  Расскажу более подробно:
                  Сделал я пустой проект, подключил к нему библиотеку yarfraw (добавил в либы проекта yarfraw-0.92.jar и написал import yarfraw.io.FeedReader;).

                  Потом мне понадобилось скормить конструктору FeedReader объект типа HttpURL, который, как я понял, надо взять из пакета org.apache.commons.httpclient.
                  В архиве с yarfraw был commons-httpclient-3.1-beta1.jar, я подключил его точно так же, но import org.apache.commons.httpclient; не работал, как я уже говорил. Я подумал на бету, и скачал релиз той же версии commons-httpclient-3.1.jar. Убрал подключение беты, подключил это — такой же результат.

                  Вот собственно потому и пишу.
                    +1
                    Подозреваю, что дело все же с правильностью подключения библиотеки…
                    — Если бы проблема была с бетой, версией java или версией библиотеки, вы бы скорее всего увидели что-то вроде BadClassVersion, NoSuchMethodException или еще чего в этом духе, но раз его просто ipmort не видет…
                    — попробуйте создать чистый проект только с commons-httpclient-3.1.jar и проверьте там import org.apache.commons.httpclient (да и уберите его из системного classpath-а, чтобы не было проблем в других проектах с другими версиями библиотеки)
                    — проверьте так же библиотеки, которые мог добавить NetBeans по-дефолту, возможно он добавляет какие-то свои стандартные javaee либы, которые уже содержать тот самый org.apache.commons.httpclient
                      0
                      Мне кажется последний вариант маловероятнен — ну не должен нетбинс по умолчанию включать апачевские библиотеки. Думаю, они на такое бы не пошли.
                        0
                        Всё, получилось.

                        Надо было:
                        1. Оставить в classpath только одну версию библиотеки.
                        2. Импортировать не org.apache.commons.httpclient, а org.apache.commons.httpclient.*. Почему нельзя было просто httpclient, я не понял. Может поясните?
                          0
                          Это вам я поясню. Импорт — это импорт класса, вы должны указывать его имя. Конструкция import пакет; не работает ровно потому, что она и не должна. Конструкция import пакет.*; работает потому что она указывает на набор классов (типа: все которые в этом пакете) а не на сам пакет. Так было задумано разработчиками.
                            0
                            А если в пакете нет классов, а есть только подпакеты, прокатит ли импорт пакет.*;, или надо будет обязательно делать
                            импорт пакет.подпакет1.*;
                            импорт пакет.подпакет2.*;

                            ?
                              0
                              Не прокатит.
                    +1
                    вообще-то выкиньте глобальную системную переменную classpath вообще. она имела смысл разве что в яве 1.1.х

                    основной явовский рантайм всегда находится сам по себе, если нормально установлена ява.
                    ваши собственные библиотеки надо прописывать в свойствах проекта в NetBeans, а при запуске из командной строки прописывать в параметрах к java
                    устраивать свалку из библиотек включаемую в classpath всех приложений вам может неоднократно повернуться боком
                      0
                      > устраивать свалку из библиотек включаемую в classpath всех приложений
                      > вам может неоднократно повернуться боком
                      А чем, если не секрет?
                        –1
                        во-первых, часто нужно иметь специфическую версию библиотеки (и тут вы можете получить перекрытие совпадающих классов из библиотек разных версий)
                        во-вторых, библиотеки часто зависят друг от друга, что, в случае такой свалки, чревато проблемами из-за класслоадеров. подробное объяснение займет много времени, так что поверьте на слово или изучите концепцию дерева класслоадеров самостоятельно :)

                        кстати, если уж так хочется держать какие-то библиотеки в одном каталоге, который будет автоматом включаться в Classpath всех приложений, то для этого есть каталог jre\lib\ext\
                        но делать это не рекомендуется по вышеуказанным причинам :)
                  0
                  lib — в смысле commons-httpclient-3.1.jar
                    0
                    Там я уже включил это файл, я писал об этом в первом посте.
                      0
                      Попробуйте дописать ключ на вызов JVM: -cp путь_к_jar
                        0
                        > java -classpath C:/path/to/commons-httpclient-3.1.jar;D:/path/to/other.jar mypackage.MyAppMain
                        или
                        > java -jar E:/path/to/MyApp.jar
                      0
                      Вам правильно ниже написали, classpath != виндовый path
                      Чтобы добавить его вы дописываете ключ при вызове java: -classpath /путь/до/папки_или_джара
                      +2
                      Вопрос автору: а как давно Вы знакомы с Java?
                        0
                        Примерно два года. Пишу на ней на работе — полтора. А что?
                          0
                          Стало интересно, сколько нужно времени, что бы приобрести хороший опыт программирования на Java :)
                            +1
                            Зависит от того с чего начинать. На мой взгляд, не стоит сразу пытаться охватить все уровни познания, но при этом стоит знать всё и понемногу. Например после этого поста понятно как работает ClassLoader. А потом я могу рассказать, например, что можно создать свой, который будет классы, допустим, из сети вытягивать, и отдавать системному. Это будет интересно, но не нужно почти никому.
                            Так вот, подобного в Java навалом. Моё дело — рассказать о главном, а уж получать конкретный опыт — это нужно сидеть, и думать, что да как и разбираться. Ну и писать действительно работающие приложения…
                        –1
                        Именно поэтому нельзя написать так:
                        import ru.vp.SuperClass;
                        import ru.mashka.SuperClass;

                        Ну не сказать уж что совсем нельзя, этим как раз активно пользуются обфускаторы P

                        Ну и про общие конструкторы рассказывать без объяснения обычных конструкторв конечно как-то странно, а вообще неплохой ликбес)
                          0
                          Странно, у меня не получается сделать такой импорт. пишет
                          имякласса is already defined in a single-type import

                          Про конструкторы расскажу, сейчас цель была дать понять что можно творить и чего стоит опасаться.
                          –3
                          Неплохо бы было дополнить эту статейку табличкой уровня доступа к элементам класса пакета итд
                          Взято из книги Г.Шилдта
                            0
                            Всё это будет в следующей статье.
                            0
                            В примере с фреймом заголовок констрактора потерялся перед открытием фигурной скобки.
                              0
                              Хмм, не знал о такой фиче…
                                +1
                                :)
                                  0
                                  это с какой джавы есть такое?
                                    0
                                    В 1.4 уже было, если я правильно помню.
                                  0
                                  Блок вызывается при вызове любого из констракторов и исполняется перед основным кодом…
                                    0
                                    Да. Это, как написано, иногда называют обобщённым конструктором. Обычно толку от него ноль, так как вся инициализация зависит от параметров. А так — да, он перед каждым конструктором вызывается.
                                      0
                                      Ага, при необходимости вынести общий код есть более прозрачные методы, век живи, век учись :)
                                  0
                                  наверное, было бы лучшим тогда оставить и сам конструктор и фигурные скобки для наглядности
                                  +1
                                  > зазипованная директория с сорцами с разрешением .jar
                                  наверное, всё-таки «зазипованая директория с .class файлами и расширением .jar»?

                                  Про загрузку классов тема не раскрыта, напишите например, как происходит загрузка класса HelloWorld
                                  Что писать далее: расскажите про класс Class, reflection, почему может быть, что HelloWorld.class != HelloWorld.class (разные classloader-ы :) ) Static секции, модификаторы, анонимные классы.
                                    +1
                                    Да, спасибо, стормозил.

                                    Про загрузку классов можно рассказывать бесконечно, но мне кажется эта тема раскрыта настолько, насколько нужно.
                                    Согласитесь, не каждый же день вы байт-код класса получаете по сокету и скармливаете лоадеру :)
                                      0
                                      Про структуру языка будет обязательно. Заодно про то, чем становятся enum'ы…
                                      0
                                      Хотелось бы видеть маны по созданию именно веб-приложений. Т.е. коннект к базе данных, работа с шаблонами и пр. Но до этого еще, наверное, далеко… (:
                                        +1
                                        Лично меня больше интересуют Servlet + JSP, но это чисто с профессиональной точки зрения. Возможно другим читателям нужны именно мидлеты…

                                        Забегая вперед, мне хотелось бы задать вопрос, который мне давно интересен, но как-то не у кого было спросить. Может быть автор сразу и ответит…

                                        Вопрос такой: с точки зрения best practices Java и паттерна MVC сервлет является контроллером, т.е. он должен приять реквест + подготовить данные для View. Вопрос, как передать подготовленные данные из сервлета в JSP? Есть ли какой-то класс/паттерн для этого? Может быть есть какие ссылки под рукой на тему MVC в веб-приложениях под Java?

                                        Вопрос, конечно, очень преждевременный, но, как говорится, наболело :)
                                          0
                                          Хороший вопрос, но ответ прост.
                                          Вы должна использовать фильтры. Фильтр должен получать нужные вам данные и класть их в аттрибуты запроса, через
                                          request.setAttribute
                                          а в JSP:
                                          request.getAttribute…
                                            0
                                            а при чем тут фильтры???
                                              0
                                              А где вы ещё собрались делать setAttribute?
                                                0
                                                конечно в сервлете.

                                                стандартная логика

                                                doPost(reqest,response) {
                                                myLogic(myModel);
                                                request.setAttribute(«model»,myModel);
                                                RequestDispatcher dispatcher =
                                                getServletContext().getRequestDispatcher("/myapp/my.jsp");
                                                dispatcher.forward(request, response);
                                                }
                                                  0
                                                  Можно и так, особой разницы нет. Или я ошибаюсь?
                                                    0
                                                    ну, я вашего примера не видел, потому не берусь сказать чем это чревато.
                                                    более того, я 1й раз в жизни слышу про такое использование фильтров.
                                                    во-первых, для простой задачи по передаче модели между контроллером и View это лишняя сущность в приложении, во-вторых, это использование сущности не по назначению.
                                                      0
                                                      Если, допустим (хотя это и не реальная ситуация), нам нужно отображать в каждом JSP юзер-агент человека, логично будет вынести определение UA в фильтр, а не вызывать его из каждого сервлета напрямую, нет?
                                                        0
                                                        неудачный пример привели с юзерагентом. это не часть обычного MVC потока.
                                                        вообще, подобные параметры или кладут в сессию, или включают тем или иным образом в состав основной страницы (tiles framework, sitemesh, jsp include)

                                                        более-менее крупные приложение сейчас никто не пишет на голых сервлетах, обычно используют MVC-фреймфорк на основе шаблона FrontController (готовый или самописный), соответственно, общую функциональность можно добавлять в него (в FC)

                                                        фильтры используют обычно для аутентификации, логгирования/отладки, работы с кодировками, работы с DB-сессией и т.д. фактически, это всё аспекты несколько иного уровня.
                                                          0
                                                          Что ж, думаю вы правы. К сожалению, про фильтры я только читал спецификацию, т.к. обычно имею дело только с Echo-интерфейсом, который реализуется достаточно «втупую» одним сервлетом, а логика переносится чуть глубже.
                                            0
                                            стандартный и самый частоиспользуемый способ передачи параметров для JSP View это request.setAttribute(«model»,myModel)
                                            есть и более красивые способы, но они обычно требуют определенных оберток или фреймворков.

                                            одна из лучших книг по теме: www.manning.com/fields2/ её можно скачать много где…
                                            • НЛО прилетело и опубликовало эту надпись здесь
                                                0
                                                Пример использования JSF — фреймворка сервлетов:
                                                izenfire.blogspot.com/2009/01/jsf-tomcat.html
                                                0
                                                так, а нет ли случайно желающих начать со мной писать про groovy/grails?
                                                  +1
                                                  Я бы с удовольствием почитал про Groovy — пишите.
                                                  0
                                                  Насчет продолжений — думаю сколько людей, столько и мнений, но, имхо, правильнее сначала рассмотреть собственно язык, буковки-циферки, ветвления-циклы, потом ООП, ну а потом оконные приложения. Хотя можно и сразу к оконным, объясняя все по ходу дела.
                                                    0
                                                    Интересно было бы почитать про сервлеты, начиная с самого начала. От выбора сервера, ИДЕ и заканчивая каким-нибудь работающим простым примерчиком.
                                                    Н и список литературы, откуда можно уже черпать что необходимо.
                                                    Так сказать быстрый старт
                                                    +1
                                                    Знакомлюсь с Java по этим статьям. Сделал пример с Applet'ом.

                                                    import java.applet.Applet;
                                                    import java.awt.Graphics;

                                                    public class HelloWorld extends Applet{

                                                    public void paint(Graphics g){
                                                    g.drawString(«Hello World»,15,15);
                                                    }

                                                    }

                                                    Скомпилировал
                                                    javac HelloWorld.java

                                                    Сделал HTML-файл:

                                                    <body>
                                                    <applet code=«HelloWorld.class» codebase=«file:///D:/project/java/» width=«150» height=«30»></applet>
                                                    </body>
                                                    </pre>

                                                    Открываю в броузере, вижу в логах:

                                                    load: HelloWorld.class is not public or has no public constructor.
                                                    java.lang.IllegalAccessException: Class sun.plugin2.applet.Plugin2Manager can not access a member of class HelloWorld with modifiers ""
                                                    at sun.reflect.Reflection.ensureMemberAccess(Unknown Source)
                                                    at java.lang.Class.newInstance0(Unknown Source)
                                                    at java.lang.Class.newInstance(Unknown Source)
                                                    at sun.plugin2.applet.Plugin2Manager.createApplet(Unknown Source)
                                                    at sun.plugin2.applet.Plugin2Manager$AppletExecutionRunnable.run(Unknown Source)
                                                    at java.lang.Thread.run(Unknown Source)
                                                    Exception: java.lang.IllegalAccessException: Class sun.plugin2.applet.Plugin2Manager can not access a member of class HelloWorld with modifiers ""

                                                    Что где не так?
                                                      0
                                                      Странно, у меня аналогичный код запускается нормально. Какой jre и браузер?
                                                        0
                                                        Java SE Development Kit (JDK) 6 Update 11
                                                        Java(TM) 6 Update 11
                                                        Firefox 3
                                                          0
                                                          А если добавить в код класс пустой конструктор:
                                                          public HelloWorld(){}
                                                          ?
                                                            0
                                                            А так всё замечательно. Спасибо.

                                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                                    Самое читаемое