Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
Форум программистов > Java EE (J2EE) и Spring > JSP — с чего начать? |
Автор: nerezus 2.12.2006, 15:47 |
Знаком с веб-разработкой. Знаком с синтаксисом джавы. Хотел бы совместить. Я понял, что мне следует юзать JSP. Что мне качать? ) Я понял, что томкат+апач? ОС для разработки: винда. На серваке линух. Какие советы можете дать? |
Автор: tux 2.12.2006, 17:01 |
Apache тебе не нужен. В нем есть необходимость только при развертывании готовых приложений, да и то не всегда. В общем, обойдешься и одним Томкатом. Что касается JSP... Эта технология разработана Sun и ей активно продвигается. У JSP есть свои достоинства и недостатки. Большинство веб-приложений на Java разрабатываются с его использованием. Однако, вряд ли можно однозначно утверждать, что это лучший выбор. Почитай еще вот эту тему - http://forum.vingrad.ru/topic-44988/view/all/index.html. Возможно склонишься в сторону Velocity. |
Автор: nerezus 2.12.2006, 18:15 |
Спссибо, поставил томкат. А как теперь хеллоуворлд в нем запустить? ) |
Автор: bingo 2.12.2006, 18:40 |
А там есть примеры (если не убрал галочку при установки), залазь на localhost и порт 8080 не забудь указать (он по-умолчанию), там найдешь необходимые ссылки. Директория для веб-приложений tomcat/webapps, корень - ROOT. Да что я рассказываю как маленькому, ты уже сам скорее всего разобрался. |
Автор: Stampede 2.12.2006, 21:34 |
nerezus, очень приятно видеть, что люди тянутся в Java. Добро пожаловать в мир кроссплатформенных решений, открытых сырцов и могучей комьюнити! ![]() А теперь по сути вопроса. Даже не знаю, как бы тебе поделикатней сказать... В общем, tux уже выразил эту мысль в достаточно осторожных формулировках, поэтому повторяться не буду, а лучше обращу твое внимание на немного другой аспект вопроса. Понимаешь, тут очень многое зависит от того, для чего тебе все это надо. Если ты занимаешься этим, рассчитывая в будущем устроиться на работу в качестве жабного веб-программиста, то тут без вариантов: JSP, Struts и все такое. Потому что это мэйнстрим и если ты всего этого не знаешь, тебя просто не поймут. И совсем другое дело, если тебе это нужно для реализации собственных веб-проектов. Это будет уже совершенно другая картинка, и в этом случае я готов лично приложить максимум усилий, чтобы помочь тебе встать на истинный путь. И можешь быть уверен, что среди множества прямоезжих трактов и накатанных колей этот истинный путь выглядит едва заметной тропкой, так что без проводника его так сразу и не нащупать. В общем, пиши. ЗЫ. Хеллоуворлд запустил? |
Автор: nerezus 3.12.2006, 20:49 | ||
Надо будет сервлеты потыкать потом ) Вчера заказал книгу по J2ME ) Начну не с веба ) |
Автор: Stampede 4.12.2006, 20:58 |
"Однако вместо этого я съел пирожок" (с) сам знаешь кто ![]() |
Автор: nerezus 4.12.2006, 22:15 | ||
![]() ![]() Так что там в мейнстриме? ) |
Автор: Stampede 4.12.2006, 22:27 |
Дык! ![]() Фсмысле? |
Автор: nerezus 5.12.2006, 00:08 | ||
|
Автор: check 5.12.2006, 00:22 | ||
|
Автор: nerezus 5.12.2006, 00:26 | ||
|
Автор: Stampede 5.12.2006, 00:43 | ||
Правильной дорогой идете, товарищи! Только я так и не услашыл, вам жабный веб для какой надобности? |
Автор: nerezus 5.12.2006, 06:47 | ||
Не, если серьезно, то пока просто попробовать хочу. |
Автор: Stampede 5.12.2006, 07:12 |
Рассказывай, чего хочешь от хомепаги. От этого и будем плясать. Шаг за шагом. Потом оформим как туториал. Многим потом пригодится. Я серьезно. |
Автор: nerezus 5.12.2006, 10:21 |
Ну как минимум каркас. Т.е. ядро с подгрузкой модулей, реализующих определенный интерфейс. Класс для работы с БД с возможностью логирования, фильтрации нежелательных запросов и т.д. Унификация работы с AJAX. Унификация получения данных запроса. Умный шаблонизатор, хотя думаю вариант с выводом XML прокатит (а потом просто на клиенте XSL преобразования сделать) Естественно, сейчас я этого реализовать не могу на джаве, но хочу ) Вот ![]() |
Автор: Stampede 5.12.2006, 11:38 | ||
Не-не-не, "ты не умничай, ты пальцем покажи" (с) анекдот ![]() Я имел в виду, что вообще будет на сайте? Ну контент, само собой. Что еще? Гостевая? Блог? Интерактив какой-нибудь? Аякс - в каком месте? И чего ради? Аякс - это вообще целая отдельная песня. Было бы ради чего. В общем, давай по порядку: какие разделы и что в каждом из них. |
Автор: nerezus 5.12.2006, 19:36 | ||
http://HungeR.ru/ Из фишек: Статичные страницы Лента новостей. Лента релизов. Каталог файлов(с подкатегориями) Набор ссылок(к примеру, "друзья") А набор фишек типа php2exe не нужен ) Сейчас страница собирается из кусков, дизайн в значительной части помешан с логикой. Этого бы делать не хотелось. А аякс - я хотел еще FAQ прицепить. Вот там он будет нужен. |
Автор: Stampede 5.12.2006, 21:36 |
Да, я его уже смотрел. Значит, для начала воспроизводим то что есть, так? Хорошо, поехали. Я предлагаю следующий формат: я даю задание, ты его выполняешь. Если по ходу возникают вопросы - обсуждаем. Через несколько итераций будешь с новым движком на Java. Если все устраивает, у меня есть наготове первое задание. |
Автор: nerezus 5.12.2006, 22:35 |
Гут ) JSP? |
Автор: Stampede 5.12.2006, 22:49 |
Чур, чур меня ![]() Значится, так: для начала надо вообще забыть на время про слово веб. Это важно. То есть делаем просто standalone приложение. В нем будет один главный класс - точка входа во всю прогу. Назовем его ru.hunger.Hunger. Для простоты лукапа (look-up) сделаем этот класс синглтоном. Вообще синглтоны - это не есть хорошо (почему - потом поговорим отдельно, когда придет время), но для начала сойдет. Кроме этого, нам понабдибится класс Configuration, в который мы временно зашьем важные параметры конфигурации: параметры базы данных, кодировки и пр. Потом все это будет читаться из файла. В каждом из этих классов надо завести по экземпляру логгера. Чтобы не тащить лишние зависимости, можно взять родной джавовский логгер. Выходная форма Задания 1 Классы:
Структуру пакетов выбрать по своему усмотрению. Запуск программы - из класса TestHunger, стандартным main(). Hunger должен выполнить свою инициализацию и выдать отчет о текущей конфигурации. Все, на этом пока все. И еще одно замечание: нам по ходу придется время от времени делать рефакторинг: перегруппировывать пакеты, выносить вещи в интерфейсы и пр. Это нормально. Просто если с самого начала делать все так, как оно будет в готовом виде, то целесообразность многих решений не будет вполне очевидной. Рефакторинг сам по себе вещь достаточно муторная из-за объема правок, но на наше счастье большинство приличных IDE берут на себя большую часть рутины, так что на практике все оказывается достаточно просто. Ты какой IDE пользуешься? Если еще не выбрал, то советую взять Eclipse - у него плагинная архитектура и большое число полезных плагинов. Да и вообще штука приятная в работе. Правда, сторонники IDEA утверждают, что IDEA намного круче, но она стоит котлету денег, а пользовать кряки, как я понимаю, тебе не позволяет религия. |
Автор: nerezus 6.12.2006, 00:11 |
Угу, т.е. просто чтение конфига классом Hunger? ) Вопрос появился: какие имена у класса стандартного логгера и класса для чтения из файла? ) Чтобы знать, по каким словам koders.com для примеров мучать ) |
Автор: Stampede 6.12.2006, 00:38 | ||
Нет-нет, это пожалуйста самостоятельно. Вообще все нужно будет делать самостоятельно, читая для этого спеки, доки, статьи, обсуждения и пр, , потому что только таким образом можно сложить сколько-нибудь отчетливую картинку, что и как устроено в Java. Я и другие можем только корректировать этот процесс. Взять хоть тот же "класс для чтения из файла". А нету такого класса! Потому что весь ввод-вывод завязан на идее потоков. Или вот логгер: почему он такой, какой есть, а не какой-нибудь иной? Какими соображениями руководствовались его архитекторы? Что было его идейным прототипом? В общем, со всем надо разбираться. |
Автор: nerezus 6.12.2006, 00:48 |
Ок, идея понятна ) |
Автор: batigoal 6.12.2006, 12:32 |
Я тоже постараюсь участвовать в этой затее, если а) Stampede и nerezus не против; б) позволит время. |
Автор: Stampede 6.12.2006, 15:39 |
batigoal, я не против, но тут возникает несколько вопросов: Например, по кому будем делать зачет? По первому выполнившему задание? По последнему? Потом, сколько веток будем поддерживать? Две разные? Запутаемя. Одну? А если возникнут разногласия? Далее, если один запостит свое решение раньше, это неизбежно повлияет на ход выполнения задания другого. Хотелось бы избежать. Поэтому, batigoal, учитывая, что изначальная инициатива принадлежит nerezus'у, и объем для изучения у него явно больше, давай ты присоединишься на принципах минимального вмешательства? То есть темп отмеряем по nerezus'у, раньше него с ответами не лезем и за окончательный принимаем вариант, который выберет nerezus. То есть ты как бы присоединяешься с правом совещательного голоса. Я понимаю, что это не самые привлекательные условия, ну дак а что делать? Иначе пострадает качество учебного процесса. |
Автор: tux 6.12.2006, 15:51 |
Модератор: Для более удобного наблюдения за сериалом зафиксировал тему. |
Автор: batigoal 6.12.2006, 16:33 | ||
Главное, оптимальные. К тому же, моя скорость вполне может быть ниже (наш проект перешел в эндшпиль, потому...) |
Автор: Stampede 6.12.2006, 17:16 |
Хорошо хоть мыльной оперой не обозвали ![]() |
Автор: nerezus 7.12.2006, 00:10 |
У меня на сон уходит меньше 5 часов в день, напряженка, поэтому, думаю, обучение будет медленно. Но с конфигами через FileReader разобрался. логирование не нашел еще(искал мало), но, думаю, на крайний случай свой напишу ) |
Автор: Stampede 7.12.2006, 00:29 | ||
Ты Java по методу Илоны Давыдовой собрался учить? ![]() По заданию: то, что с FileReader'ом разобрался, это хорошо. Только если ты внимательно читал условие, читать на самом деле пока ничего не надо. Я предлагал пока зделать заглушку будущего объекта Configuration с жестко прописанными параметрами. Потому что в дальнейшем этот объект будет инициализироваться из файла XML. А для парсения XML в Java не нужно предварительно зачитывать весь файл в память. |
Автор: nerezus 8.12.2006, 20:26 | ||||||||
![]() Просто у меня напряженка в реале =)
|
Автор: Stampede 8.12.2006, 22:12 | ||
Да был такой лохотрон в начале 90-х - аудиокассеты для обучения английскому во сне ![]() То, что с временем туго - это, конечно, не очень здорово. Понимаешь, я пытаюсь напривлять твою разработку в стиле agile programming (шустрое программмирование) - слышал, наверное, про такую методологию. Ну и вот, а если на каждое задание будет уходить по нескольку дней, то эффект будет маленько смазанным. То есть на самом деле разницы большой нет, но вот психологически будет по-другому восприниматься. Но понятно, что жизнь есть жизнь и получается так, как получается. Ладно, давай к заданию. В принципе все очень неплохо, но есть ряд недочетов, которые лучше устранить. Перечисляю в призвольном порядке: 1. Имя переменной _instance противоречит конвенции и из-за этого несколько "режет глаз". Подробнее о соглашении об именах - вот здесь: http://java.sun.com/docs/codeconv/html/CodeConventions.doc8.html#367 2. Логгер у тебя конфигурится программно. В том числе оказывается, что имя логфайла зашито в коде. Между тем один из основных мотивов создания логового фреймворка состоял в том, чтобы вынести конфигурацию логирования во внешний редактируемый файл. Как это делается в штатном логгере - я, честно говоря, совершенно без понятия, так что на-ко вот скачай книжку и разберись. Заодно почитаешь там про log4j, и может быть его и выберешь. Вообще, логирование в Java - это вещь достаточно запутанная и капризная. Почему - станет ясно, когда начнешь подключать сторонние библиотеки и пытаться настраивать их вывод. В общем, сделай внешнюю конфигурацию. Книжку выкладываю вот сюда: http://bfigeiro.googlepages.com/logging.zip. 3. Тестовых классов у нас еще будет много, поэтому лучше для них создать отдельный пакет, например ru.hunger.test, и перенести туда TestHunger. 4. Классическая реализация синглтона требует приватного дефолтного конструктора, а иначе появляется возможность унаследовать от твоего класса, и вот уже у тебя в программе больше одного объекта данного типа. 5. Когда ты начнешь плотнее работать с приложением, у тебя окажется не одно рабочее окружение, а несколько. Соответственно, нужно будет как-то указывать, откуда брать конфиг. Поэтому для класса Hunger нужно как-то предусмотреть возможность задания рабочей директории. Поскольку от конфига зависит вся дальнейшая раскрутка, имеет смысл завести метод с такой сигнатурой:
Понятно, почему это надо делать не в конструкторе? Потому что класс у нас синглтонный, конструктор приватный, а инфы о том, где брать конфигурацию, на момент создания экземпляра еще нету. 6. Рассмотрим метод getConfigItem(). Это хорошо, что параметров конфига у нас пока мало. А когда будет много, мы что же, будем дублировать все методы из класса Configuration? Нет, нам этого не надо. Лучше просто отдавать объект Configuration - и пусть кому надо смотрят в нем что хотят. 7. Метод log() нам тоже не нужен. Этак мы сами себя подстрекаем использовать один общий логгер на всю прогу. А идея-то с логгерами в том, чтобы можно было, в частности, управлять уровнями логирования на уровне иерархий классов. Так что логгеры у нас в каждом классе будут свои. 8. Ввиду предыдщих двух пунктов, не очень правильно заставлять класс-раскрутчик (TestHunger) выпытывать подробности конфигурации. Раскрутчик должен сделать всего две вещи: получить ссылку на синглтон Hunger и проинициализировать его значением пути к рабочей директории. Путь имеет смысл брать из аргументов запуска программы. Если путь не указан, то берется текущая директория. А выводом параметров конфига должен заниматься сам класс Configuration, по указке Hunger. 9. Да, классу Configuration тоже не помешает знать путь к рабочей директории. Удобно будет задавать его через параметр конструктора. В общем, предлагаю переделать с учетом замечаний. |
Автор: nerezus 8.12.2006, 22:25 |
Угу, спасибо =) Только я наверно это только завтра смогу сделать =\ По крайней мере в воскресенье надеюсь уделить часов 12 джаве =) |
Автор: nerezus 10.12.2006, 12:59 | ||||||
Вот в общем написал, но чую, что плохо. Пока почитаю про коллекции ) Еще примерно какие области надо знать в первую очередь? Коллекции, потоки, сеть(http, сокеты), файлы....
|
Автор: Stampede 15.12.2006, 22:57 | ||||
nerezus, так не пойдет. Или мы делаем проект, или не делаем. Из девяти замечаний, которые я привел, ты отреагировал только на пункты 1, 3, 4, 5, 7, да и то не в лучшем виде. Зачем сделал публичной переменную config? Там просто геттер нужен. Где метод в Configuration, который возвращает текстовое описание конфига? Можно для этой цели переопределить toString(). Почему по-прежнему явно инициализируешь логгер? Зачем из TestHunger пытаешься вывести значение конфиг параметра? А если будет десять параметров? А если двадцать тестовых классов (хинт: со временем и тех, и других может оказаться намного больше). И потом, что это за второй параметр в init()? Передаешь в него вроде файл конфига, а используешь для вывода логов. Неряшливость, невнимательность. В общем, если хочешь продолжать, то давай работать, если нет - нет. С логгером если не можешь разобраться, то сделай хотя бы так: подключи либу log4j и положи в корень классов файл log4j.properties с таким содержанием:
Переменная логгера определяется в одну строчку:
Жду новостей. |
Автор: Omut 28.12.2006, 21:23 |
Tак, тема уже не зафиксирована ![]() ![]() |
Автор: tux 28.12.2006, 21:29 |
Omut, а сам? Нет желания поучаствовать? Если согласен, верну тему на место. Хотя... Смотря, что скажет Stampede. |
Автор: nerezus 29.12.2006, 07:22 |
Я тоже за то, что batigoal поработает, т.к. у еня сложилось предвзятое мнение к джаве в вебе... Я конечно извиняюсь за потраченное время, но.... короче еще раз извиняюсь =( |
Автор: batigoal 29.12.2006, 11:45 |
У меня назрело решение об альтернативном проекте, так что я теперь "вне игры" ![]() |
Автор: Omut 29.12.2006, 12:11 | ||
Блин!!!!! |
Автор: Hidrag 30.12.2006, 01:01 |
А могу я себя выставить в роли ученика? ..правда есть уже небольшой опыт (ссылка в подписи - все на JSP/Servlet сделано) но многое еще не осовоил, например куки, JavaBeen... да многое наверное... Добавлено @ 01:02 nerezus, почему сложилось такое мнение? |
Автор: Stampede 31.12.2006, 00:05 | ||
nerezus, а ведь мы с тобой к вебу еще даже не притронулись. Похоже, тебя отпугнула именно Java как таковая. Что я могу на это сказать... Да, в изучении Java существует некоторый начальный порог, но если его пройти, дальше все становится не так грустно. Так что может быть когда-нибудь еще вернешься.
Ну что ж, давай попробуем. Исходные данные сопоставимы. То, что есть опыт с JSP - вообще отлично: значит шишки уже набиты ![]() Поэтому первым делом: расскажи, чего хочешь от сайта. В частности, зачем предусмотрена регистрация? Что будет доступно юзерам и недоступно прочим? |
Автор: Hidrag 31.12.2006, 00:57 |
Хочется развить сайт в такой некий опен-сорс ресурс, типа в вкладке проектов будут описания начатых проектов, если посетитель не зареген то он может скачать только бинарникили вообще ничего, если зареген то и исходник, и например обновить файлы проекта (например что то дописал) - пока только это в голову пришло... Да и хостинг там сильно ограничен всего 10 мб и отсутствует возможность средствами сервлета создавать и редактировать файлы, а так бы админку сделал бы для наполнения содержимым, правда есть доступ к БД, можно сделать админку типа, написал текст для главной странице он залился в базу, потом при вызове этой страницы данные из базы считались и вывелись... Еще на хостинге нельзя почтовиком пользоваться, придется функцию "Восстановления пароля" сделать такой чтобы данные о таком запросе вбивались в базу а админ потом уже сам проверял и отсылал в ручную... Еще была идея сделать гостевую... но не как обычно в виде мини форума, а в виде аплета. Аплет как паинт, т.е посетитель рисует на нем мышкой, жмет - сохранить и этот рисунок отображается на странице, естественно регулировать количество этих "художеств"... Так что этот сайт скорее полигон, на данном этапе, чем конечная цель. Если из него что то вырастет то будет гут, нет, так тоже не обижусь так как опыт при его создании очень важен. Одна из задач такая - для пользователя со статусом админа показывать юзеров. Одно из решений это считать данные из базы в колекшн а потом значения из колекшена выводить в JSP в цикле... может есть более красивое решение? Типа считать данные в некий датасет, кинуть на страницу таблицу где источником данных взять этот датасэт (влияние долгого программирования на Delphi)? воть... ![]() Всех с наступающим! ![]() ![]() |
Автор: Hidrag 8.1.2007, 15:55 |
Итак, первая задача: 1. Сделать страничку админки - слева размещаются ссылки: "Юзеры", "Заявки на восстановление паролей" - пока только эти, таблицы базы будут появляться, будут появляться новые ссылки. По щелчку на ссылке к БД идет запрос (стандартный селект *) и данные выводятся в таблицу, после чего можно любую запись этой таблицы изменить/удалить соответственно к БД полетит что то типа апдейт или делит - админка состоит из одной веб-странички - слева ссылки, в центра таблица, справа кнопки удалить/изменить/вставить... Как работать с БД, для меня не проблема, проблема в том что я не знаю как нарисовать таблицу и вывести в нее данные, кроме как запихать результат запроса в коллекцию... и потом как сделать возможность редактирования данных этой таблицы и чтобы потом передать верный sql экзекьюту ![]() |
Автор: Иван Человеков 8.2.2007, 12:34 |
Ребята хотел от себя добавить. В изучении чего либо существуют пороги, пройдя который ты уже будешь легче двигаться вперёд. Видимо они будут всегда, но самый трудный, видимо, и высокий – это первый, он окутан страхом и сомнением: «Смогу ли я?». Может придти и более коварная, потому как незаметная мысль: «Сменить предмет обучения, потому как он мне не по душе, либо не подходит». Например: «Человек взялся за Java (нужное вписать), месяц или два поучив, он говорит не мне не нравится синтаксис, сама её идея, надо знать английский, потому как документация к ней почти вся на английском, возьму-ка я, Delphi (нужное вписать)!» Почему мысль коварная? Да потому, что она постоянно будет приходить, и скорее всего Вы будете поступать уже по шаблону – выработавшейся привычке. Бывают и исключения, человек часто занимается тем, что ему не по душе, а что круто или за это много платят денег ![]() ![]() Не нравится быть программистом не будь им ![]() ![]() ![]() ![]() Любимое дело будет давать плоды не только в деньгах, а в моральном удовлетворении ![]() ![]() Во как, а начал с порогов, которые сам преодолеваю ![]() Удачи и здоровья ![]() |
Автор: KaKTyCc 10.2.2007, 23:41 |
Доброго времени суток! Я тоже задал себе вопрос, JSP — с чего начать? И думаю я не один. Хочется заниматься веб разработкой на Java. Сейчас очень много всевозможных фрэймворков, технологий и т.д. И действительно задаёшься вопросом с чего начать и что изучать, чтобы максимально быстро разобраться в основном и главном, а остальное уже прирастёт. Кто подскажет? С Java не знаком. Сейчас работаю Oracle Developer'ом. В будущем хочу заниматься разработкой веб приложений на Java под Oracle. |
Автор: Иван Человеков 12.2.2007, 08:03 |
KaKTyCc, начни с ичучения языка ява ![]() ![]() |
Автор: Dagger 20.2.2007, 22:10 |
Народ, могу продолжить сериал. Кто-то тут из джава программеров хочет еще делиться опытом?:-) |
Автор: Tony 20.2.2007, 22:27 | ||
Последнее вреня о4ень много вижу, 4то c++ девелоперы мигрируют на java. Почему? |
Автор: powerOn 21.2.2007, 00:37 | ||
|
Автор: Hidrag 21.2.2007, 11:36 |
ИМХО тему можно откреплять, все что нужно теперь можно найти в факе в разделе студенческого отдела кадров, Антон там все хорошо расписал. |
Автор: tux 21.2.2007, 11:48 | ||
Подождем до весны, может кто созреет. ![]() |
Автор: fixxer 23.5.2007, 13:04 |
Кадетов еще набираете? |
Автор: Stampede 23.5.2007, 16:58 |
fixxer, рассказывай, чего хочешь - покумекаем ![]() |
Автор: fixxer 24.5.2007, 09:38 |
Хочется продолжить начатый тур. Цель - научится проектировать грамотную архитектуру. (Loose coupled и все такое) База java есть, но много белых пятен. Да и с ЕЕ мало опыта. |
Автор: Izabela 24.5.2007, 11:46 | ||||
nerezus, сли использувать JSTL теги---будет менше смести ![]() Добавлено через 1 минуту и 25 секунд
nerezus, сли использувать JSTL теги---будет менше смести ![]() |
Автор: Izabela 24.5.2007, 12:07 |
аааааааааааааа....ступила что тема уже на 4 страницах............![]() Stampede, |
Автор: Stampede 24.5.2007, 18:38 | ||
А, понятно. Боюсь, тут я помочь не смогу. Это тебе надо сюда: http://java.sun.com/javaee/5/docs/tutorial/doc/. Там как раз все грамотно и пре EE. Та архитектура, которую я могу предложить - оне не столько "грамотная", сколько "с позиций здравого смысла". Увы, при нынешнем положении дел в Java-технологиях эти два понятия совпадают далеко не всегда и не везде. Поэтому еще раз: все, кто преследуют абстрактный интерес - пожалуйста, используйте официальную литературу и общепринятые подходы. Так будет полезнее для вашей карьеры и спокойнее для души. Сюда же прошу обращаться только тех, кто замыслил свой личный онлайновый проект - который планируется разрабатывать, сопровождать и развивать самостоятельно. Вот тут сразу найдется о чем поговорить. Пока что ближе всех к такому определению был автор этой темы nerezus, но он, похоже, разочаровался в Java. А жаль. Всякие Ruby on Rails, конечно, подкупают легкостью начального вхождения, но в какой-то момент ведь все "домашние заготовки" из серии "convention over configuration", которыми и обусловлена эта легкость, закончатся, и придется таки заняться реальным программированием. И тут Java ой как поборется... ![]() |
Автор: nerezus 24.5.2007, 19:08 | ||
Собственно со времени открытия темы успел поковырять J2ME(причем очень успешно) и Python, где впервые увидел РАБОТАЮЩУЮ реализацию ORM(в свое время хотел писать такое под пхп, но потом как-то поугас). Сейчас заказал себе трехтомник Дейтелов по джаве и кучу книг по теории программирования, но сейчас почему-то считаю, что при программировании опыт набирается гораздо быстрее, чем при чтении ) Тот же xmlpull освоился за 5 минут, в то время, как в книгах уделяют парсингу XML намного больше места и времени. Сейчас для себя поставил ряд целей, среди которых есть и сайт на джаве(на вашем фреймворке, кстати). Однако самая главная цель - дописать оставшиеся 90 страниц документации за следующие 15 часов, с чем и удаляюсь ;) |
Автор: fixxer 24.5.2007, 21:33 | ||
Да, вот это я наверное и сам как-нибудь освою. Просто мне очень импонирует Ваш подход когда ядро системы проектируется в отрыве от морды. Я сам об этом думал, но наверное опыта маловато. И в начале топика понял что процесс пойдет именно по этому пути. Но nerezus всех обломал. Поэтому и хотелось продолжить. Ну нет так нет. Да, если не сложно, поясните, пожалуйста, почему по Вашему мнению грамотная и не противоречащая здравому смыслу архитектура порой различаются. Как по мне так это нонсенс. |
Автор: Hidrag 24.5.2007, 22:31 |
Все правильно, это как автомобиль! Сборка начинается с двигателя и заканчивается уже кузовом - сначала логика и движок приложения, потом уже и гуишник или веб морда ко все этому крепятся. |
Автор: Stampede 25.5.2007, 01:42 | ||||||||
Привет, nerezus - скока лет, скока зим ![]()
Правильно считаешь. Книшки, конечно, пригодятся, хотя бы в качестве справочника, но без реальной практики программирования никакого опыта не прибавится. Причем речь не об учебных примерах, а именно о реальных проектах.
Похвальный выбор ![]() Теперь fixxer.
Я очень рад, что ты выделяешь этот аспект. Для многих это совсем не очевидно, так что комментарий Hidrag'а тут совершенно мимо кассы: и пример неудачный, и тезис не подкрепляется практикой - и мы к этому еще вернемся. Что касается продолжения сериала с твоим участием: понимаешь, нужна задача, цель. Нужно, чтобы ты зачем-то хотел довести дело до конца. Иначе все бросится на полпути. Только впустую потратишь свое и чужое время. Это я тебе как гносеолог со стажем говорю, ибо практика показывает, что эффективность когнитивного процесса напрямую определяется степенью мотивации. Ну и напоследок разъяснение.
Эх, чувствую, начать придется издалека... Если взглянуть вокруг внимательным глазом, то нетрудно заметить, что нонсенсы такого рода окружают нас буквально повсюду. Вот навскидку пара примеров. Пример 1. В моей цифровой мыльнице (Кодак) при разряде батареек слетают все настройки. Приходится каждый раз выставлять их заново. Еб@н@фты! У вас в аппарате 32 мега встроенной энергонезависимой памяти - неужели нельзя было отвести кусочек под хранение настроек??? Пример 2. У меня в машине магнитола, часы и розетка под прикуриватель работают только при включенном зажигании. Нахера?! Где блин логика? Получается, если что-то надо (например, элементарно подзарядить сотовый или влючить музыку) - приходится вставлять ключ и включать зажигание. В результате пару раз на кемпинге приходилось заводиться прикуриванием. Примеров такого рода - миллион. О чем это говорит? О том, что мир не всегда и не во всем устроен в гармонии со здравым смыслом. Например, в случае с техническими девайсами наивный потребитель может думать, что рынок и конкуренция должны заставлять производителей выпускать изделия высочайшей степени функциональности, юзабилити и потребительских характеристик. А на деле выходит, что главное, к чему подталкивает рынок - к регулярному выпуску новых моделей. А какие они при этом получаются, всем ровным счетам наплевать. Примерно такая же петрушка и с софтом. Sun'у по большому счету пофиг, хорошо ли, удобно ли разрабатывать приложения под их спецификации. У них свой процесс разработки индустрийных стандартов. Достаточно непростой, надо заметить, процесс, и при этом взаимоувязанный со множеством других процессов: документрование, написание туториалов, разработка программ сертификации, работа с партнерами, и многое-многое другое. Неудивительно, что при таком положении дел многие вещи появляются на свет в виде, достаточно далеком от идеала - особенно учитывая концентрацию теоретиков во всяких комитетах по стандартам. Но раз уж появился - делать уже нечего: стандарт есть стандарт. И начинается "мыши плакали, кололись, но продолжали есть кактусы". Например, счастливо отведать Java DOM. Когда противоречия между желаемым и действительным достигают особой остроты - ну, приходится как-то реагировать. Как правило, перенимают решения, ставшие стандартом де-факто. Так появился EJB 3 (Hibernate), пакеты concurrent (oswego), logging (log4j) и многое другое. Но до многих вещей руки так и не доходят. Может, со временем вместо неработающего JAAS приспособят Acegi. Может, канонизируют легковесные контейнеры, взяв за основу Spring. Ну а пока - вот так, как есть. Так что ничего особенно необычного - не более, чем во всем остальном. Но разве кто-то обещал, что будет легко? ![]() |
Автор: fixxer 25.5.2007, 06:37 |
Stampede, спасибо за объяснение. Просто я пока архитектуру воспринимаю с точки зрения ООП, и привязка (увязка) технологического стека к domain model, это наверное следующий этап моего развития. ![]() |
Автор: vzf 25.5.2007, 09:45 |
Stampede, можете рассказать в каких случаях не работает JAAS и чем Acegi лучше. P.S. Просто интересно ![]() |
Автор: Stampede 25.5.2007, 19:27 | ||
Нет, это будет оффтоп. Можете создать отдельную тему, если есть желание. |
Автор: diablero 19.6.2007, 18:03 | ||||||||
Пожелал стать новым учеником, и получил на это согласие. Задача. Основная цель изучения это реализация своего проекта. Сайт не сложный, со стандартными сервисами ,к примеру, http://real-english.ru. log4j.properties. Пишет все в консоль и файл.
|
Автор: Stampede 19.6.2007, 18:55 |
diablero, c почином! ![]() Для начала в общем неплохо. Замечания такие. 1. Название пакета. Принятая в Java схема именования пакетов помимо всего прочего служит цели по возможности разнести пространства имен разных производителей, дабы минимизировать риск конфликта имен. Для этого предлагается начинать название пакета с префикса, который хоть как-то гарантирует уникальность - с домена конторы, записанного задом наперед. То есть если у проекта Апачи есть домен apache.org, то пакеты Java, разрабатываемые под его крышей, имеют вид org.apache.*. Ты сейчас выбрал имя ru.infinite. Но домен infinite.ru уже существует, и если только это не твой домен (в чем я сильно сомневаюсь), то в будущем это может быть чревато ненужными осложнениями. Идеологически более правильно было бы пойти сейчас на сайт любого регистратора (например, http://www.webnames.ru/) и если не зарегистрировать, то хотя бы подобрать себе свободный, незанятий домен. 2. Название проекта. Ты пока что просто подобрал название, доставшееся от nerezus'а, Hunger. Вряд ли это имя отражает суть твоего начинания. Между тем название проекта - это исключительно важный момент. "Как вы яхту назовете, так на ней и поплывете" (с) ![]() У меня еще есть замечания по коду, но давай мы сначала решим эти два вопроса. |
Автор: diablero 19.6.2007, 22:28 |
infinite.ru был свободен. Исправил... |
Автор: Stampede 20.6.2007, 00:04 | ||||||
Смотрим:
Но да ладно. Значит, говоришь, ru.selfexpression? Ну что ж, нормально. А насчет Tools точно уверен? Больше походит на название класса-утилиты... Впрочем, хозяин - барин. Теперь следующее. Я бы хотел, чтобы ты создал папку, которая будет домашней директорией всей проги, а в ней такую структуру:
Далее, в командной строке запуска проги предусмотри два необязательных аргумента: (1) путь к домашней директории и (2) имя файла конфига. Дефолтным значением для первого пусть будет текущая (рабочая) директория, а для второго - какое-нибудь фиксированное имя, например, tools.properties. Содержимое log4j.properties придется подправить, чтобы он открывал лог-файл относительно домашней (а не текущей!) директории. По счастью, log4j понимает переменные окружения, так что это можно указать таким образом:
Соответственно, переменную tools.home нужно будет выставить в коде (через System.setProperty()) до вызова PropertyConfigurator.configure(fileName). То есть не в конструкторе, а в методе init(), потому что конструктору синглтона мы никакие параметры передать не сможем. Сам файл log4j.properties удобно будет засунуть в директорию conf, чтобы все конфиги были у нас в одном месте. Разумеется, при этом надо будет не забыть скорректировать логику формирования полного пути к этому файлу относительно домашней директории. Теперь по коду: 1. Конструктор класса Configuration пусть принимает имя файла конфига. Хорошо также отображать это имя в выводе описания конфига. Что-то вроде:
2. В самом конце метода init() надо будет дернуть метод Configuration.toString() и вывести полученную строку в лог. Так у нас будет сохраняться полная история, что, когда и с какими параметрами мы запускали. 3. Класс Tools не должен никому давать свой логгер! Я уже писал об этом. Log4j позволяет как угодно тонко настраивать уровни и форматы вывода, но вся эта логика основана на иерархии пакетов, и если мы предоставим себе возможность пользоваться одним общим логгером на всех, то сами спрвоцируем себя на "насилование" (abuse) полезного инструмента. 4. В классе test (кстати, в Java названия классов принято писать с большой буквы) нужно проанализировать аргументы командной строки, получить экземпляр синглтона, и запустить его инициализацию. Больше там ничего не нужно. Обо все остальном должен позаботиться главный клас приложения. Вот так вот пока. |
Автор: diablero 20.6.2007, 10:12 | ||
Отредактировал свой пост выше, в котором я код выкладывал. Потому, что есть парочку вопросов. 1. Чего - то я не пойму как быть с логером. В приватном конструкторе я его инициализирую относительно папки проета. А как быть при вызове метода init() ? Логер же нужно перенастроить. 2.
Я чего-то не могу понять как будет происходить логирование. Если не трудно покажи на коде. 3. Как задать имя лог файла? Через переменные окружения или там есть какой-то метод? |
Автор: Stampede 20.6.2007, 19:30 | ||||
Хорошо, смотри:
Теперь комментирую. Обрати внимание, что код стал намного короче. Это особенно важно для тестового класса. Если тесты делать длинными, то писать их будет в лом. А надо, чтобы было в радость ![]() Теперь по главномк классу. Я ведь тебе намекал, что в конструкторе ничего особо полезного сделать не получится, поскольку мы еще не знаем путь к домашней директории. Ну так и незачем тогда вообще его засорять - оставим его пустым. Сам по себе приватный дефолтный конструктор нам нужен, чтобы мы случайно (или кто-то другой по злому умыслу) не унаследовали от главного класса. Я также убрал лишние проверки и выбрасывания исключений. В том виде, в котором они у тебя были, от них пользы все равно никакой. Когда у нас наберется побольше кода, тогда и обсудим стратегию обработки ошибок, лады? Еще один важный момент в реализации синглтона: создавать инстанс надо прямо при объявлении статической переменной. Тогда он гарантированно создастся до первого обращения к методу getInstance(), что избавит нас от необходимости делать проверки и объявлять его синхронизированным. Это, в общем, азбука паттерна Синглтон. Ну вот, а теперь ты расскажи, что у тебя получается, и какие пока ощущения от работы над проектом. Да, укажи, какой IDE пользуешься. До связи. |
Автор: diablero 20.6.2007, 21:14 | ||||||
Договорились.
Пока как все будет мне не ясно. Но уже из того что есть я извлек много пользы для себя. Одно дело изучать самому, другое, под руководством знающего человека. Ощущения только положительные, интерес не только не пропал, но и обострился ![]() Жду заданий...
IntelliJ IDEA 5.0 |
Автор: Stampede 20.6.2007, 23:32 |
Ага, отлично. Давай теперь сделаем загрузку конфигурации из файла. Нужно также завести несколько переменных в конфиге и определить для них геттеры. Переменные такие:
Загрузку лучше вынести в отдельную функцию load(). Обрати внимание, что ключи для пропертей принято писать с маленькой буквы, разделяя слова точкой. Строки ключей можно определить как константы, хотя в общем не обязательно. Каждую переменную инициализируй дефолтным значением. Это позволит минимизировать объем конфигурирования. Метод toString() пускай выводит реальные данные конфига (непосредственно из переменных). Да, еще попрошу не редактировать исходный пост, а постить новый код. Это чтобы тем людям, которые позднее будут читать эту ветку, была более понятна логика изменений. |
Автор: diablero 21.6.2007, 17:44 | ||||
maxConnections = 90. Потому что посмотрев доки mySQL, обнаружил эту цифру, как максимально число подключений. Исходя из своего опыта, скажи, какие должны быть значения по умолчанию?
|
Автор: Stampede 21.6.2007, 18:44 | ||||
Так-так, минутку... А что же это ты так смело присваиваешь значения переменным?
А если параметр отсутствует в пропертях? Для чего же мы тогда заводили дефолтные значения? Кроме того, если в конфиге будет пропущен параметр max.Connections, то прога вообще не запустится, так как вылетит по NullPointerException. Так что тут-то как раз проверки нужны. И по поводу исключений. Вот мы наконец и столкнулись с ситуацией, когда игнорировать их нельзя, поскольку они чекнутые (checked). Совсем-то их молчком проглатывать, как у тебя - это тоже не дело. Давай пока сделаем так: в тех местах, где может выбрасываться чекнутое исключение, будем оборачивать его в RuntimeException и пробрасывать наверх:
В общем, надо исправить. Когда сделаешь - потести с пропущенными параметрами конфига, вплоть до полностью пустого файла. Убедись, что в этом случае подхватываются дефолтные значения. Да, и по поводу параметра maxConnections. Если честно, совершенно пофиг, чему он будет равен. Мы все равно будем использовать пул соединений. Я просто хотел, чтобы у нас был какой-нибудь числовой параметр - чтоб служба медом не казалась ![]() |
Автор: diablero 21.6.2007, 22:36 | ||
Подправил.
|
Автор: Stampede 21.6.2007, 23:55 |
Вообще-то я немного не такой подход имел в виду. Я хотел бы, чтобы все параметры конфига фиксировались именно в переменных, а не вытаскивались "на лету" из пропертей. Одна из причин заключается в том, что нам может со временем понадобиться перейти на другой формат конфигурации, например XML. А коль скоро у нас доступ к параметрам уже "зашит" в коде геттеров, то придется все менять. То есть еще раз: идеологически правильнее сделать так, чтобы класс конфигурации мало зависел от формата хранения. Для этого формато-зависимый код надо как можно сильнее локализовать и изолировать. У нас для этого есть функция load(). Тогда с переходом на другой формат мы просто перепишем этот метод - и всех делов. Если сильно захочется, можем даже оформить в виде иерархии классов. Но это в данном случае будет, пожалуй, overkill. ЗЫ: Еще раз напоминаю: не надо редактировать код в ранее написанных постах. Люди ведь не поймут, о чем мы тут говорим. |
Автор: diablero 22.6.2007, 00:14 | ||
Подправил. Часть 2
|
Автор: Stampede 22.6.2007, 00:45 | ||||
Ага, теперь такой момент: зачем нам классовая переменная ptoperties? Смотри, ты объявляешь ее в одном месте, создаешь для нее объект в другом, а используешь - в третьем. У Блоха на этот счет есть даже отдельное правило:
А Блох - это товарищ, который знает, о чем говорит ![]() Когда ты сделаешь переменную properties локальной для блока try, ты перестанешь ее видеть в том месте, где у тебя извлекаются значения параметров. Пофиксить можно переносом этих строк вовнутрь блока try. И еще по поводу параметра max.Connections. У тебя сейчас вот так:
Но тут по-хорошему надо бы различать два случая - когда параметр просто отсутствует, и когда он не является валидным целым. В первом случае надо просто присвоить дефолтное значение, а во втором - это на усмотрение. Я бы так и не ловил его вовсе, благо оно не чекнутое, или ловил и оборачивал в другой RuntimeException, с подробным указанием причины. |
Автор: diablero 22.6.2007, 01:07 | ||
Давай оставим пока так. А позже уже определимся куда и что будем делать со всеми исключениями. |
Автор: diablero 22.6.2007, 01:25 | ||
Понял всю глубину. Такая задержка в понимание из-за столь позднего времени. ![]() Уже скоро на работу, посплю чуток. Днем подправлю. Может еще одно задание? ![]() |
Автор: Stampede 22.6.2007, 01:30 |
Нет, нам нужно сначала подвести важную черту. Зафиксировать, тыкскыть, достигнутое. Так что до завтра. |
Автор: batigoal 22.6.2007, 07:22 | ||
Я обычно решаю эту проблему так: создаю класс Config, а в нем - внутренний класс ConfigReader. В случае изменения формата хранения - будет правиться ConfigReader, который никем, кроме самого Config не используется --> хорошо локализован. |
Автор: Stampede 22.6.2007, 09:32 | ||||
Вот, обрати внимание, diablero - batigoal приводит очень хороший пример разбиения компонентов по границе функциональной ответственности, причем имено в технике ООП, специфической для Java - с использованием внутрених классов. Это и есть то, что называется "слабо связанными" (loosely coupled) компонентами. Тут уже нетрудно увидеть, как можно развить этот подход таким образом, чтобы вообще ничего не править, а просто по мере надобности использовать тот или иной ридер конфига. Например, завести отдельно:
Но тут я еще раз напоминаю, что мы не пытаемся написать компонент общего назначения, и раньше времени вводить излишнюю гибкость нам ни к чему. В английском языке есть хорошая поговорка: "we'll cross that bridge when we come to it" (когда дойдем до моста, тогда и будем по нему переходить). Она хорошо передает суть "шустрого программирования"(agile programming) - начинай с самой простой реализации, которая только будет работать. Важно только, чтобы в этой реализации было как можно меньше "закладок", которые впоследствие затруднят нам функциональное дробление компонент, как это имело место с твоей ранней версией получения параметров конфига. А вообще в борьбе внутреннего Шустрого Программиста и ОО Архитектора внутри себя, программист должен доминировать безоговорочно, и если что - не стесняясь бить архитектора по рукам, чтоб не терял чувства реальности ![]() batigoal, спасибо за удачное развитие темы. |
Автор: diablero 22.6.2007, 13:10 | ||||||
мини-фабричка
|
Автор: Stampede 22.6.2007, 13:40 |
НЕТ, НЕТ, НЕПРАВИЛЬНО! Я же говорю, НЕ НАДО раньше времени ваять лишний ООП! Пожалуйста, верни все как было, у тебя же уже все было почти хорошо, за исключением чтения maxConnections. А если уж говорить о разбивке на классы... Зачем ты перенес переменные в ридеры? Переменные конфигурации должны быть именно в классе конфигурации. Поясняю еще раз. Вот есть класс конфигурации. Он знает (и может выдавать) только те переменные, которые мы в нем пропишем. Как эти переменные инициализируется, откуда берутся значения для них - его не колышет абсолютно, для этого есть вспомогательные классы-ридеры. И ридеры не дублируют переменные конфига - они работают непосредственно с приватными полями класса Configuration. Поэтому-то batigoal и подчеркивал, что классы объявлены как внутренние - чтобы у них был доступ к этим полям. Но я еще раз повторяю, не надо нам этого пока. Так что вертай все взад. |
Автор: diablero 22.6.2007, 13:55 | ||
Вернул как было, и исправил ситуацию с maxConnections
|
Автор: Stampede 22.6.2007, 14:11 | ||
И опять неправильно. Правильно (почти) было в том вариенте, который ты запостил перед тем как пойти спать. Вот этот вот:
Там все, что нужно доделать - это проверять на null значение properties.getProperty("max.Connections"), и обрабатывать отдельно. Ты же говоришь, что понял этот момент. diablero, может ты не выспался? Я серьезно. Добавлено через 3 минуты и 59 секунд diablero, извини, не заметил вот это: String.valueOf(maxConnections). Значит, осталась только классовая переменная properties. |
Автор: diablero 22.6.2007, 16:12 |
Блин, не с того поста вертал все взад ![]() |
Автор: Stampede 22.6.2007, 18:13 |
Хорошо. Теперь нам нужно сделать важную вещь - подвести жЫрную черту под тем, что мы на данный момент наваяли. Я попрошу тебя сбросить файлы проекта в zip-архив и залить в аттачмент. Предварительно убедись, что все нужные файлы в наличии, все компилируется и работает. В названии zip файла предусмотри номер версии. Ждем-с. |
Автор: diablero 22.6.2007, 19:00 |
Готово. |
Автор: Stampede 22.6.2007, 20:58 |
Ага, посмотрел. В целом нормально, пойдет. Только ты недовложил зависимые библиотеки (log4j). Еще замечание по методологии тестирования: у тебя в infinite.properties прописано max.Connections=90. А как же ты тогда проверял, что оно перебивает дефолтное значение, если и там и там 90? Правильнее использовать разные. Кроме того, нет такой кодировки - KOI-8, есть KOI8-R. Но это в общем детали. Самое главное: мы заложили фундамент будущей проги! То, что у нас при этом получилось - уже хорошая основа для дальнейшего наращивания функциональности. У нас имеется:
Да, это заняло какое-то время. Да, не все получалось сразу. Но оно стоило того. Я думаю, ты и сам это заметил. Прежде чам мы приступим к следующему этапу, я хотел бы услышать твою ответную реакцию: как в целом оцениваешь опыт, что считаешь приобретением для себя, в чем были наибольшие трудности, какие есть пожелания, и т. д. После этого продолжим, а пока - поздравляю! ![]() ![]() |
Автор: diablero 23.6.2007, 07:58 |
Чувствуется нехватка опыта проетирование и необходимость структирировать, изучить и углубить теоритические знание. Я и раньше писал нечто подобное, теперь осознаю, что это было кривавато. Жду заданий. |
Автор: Stampede 23.6.2007, 23:46 |
Итак, следующий этап. Нам предстоит создать программную модель предметной области. Но не сразу. Сначала я хотел бы, чтобы ты изложил, как можно более подробно, что у тебя будет за сайт, какие в нем будут особенности, и какой в нем предполагается функционал. Если хочешь, можкшь привести список бизнес-сущностей, но сильно углубляться в детали пока не стоит. Поехали. |
Автор: diablero 24.6.2007, 02:10 |
Это должен быть тематический сайт-форум. Авторизированные пользователи, отправляют запрос на размещение своей статьи, по определенной тематике, после проверки, она размещается на сайте. Что-то вроде персональных страниц, только загнанные в стальные рамки тематикой сайта. Каждый читатель сайта может проголосовать за статью, поставить оценку, на основании которых выставляется рейтинг. И в зависимости от количества статей и их рейтинга пользователь присуждается группа. Каждый зарегистрированный пользователь имеет учетную запись - профиль пользователя (персональные данные, подписи, аватар, настройки), размещенные статьи, личный ящик, статистика. В связи с этим должны быть, на мой взгляд, следующее:
|
Автор: Stampede 24.6.2007, 15:19 |
В общем примерно понятно. Попробуй теперь, исходя из своего видения будущего вебсайта, набросать перечень бизнес-сущностей. Для каждой сущности укажи набор значимых свойств. Да, подсистему статистики пока не трогай, это вешь достаточно перпендикулярная всему остальному, и при этом весьма трудоемкая. Придет время - сделаем отдельно. |
Автор: diablero 24.6.2007, 17:23 | ||
А простым языком скажи, что от меня надо? ![]() |
Автор: Stampede 24.6.2007, 17:30 |
Я думал все и так прозрачно ![]()
... |
Автор: diablero 24.6.2007, 17:34 |
Понял, как с работы приду, постараюсь все это написать |
Автор: diablero 24.6.2007, 23:17 |
User:
|
Автор: v2v 25.6.2007, 10:01 |
Stampede, какая чудесная инициатива. Я бы тоже с удовольствием поучаствовал в таком проектике, но за не хваткой времени не смогу вовремя присылать ответы на задания. Поэтому, Stampede , не мог ли ты направить меня на путь истинный, если основной моей целью является знакомство и изучение JEE. Что читать? Какие технологии изучать? |
Автор: Stampede 25.6.2007, 16:16 | ||
v2v, я не так давно отвечал практически на такой вопрос fixxer'у:
To diablero: Отлично, давай теперь запишем это в виде Java классов. Начнем с сущностей User и Post. В каждом классе должен быть дефолтный конструктор, приватные переменные и публичные геттеры/сеттеры. |
Автор: v2v 25.6.2007, 17:54 |
Stampede, я видел этот ответ и смотрел туториал - там много, а читать всё подряд потребует очень не мало вреемени. (( Может быть вы как то обобщите процесс обучения. Тут вы выбрали конкретного человека и пытаетесь с ним реализовать его проект. Это конечно хорошо, и уверен этот человек очень классно разберётся. Но было бы не плохо если бы вы не внедрялись в потробности анализа кода и исправления ошибок, а создали шаблон, по которому можно было бы всем пробовать свои возможности. например : 1. проектриврание системы. Определить чего хотите ... если выберет то , то надо будет делать то , а если то, то то и т.д.... 2. выбор средства разработки .. есть такие то... такие то могут то то, такие то я рекомендую.. 3. реализация ... юзайте те то технологии создайте такие то основные файлы без которых жить не сможете, распихать по таким то пакетам.. 4. проектирование бд, где будет то то и то ... 5. и т.д.... |
Автор: Stampede 25.6.2007, 18:25 |
v2v, я не позиционирую себя как опытного JEE разработчика в том понимании, которое вы вкладываете в это слово. Поэтому ваши вопросы - это не ко мне. Попробуйте создать отдельную тему, или посмотрите вот этот только что открытый топик: http://forum.vingrad.ru/forum/topic-160671/kw-%D1%81%D1%82%D0%B0%D0%B6%D0%B5%D1%80-2ee-%D0%B7%D0%BD%D0%B0%D0%BD%D0%B8%D1%8F-%D0%BA%D0%BE%D1%80%D0%BF%D0%BE%D1%80%D0%B0%D1%82%D0%B8%D0%B2%D0%BD%D0%BE%D0%B5.html. |
Автор: diablero 26.6.2007, 18:26 | ||||
User
Post
Добавлено через 1 минуту и 43 секунды Stampede мы будем с локализацией заморачиваться? |
Автор: Stampede 26.6.2007, 19:11 | ||||
Так, препарируем. Замечания такие: 1. Зачем хранить пароль в виде байтов? Гораздо удобнее в виде строки - hex или base64. 2. Аватар собираешься прямо в базе хранить, блобом? Подумай, удобно ли? В прниципе это возможно, но поддерживать будет трудно (вставлять/обновлять/просматривать). Я бы все-таки предложил хранить в файловой системе. Тогда поле avatars (кстати, почему во множественном числе) будет строкой, содержащей путь к файлу картинки. 3. По поводу пола. У тебя вот такая конструкция:
Есть ли в этом смысл? Практически единственное место, где у тебя будет создаваться объект типа User - это при обработке соответствующей веб-формы. Пол там будет скоее всего задаваться радиокнопкой. Ну и зачем нам связываться с преобразованием строки в число, а потом обратно, если мы можем везде работать со строкой? Я бы сделал так:
Проверками пока не заморачиваемся, потому что для валидации все равно нужен будет какой-то целостный подход. 4. В классе Post ссылка на автора поста идет по имени юзера. Правильнее было бы по ID. Это как бы азбука проектирования реляционных баз данных. В остальном вроде нормально. Да, вот еще. В классе Post я заметил у тебя переменную int section, а ты такую сущность не описывал. Так что отредактируй классы с учетом замечаний и приведи список свойств для Section. После этого займемся непосредственным созданием интересующих нас объектов. |
Автор: diablero 26.6.2007, 20:08 | ||||
section это тематические разделы сайта. Например, с++ java asm ... Как и если она нам понадобиться мы ее опишем или уберем.
|
Автор: batigoal 26.6.2007, 20:45 | ||
А почему не enum? Вроде у нас Джава не четвертая... |
Автор: Stampede 26.6.2007, 23:00 |
Ага, годидзе. Теперь очень важный момент. Нам нужно будет насоздавать экземпляров определенных нами сущностей. В этом месте многие начинающие программисты сразу приступают к программированию датабазного слоя, с тем чтобы далее юзать данные из набитой вручную базы данных. Это не есть хорошо. При таком подходе вектор зависимости компонентов проги неизбежно отклоняется в сторону датабазной логики. При еще менее удачной архитектуре, когда начинают плясать от вебного слоя, этот вектор тяготеет также в сторону Servlet API, что в сочетании дает совершенно невообразимую мешанину, в которой практически невозможно разобраться. Поэтому крайне важно смотреть на базу данных лишь как на один из возможных способов персистенции, и соответственно экранировать реализацию низкоуровневого доступа к базе неким более общим интерфейсом, который знать ничего не знает о таких вещах как Statement, ResultSet и SQLException. Но вернемся к исходному вопросу. Как же все-таки создавать объекты для тестирования? Выход состоит в использовании т. н. mock objects - болванок, имитирующих реальные сущности (mock == имитация). Для их генерации существует несколько подходов:
To batigoal: Ну, для поля gender с двумя возможными значениями это был бы все-таки overkill. Тем более что ведение enum не решало бы вопрос о преобразовании из строкового значения в enum'ное. |
Автор: diablero 26.6.2007, 23:24 |
Не знал как это называется, но всегда для тестов использовал сгенириный файлик.![]() Так что я склоняюсь к файлу описателю. Т.е. моя задача в какую-нибудь структуру нагенерить кучу User и Post. |
Автор: Stampede 27.6.2007, 00:08 | ||||
Типа того. Тут можно еще сделать финт ушами и реализовать это дело малой кровью. Фокус заключается в том, чтобы задействовать один из имеющихся средств сериализации Java объектов в какой-нибудь текстовый формат. Для этого подойдет например класс http://java.sun.com/j2se/1.5.0/docs/api/java/beans/XMLEncoder.html, или библиотека Burlap (вот тут рассказывается, как в две строчки за- и рассериализовать объект: http://www.caucho.com/resin-3.0/protocols/burlap.xtp), или XStream (вот пример сериализации в строку: http://xstream.codehaus.org/tutorial.html), или дюбой другой (погугли по java xml serialization). Я бы от себя порекомендовал XStream - очень простой, четкий и внятный. Сделать надо вот что. Пишешь новый тестовый класс, скажем TestUser, и в нем (можно прямо в main()) создаешь экземпляр User, заполняешь поля значениями, и печатаешь его XML представление. Да, предварительно засовываешь объект в какую-нибудь коллекцию, чтобы потом загружать сразу пачкой. Примерно вот так:
После этого копируешь вывод в буфер, заносишь в файл, скажем, conf/users.xml, и, творчески применяя излюбленный метод китайских программистов под названием copy-paste, создаешь столько юзеров, сколько тебе надо. Фсе! Телемаркет! ![]() Как потом прочитать - объяснять, я думаю, не надо. |
Автор: diablero 27.6.2007, 01:00 | ||||
Я сделал проще. Взял сворй старый генератор csv файла и создаю сколь угодно User вида:
Post аналогично. И не в две, а чуть больше строчек, с помощью StringTokenizer создаю n-е количество User и Post
|
Автор: Stampede 28.6.2007, 01:15 | ||||
Рад, что тебе понравился XStream. Поехали дальше. Давай теперь напишем два класса-менеджера, которые реализуют простенький интерфейс:
Менеджерами будет заведовать класс Infinite: создавать, инициализировать и выдавать через геттеры. В самих менеджерах надо будет при инициализации загрузить из файла моки соответственно юзеров и постов, и сохранить их в мапе по ID. Выдавать методами getUser(int id) и getPost(int id). В классе UserManager дополнительно определи методы
Попробуй все это потестить отдельным тестовым классом.
Не совсекм понял вопрос. А вообще, чтение конфига - это не та задача, где надо выжимать максимум быстродействия. |
Автор: diablero 28.6.2007, 12:37 | ||||
UserManager
PostManager
Потестил, работает. |
Автор: Stampede 28.6.2007, 17:48 |
Отлично! Теперь проведем вторую жЫрную черту. Выкладывай zip-архив всего, что наваял. Не забудь про сторонние библиотеки и файлы для моков. После этого приступим к обсуждению персистенции. ЗЫ. Я бы все-таки поменял Vector на ArrayList. Vector и Hashtable - это тяжелаое наследие проклятого царизма, когда еще не было фреймворка Collections, а коллекции на всякий случай делались полностью потокобезопасными (естественно, в ущерб производительности). |
Автор: diablero 28.6.2007, 19:41 |
Сменить vector на arraylist я захотел после того как посмотрел соотношение производительности между ними. А какой струтурой заменить hashmap? Архив с библиотеками запостить не получиться. Лимит всего 120кб, а со всеми библиотеками архив будет пол мегабайта. Как сменюсь с дежурства все сделаю. |
Автор: Stampede 28.6.2007, 20:13 | ||
Хм, жалко... Модераторы, есть ли какая-нибудь возможность разместить полный архив? Может, дать доступ к FTP или еще-как-то. Спасибо. Читай внимательнее. HashMap как раз в полном порядке. И еще. Я сегодня уезжаю на пять дней. Доступа в инет скорее всего не будет. Так что если не хочешь, чтобы работа на это время застопорилась, давай попытаемся успеть обсудить персистенцию. Основные кандидаты - голый JDBC, Hibernate, iBatis, Castor. Выбирай с учетом предыдущего опыта и/или желания освоить какую-то технологию. ЗЫ. Я сам с ORM не работал, так что если что - придется кого-то просить в помощники. Или разбираться вместе. |
Автор: diablero 28.6.2007, 20:39 |
Из перечисленного я имел дело только с jdbc. Заметил что часто требуется работадателями и много народ обсуждает hibernate. Давай будем работать с ним. Или есть аргументы в другую сторону? |
Автор: nerezus 28.6.2007, 20:43 | ||
попробовал однажды - теперь отвращение к другим способам. |
Автор: batigoal 28.6.2007, 21:09 |
Есть ведь еще Java Persistence API. Я с ним сейчас пытаюсь заморачиваться. Как я понимаю, он во многом базируется (или тупо повторяет) Hibernate. |
Автор: Stampede 28.6.2007, 22:22 |
Говоришь, Hibernate? Ну что ж, хороший выбор. Попробуй сначала разобраться самостоятельно. Для двух простых таблиц, думаю, сделать самому вполне реально. Ну а если что - проси помощи клуба. В принципе тут грамотных людей хватает. Особенно хорошо будет, если tux согласится помочь. Но сначала выложи зип. Добавлено через 2 минуты и 3 секунды Я его тоже посмотрел. Но для него, как я понял, нужен полновесный JEE сервер. А мы все-таки делаем обычный вебсайт. |
Автор: w1nd 28.6.2007, 22:28 | ||
Не обязательно, возможны (и вроде есть) легковесные реализации. |
Автор: Stampede 28.6.2007, 23:32 |
У JPA (Java Persistence API) есть все предпосылки стать той технологией работы с данными, которая вот уже много лет напрашивалась к реализации и которую так долго ждали разработчики. Думаю, уже в самом скором времени производители ORM-средств адаптируют свои продукты таким образом, чтобы они соответствовали JPA. А когда вслед за этим JPA включат в состав Java SE, на всей планете наступит большое щястье. Но до этого светлого времени еще нужно дожить. Поэтому давайте пока все-таки придерживаться проверенных технологий. |
Автор: fixxer 29.6.2007, 10:20 |
Хочу заметить, что JPA вполне можно использовать в Java SE. http://hibernate.org/397.html Для этого необходимы пакеты: http://sourceforge.net/project/showfiles.php?group_id=40712&package_id=127784&release_id=509407, он скорее всего уже есть http://sourceforge.net/project/showfiles.php?group_id=40712&package_id=139933 http://sourceforge.net/project/showfiles.php?group_id=40712&package_id=156160 Также, если нужно, могу запостить книжку Java Persistence with Hibernate |
Автор: diablero 29.6.2007, 20:10 |
Подвожу вторую жЫрную черту |
Автор: Entry_N3 30.6.2007, 11:57 | ||
JSR 220 Java Persistence Architecture – в Java SE 7 планируется включить разработанный группой экспертов по EJB persistence API, значительно облегчающий работу с реляционными БД из объектно-ориентированного кода. |
Автор: tux 1.7.2007, 15:27 |
tux согласится. ![]() |
Автор: powerOn 1.7.2007, 18:40 | ||
Не нужен никакой контейнер. Как правильно заметил fixxer, JPA можно легко использовать в stand-alone программах. JPA - это спецификация описывающая единый интерфейс для ORM фрейвёрков. JPA описывает интерфейсы, которые должен реализовать ORM фрейвёрк, что бы его можно было использовать в качестве провайдера данных. Т.е. если раньше цепочка доступа к данным шла как: Business Logic -- ORM Framework -- Database. то теперь она будет выглядеть так: Business Logic -- JPA -- ORM Framework -- Database. т.е. Business Logic для доступа к данным должна знать только интерфейс JPA, а не конкретного ORM Framework-а. Что позволяет этот самый ORM Framework легко сменить на другой, не трогая уровень бизнес логики. В частном случае могут быть такие цепочки: Business Logic -- JPA -- Hibernate -- Database. Business Logic -- JPA -- TopLink -- Database. Вот эти библиотеки как раз и реализуют интерфейсы JPA для Hibernate, что позволяет подключить Hibernate в качестве провайдера к JPA. TopLink так же имеет аналогичные реализации. Так что, ИМХО, JPA - это будущее. (И фиг его почему его ассоциируют с EJB3.0... наверное из-за аннотаций) |
Автор: diablero 1.7.2007, 23:05 | ||||
Тогда вопрос ![]() Не видет файл user.hbm.xml.
Хоть так.
Причем если у Configuration вызывать configure передавая в качестве параметра путь к hibernate.cfg.xml а не файл, то тоже получаем file not found |
Автор: tux 2.7.2007, 05:30 |
Давай разбираться как конфигурируется Hibernate. Всю свою конфигурацию он может читать либо из файловой системы либо из ресурсов. С файловой системой, я думаю, все понятно, а вот про ресурсы тебе наверное нужно почитать. Грубо говоря, поиск ресурсов выполняется так же, как и поиск классов. То есть, если ты пытаешься прочитать ресурс "/conf/user.hbm.xml", то это означает, что у тебя в CLASSPATH должен быть каталог conf, в котором лежит файл user.hbm.xml. Если посмотреть в javadoc от Hibernate, то там видно, что метод addResource добавляет файл именно из ресурсов. Простейший способ решить проблему - положить файл /conf/user.hbm.xml туда, где у тебя лежат скомпилированные классы (либо если используешь, например, Eclipse, то вместе с исходниками, он сам все сделает). Если все же все-таки хочется грузить конфигурацию из файловой системы, то используй метод addFile() вместо addResource(). Еще раз замечу, что при использовании последнего путь нужно задавать относительно корня CLASSPATH. С использованием методов configure(String resource) и configure(File configFile) та же самая ситуация - в первом случае конфигурация грузится из ресурса, во втором - из файла, потому и такой несовпадающий результат. Еще пара замечаний. Использование метода addResource() и наличие в hibernate.cfg.xml тэга mapping дублируют друг друга, поэтому лучше оставить что-то одно. Вместо метода getCurrentSession() лучше использовать openSession(). Первый метод предназначен для использования в серверах приложений и используется JTA для управления транзакциями. Обычно это нужно при работе нескольких приложений с одним источником данных. Метод openSession() явно открывает сессию, а не пытается получить уже существующую. Хотя в текущих версиях Hibernate в методе getCurrentSession() явно вызывается openSession() если не удалось сконфигурироавть JTA (в версиях 3.0 и ниже он просто не работал бы), лучше явно сказать, что мы открываем сессию. |
Автор: diablero 2.7.2007, 12:42 | ||
Какая-то не понятная ошибка закралась. Никак не могу User'а добавить в базу.
[code=xml] <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="ru.selfexpression"> <class name="User" table="user"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name" type="java.lang.String"> <column name="name" not-null="true" sql-type="VARCHAR" /> </property> <property name="password" type="java.lang.String"> <column name="password" not-null="true" sql-type="VARCHAR" /> </property> <property name="group" type="java.lang.String"> <column name="group" not-null="true" sql-type="VARCHAR" /> </property> <property name="avatar" type="java.lang.String"> <column name="avatar" not-null="false" sql-type="VARCHAR" /> </property> <property name="birthday" type="java.util.Date"> <column name="birthday" not-null="false" sql-type="DATE" /> </property> <property name="gender" type="java.lang.String"> <column name="gender" not-null="false" sql-type="VARCHAR" /> </property> <property name="city" type="java.lang.String"> <column name="city" not-null="false" sql-type="VARCHAR" /> </property> <property name="country" type="java.lan |
Автор: tux 3.7.2007, 04:35 | ||||
Сразу замечание по базе данных. В именах полей следует избегать зарезервированных слов. В частности, 'group' используется в конструкциях group by. Похоже ошибка связана с этим. Во-первых, в hibernate.cfg.xml нужно указать диалект SQL, с которым предполагается работать. Делается это добавлением еще одной проперти в hibernate.cfg.xml:
Если это не поможет, поменяй в базе данных имена полей 'password' и 'group' на что-то другое. Однако, исправив эту ошибку, вероятно получим следующую. Вот посмотри на запрос, который сгенерировал Hibernate:
В поле id вставляется значение null, хотя это видимо первичный ключ и таких значений иметь не может. У тебя используется генератор native, который зависит от используемого диалекта. Явное указание диалекта позволит Hibernate определить что именно использовать. Не помню что используется в MySQL, если это будет тип sequence, то для его использования нужно будет предпринять специальные действия. Если Hibernate будет выдавать ошибки, пока можно будет поменять генератор на простейший - increment. |
Автор: diablero 3.7.2007, 22:58 |
Изменил имена полей в базе данных и генератор на increment. Все заработало. |
Автор: Stampede 3.7.2007, 23:54 | ||||||
Всем привет, я вернулся! Очень рад, что дело не стояло на месте. Теперь вот какой важный момент.
Это исключительно хорошая новость! Очень жаль, что в документации по JPA этот момент не акцентруется. Я ведь говорю: я заходил, читал материалы, в том числе http://java.sun.com/javaee/overview/faq/persistence.jsp. Везде разговор о JPA ведется в контексте EJB и EE. А вот тут так и вообще открытым текстом:
Дальше читать резко расхотелось. И только сейчас, уже зная правильный ответ, прочел более внимательно, и нашел упоминания о возможности использования JPA в standalone приложениях:
Так вот, в свете этой новости ситуация меняется самым радикальным образом. Тем более что у Hibernate уже имеется адаптер для JPA (fixxer ловит плюса за отличную новость). Категорически предлагаю diablero переделать персистенцию в вендорно-независимом виде, то есть через JPA и с аннотациями. Пока еще не слишком поздно. ЗЫ. Заодно вопрос: а ты точно уверен, что хочешь мускуль? Есть для такого выбора какие-то веские основания? |
Автор: y3u 4.7.2007, 06:18 | ||||
"вам хочется песен? их есть у меня!"... покажите рабочий примерчик с JPA в стендэлоне ![]() ![]() |
Автор: powerOn 4.7.2007, 07:21 | ||||||||
Вы хотели пати? нати ![]() Что потребуется: Библиотеки для Hibernate Core, Hibernate Entity Manager (они есть на сайте hibernate), JDBC драйвер для MySQL. В нашем простом проекте будет всего 3 файла: 1) persistence.xml - файл конфигурации JPA. 2) jpatest.MyEntity - простая сущность. будем её сохранять в БД. 3) jpatest.Main - это класс содержит логику сохранения. persistence.xml Здесь описываем persistence-unit - параметры соединения с БД, диалекты, транзакции, подключаем Entity классы и прочее. Данный файл лежит в каталоге META-INF. Видимо по умолчанию так принято.
jpatest.MyEntity Это класс-сущность. Замапим его на таблицу MyTable используя аннотацию @Table(name="MyTable"). Имеется у данного класа 2 поля - id (главный ключ с автогенерацией) и someData. Опишем их свойства так же с помощью аннотаций.
jpatest.Main Данный код сохраняет объект типа MyEntity в БД. Сначало создаем фабрику для EntityManager-а используя описание нашего persistence-unit-а. Это можно сделать по имени. Далее создаем сам EntityManager и используем его для сохранения объекта.
Не трудно заметить, что работа с JPA весьма похожа на работу с Hibernate. |
Автор: batigoal 4.7.2007, 07:46 |
Stampede, diablero и остальные: не будете возражать, если дискуссия о выборе персистенса перекочует в отдельный топик? |
Автор: diablero 4.7.2007, 17:06 | ||||||
Категорически согласен.
Основание только одно, это единственная имеющаяся у меня база данных. Если есть какие-то основания перейти на что-то другое, то скажи на что. Stampede давай определим план действий. Что мне сейчас в итоге нужно сделать и как это оформим. Я имею ввиду структуру классов для работы с базой данных. Будем ли реарганизовывать менеджеры или их пока оставим для генерирования исходных данных. Добавлено через 3 минуты и 17 секунд
Конечно нет |
Автор: Stampede 4.7.2007, 19:42 | ||||||||
Я бы все-таки оставил здесь - для удобства всех заинтересованных, кто будет читать эту ветку в будущем. Как ни крути, выбор способа персистенции - это немаловажный момент в разработке сайта, так что высказанные здесь соображения должны помочь людям сориентироваться. Теперь о реализации. powerOn, спасибо за пример.
Одно из ключевых преимуществ ORM нового поколения (и JPA в том числе) заключается в том, что они позволяют проектировать DAO как обычные классы, или POJO (Plain Old Java Objects), без необходимости выводить их родословную из каких-то специфических классов или интерфейсов. Поэтому User и Post остаются у нас как были, только добавятся необходимые аннотации. Экземпляр EntityManager у нас будет один на все приложение. В методе Infinite.init() нужно предусмотреть его инициализацию, и потом выдавать всем желающим через геттер. Преимущество от использования единственного менеджера энтитей в том, что тогда он сможет отслеживать и разруливать обновления объектов простым и ненакладным образом. Ну и понятно, не надо будет для каждой транзакции заставлять фабрику заниматься инициализацией нового экземпляра менеджера. В UserManager и PostManager убираем работу с моками, при инициализации получаем (от Infinite) и сохраняем локально экземпляр EntityManager. Методы getPost(int id), getUser(int id), getUsers() и login(String name, String password) переписываем для работы с базой. Все действия по созданию/управлению транзакциями кодируем вне менеджеров. Пока что будем делать это прямо в тесте:
Все, пока этого достаточно.
Бесплатных СУБД много, так что на мускуле свет клином не сошелся. В принципе в последних версиях он уже вроде стал пригодным для работы, но раньше, когда в нем не было поддержки транзакций, вложенных подзапросов и много чего еще, его вообще нельзя было считать за нормальную базу. В общем, советую поспрошать в подфоруме СУБД, послушать разные мнения. От себя порекомендовал бы Postgres - вполне взрослая база, в меру быстрая и надежная, есть на большинстве Java хостингов. Но дело, конечно, хозяйское. |
Автор: diablero 4.7.2007, 22:18 |
to fixxer Ты говорил, что можешь запостить книжку Java Persistence with Hibernate. Нужно, если есть возможность. У меня пока проблемы с пониманием сути и важности Hibernate JPA. Вроде как оно долно упростить нам архитектуру приложения и избавить от множества настроек. А сама работа с базой данных аналогична. Т.е. все настраивается в файле persistence.xml. В связи с этим у меня сразу вопрос, с чем я столкнулся, это как задать путь к файлу? |
Автор: powerOn 4.7.2007, 22:39 | ||
К какому? К persistence.xml? если про него речь, то просто положи его в папку META-INF, которая будет в корне исходников. |
Автор: Stampede 4.7.2007, 22:51 | ||||
Нет, не столько упростить, сколько избавить от привязки к конкретному продукту. Понимаешь, когда ты работаешь с Hibernate напрямую, у тебя в проекте появляется множество зависимостей: формат конфига, названия аннотаций, имена классов фабрик и менеджеров, и т. д. В принципе ничего особенно страшного, но вот на другую ORM так запросто уже не перейдешь. И дело тут не в том, что ты будешь десять раз переводить прогу с Hibernate на TopLink, а с TopLink на iBatis, а в том, что при работе через единый API ты нарабатываешь базу для переиспользования в других проектах и приобретаешь "конвертируемый" опыт. И можешь быть уверен, в самом скором будущем работодатели вместо Hibernate будут требовать от кандидатов знание JPA. Потому что так будет намного проще и удобнее для всех.
Уточни, о каком файле идет речь. persistence.xml? Не знаю, почитай доку. Должен быть какой-то способ. Может, переменную окружения можно как-то задействовать. |
Автор: diablero 4.7.2007, 23:21 | ||||
спасибо, так работает. А по другому ни как? У меня вот такое исключение валиться, не знаю почему. Сделал все как в примере у powerOn
|
Автор: powerOn 4.7.2007, 23:25 |
скорее всего ты забыл у класса сущности указать аннотацию @javax.persistence.Entity. Выложи пожалуйста этот класс сюда. |
Автор: diablero 4.7.2007, 23:38 | ||||
|
Автор: Stampede 4.7.2007, 23:40 | ||
Вот еще что вычитал:
Давай действительно так и сделаем, и conf добавим к classpath. Заодно можно будет убрать ручное задание пути к конфигу log4j - сам будет находиться. Добавлено через 7 минут и 50 секунд Дак ты же в объявлени классса не указал implements Serializable! |
Автор: diablero 5.7.2007, 00:00 | ||
Увидел когда постил ![]() Exception только изменился
|
Автор: diablero 5.7.2007, 00:18 | ||
Проблемку решил. Заменив метод persist на merge По базе. Я сейчас сижу через GPRS. Попрошу, мне скачают Postgres. Только это будет не скоро. Если не трудно дай прямой линк на закачку базы и драйвера к ней. Вечером приступлю к
Долг зовет, а еще не спал ![]() |
Автор: Stampede 5.7.2007, 00:24 | ||
А покажи код, чего и как делаешь. Есть подозрение, что нужно правильно объявить scope контекста персистенции. Попробуй такое объявление фабрики:
И вообще, почитай вот это вот: https://blueprints.dev.java.net/bpcatalog/ee5/persistence/webonlyapp.html. |
Автор: fixxer 5.7.2007, 09:31 | ||
http://rapidshare.com/files/41105083/Java_Persistence_with_Hibernate.rar.html По поводу выбора БД, могу еще предложить FireBird. Простая, бесплатная база данный. Имеет, в отличие от мускуля, мощный диалект SQL, причем довольно чистый, избавленный от болезней роста многих "больших" баз. Практически полностью соответствует стандарту SQL-92 (не уверен по поводу SQL-99) Довольно шустрая и может работать с большими объемами. http://ru.wikipedia.org/wiki/Firebird http://www.firebirdsql.org/ |
Автор: diablero 5.7.2007, 20:37 | ||||||
Такое объявление ничиго не изменило |
Автор: diablero 5.7.2007, 21:25 |
Что-то с запросами у меня вообще загвостка. Погуглил и только больше запутался. Если бы кто-нибудь объяснил по русски, доступно. Было бы здорово |
Автор: Stampede 5.7.2007, 22:07 |
fixxer, спасибо за отличную книжку! Плюс без разговоров ![]() Сейчас читаю 9-ю главу (там же, кстати, освещается работа через JPA) - это как раз про контекст персистенции и состояния жизненного цикла персистируемых объектов. Там в числе прочего говорится, что изначально энтити, созданные по new (или как в нашем случае сгенерированные XStream), неизвестны механизму персистенции. Операция merge() как раз и вводит энтитю в соответствующий контекст. Так что я так понимаю, для сохранения объекта в базе после merge() еще нужно выполнить persist(). У тебя в базу что-нибудь попадает? А что именно не выходит с запросами? Выкладывай, будем разбираться. А то сам понимаешь: лето, пора отпусков - все ясновидящие на курортах ![]() |
Автор: diablero 5.7.2007, 22:20 |
Да, все работает. Загнал в базу всех User и Post. Я сейчас хотел потренироваться с запросами. Повыбирать User и Post. И пока не додумал, как получить всех User или Post. Времени уже нет разбираться, я сегодня уеду по работе до воскресенья, в лутшем случае. Доступ в интернет будет, правда только к форуму, через телефон. завтра я ее скачаю, буду разбираться |
Автор: fixxer 6.7.2007, 09:29 | ||
Подозреваю, что для введения нового объекта достаточно persist(). А merge() это для введения detached объекта (вышедшего из контекста сессии) в контекст другой сессии. Но не уверен, пусть знающие поправят. |
Автор: diablero 8.7.2007, 06:35 | ||
Мне нужна помощь, потому что я уже зарылся, и клавиатуре остался один удар до кончины![]() Если простые запросы работают, как:
А вот с запросами с параметрами проблема. |
Автор: diablero 8.7.2007, 17:28 |
Все, разобрался. ![]() |
Автор: diablero 8.7.2007, 20:12 | ||||
Готово.
|
Автор: diablero 10.7.2007, 09:11 | ||||
И Post.
Что делаем дальше? |
Автор: Stampede 10.7.2007, 21:22 | ||
Все-таки маленько не так. Помнишь, я писал:
Это важно. Понимаешь, так, как сделал сейчас ты, получается, что каждый метод менеджера выполняется в отдельной транзакции. Как же мы тогда обеспечим целостность всей совокупности обращений, например, в рамках обработки одного запроса? И еще пара замечаний.
Вообще персистенцию следовало бы хорошенько потестить на предмет разных возможных ситуаций, включая сбойные. Например, сделать вставку, а потом в этой же транзакции выполнить некорректную операцию, чтобы выскочило исключение. Посмотреть, что получится. Так можно заранее выявить и предотвратить многие неприятные сюрпризы. Это намного проще, чем потом пытаться воссоздать сбойный сценарий, тыкая в вебный интерфейс. В общем, рано пока двигаться дальше. [оффтоп]Товарищи модеры, вы не в курсе, куда делся мой аватар?[/оффтоп] |
Автор: Maksym 11.7.2007, 12:44 |
сбой базы, http://forum.vingrad.ru/forum/topic-162789/0.html |
Автор: diablero 11.7.2007, 18:35 | ||||
Поэтому и передаю экземпляр entityManager через параметр метода init(). ОК, персистенцию потестил, прямо в Test'е. Всю работу с базой в менеджерах убрал.
Какую организацию мы будем использовать для решения этой задачи? |
Автор: Stampede 11.7.2007, 21:33 | ||||||||||
У нас же есть специальный метод, Infinite.getEntityManager() - вот им и нужно пользоваться. Тут идея вот в чем. Мы неспроста завели интерфейс Manager. Это такая удобная конструкция, про которую известно, что ее можно создать, инициализировать, и в конце работы прихлопнуть. А ты введением в сигнатуру метода Manager.init() параметра EntityManager ломаешь эту конструкцию. Во-первых, JPA - это всего лишь одна из технологий персистенции, хоть и весьма перспективная. Но это конкретная технология и конкретный API, а ты ее зашиваешь в такую абстрактную вещь как Manager. Во-вторых, не все менеджеры обязательно будуть работать с базой. Например, почтовый менеджер, который нам рано или поздно понадобится, может вообще ничего не знать про какой-то там EntityManager. В общем, надеюсь, это достаточно понятно.
Там не работу с базой надо убрать, а работу с транзакциями! Чтобы в менеджере осталось только вот это:
Ты это и имел в виду?
Как я уже говорил, в тестах - прямо в коде теста, а когда будем делать вебный слой, то в коде обработки запроса. Чтобы в каждом тесте не дублировать один и тот же код работы с транзакциями, можно воспользоваться преимуществами объектного подхода и написать базовый класс для тестирования датабазного слоя. Что-нибудь типа такого:
Туда же, кстати, можно перенести и метод main() и все действия по инициализацию проги. В результате написание тестов сведется всего-навсего к определению метода doTest(), без лишней мутоты. Собственно, в JUnit примерно так и организовано: ты наследуюшь от TestCase, и все методы, что имеют имя вида testXXX, выполняются за тебя автоматически. Короче, переделай с учетом замечаний и покажи. Возможно, на этом и проведем третью жЫрную черту. После этого сразу приступим к долгожданному вебу ![]() ЗЫ. 2 Maksym: ага, понятно, спасибо. |
Автор: diablero 11.7.2007, 22:22 | ||||||
Да, я убрал работу с транзакциями
Это понятно, но чтобы проинициализировать в менеджере entityManager, нам нужно либо передать ссылку на объект через конструктор, какой-то метод, либо унаследовать от Infinite, либо сделать статиком entityManager и геттер. Ведь так? |
Автор: Stampede 11.7.2007, 22:36 | ||
Дак а мы же специально для этого сделали Infinite синглтоном - именно для простоты лукапа. Чтобы в любом место проги написал Infinite.getInstance() - и фсе, доступайся к чему хочешь. Более того, я ранее отмечал, что использовать синглтон чисто ради лукапа - это не совсем хорошая идея. Есть там кое-какие подводные грабли. Но для нас пока сойдет. А где-то ближе к концу проекта отдельно поговорим про грабли и альтернативные решения. Как все доделаешь, выкладывай зип. Тесты только перепиши в ООП-ним виде, по типу как я показывал. |
Автор: diablero 11.7.2007, 23:33 |
Готово. Я вот только так и не понял фишку с логером, геттера мы для него не делаем. Он у нас объявлен как приватный. Сделать его пабликом? |
Автор: Stampede 11.7.2007, 23:40 | ||||
Нет, просто в каждом классе, где понадобится логгер, прописываем свой собственный:
Сырцы щас посмотрю. Да, вот еще что. Я бы на твоем месте пошел и зерегил домен selfexpression.ru. От киберсквоттеров гадости можно ждать в любой момент. |
Автор: diablero 11.7.2007, 23:52 |
Подправил, архив изменил |
Автор: diablero 13.7.2007, 18:33 |
Что делаем дальше? |
Автор: Stampede 13.7.2007, 18:35 |
Хорошо бы еще скрипт создания базы выложить. И вообще, положи его в проект, пусть и в зипах тоже будет. Итак, вебный интерфейс. Расскажи как можно более подробно, что будет на сайте и какие ты предполагаешь для всего этого УРЛы. |
Автор: diablero 14.7.2007, 02:40 |
Это же обсуждалось, или подробно, это до структуры страниц? Я эту часть предложения не понял |
Автор: Stampede 14.7.2007, 03:08 |
Знач смотри. У тебя будет несколько типов страниц: главная, раздел, статья, личная страница участнега, и т. д. Надо, чтобы у тебя было представление, что будет на каждой странице (это сейчас не важно), и по какому шаблону у них будет задаваться УРЛ. ЗЫ. А ты чего там полуночничаешь? ![]() |
Автор: diablero 14.7.2007, 03:21 | ||
Добавил скрипт создания базы и положил в папку lib, readme файл, со списком необходимых библиотек. Такая у меня дурная работа, ушел в четверг, пришел в субботу. И чтобы дело не стояло на месте, читаю форум перед сном ![]() |
Автор: Stampede 14.7.2007, 03:33 |
Во, вот это правильно - загружать подкорку перед сном. Пускай подсознание молотит, пока спишь ![]() |
Автор: diablero 14.7.2007, 03:53 |
Я сейчас структурно все опишу, завтра на свежую голову подкорректирую, и выложу вордовский файл. |
Автор: diablero 14.7.2007, 18:34 |
Формат страниц готов, для наглядности сделан в вордовском файле, на таблицах. Жду критики и предложений... УРЛ предлагаю формировать по полям id |
Автор: niasilil 16.7.2007, 06:30 | ||
Удивительно. А как же принцип "program to an interface"? Так как офттопик, то открыл новую http://forum.vingrad.ru/forum/topic-163606.html. |
Автор: Stampede 16.7.2007, 18:16 |
Значит, ЧПУ* не хочешь? * http://ru.wikipedia.org/wiki/%D0%A7%D0%9F%D0%A3_(%D0%98%D0%BD%D1%82%D0%B5%D1%80%D0%BD%D0%B5%D1%82) (Человеко-понятный УРЛ) - концепт, ввведенный в широкий обиход известным флеймером всея Рунета, автором сайтного движка Register http://nudnik.ru/. |
Автор: diablero 16.7.2007, 19:20 |
Можно и ЧПУ, я за то как нам удобней. На мой взгляд по id проще запросы будут. Хоть это и относительная простота, но все же. |
Автор: Stampede 16.7.2007, 19:39 |
Хорошо, договорились. Итак, предлагай УРЛы для следующих страниц: раздел, статья и профиль. Я со своей стороны предлагаю использовать имя в формате [имя.расширение], причем расширение - не типичное для сервлетных книжек .do, а такое, которое указывало бы на характер ссылаемого документа. Например, я считаю, что .shtml было бы вполне уместно - так традиционно помечают динамические странички, генерируемые с использованием технологии SSI (Server-Side Include). Но в принципе выбор твой. |
Автор: diablero 16.7.2007, 19:55 | ||
Я согласен. news.shtml section.shtml article.shtml account.shtml |
Автор: Stampede 16.7.2007, 20:26 | ||
Отлично. Итак, приступаем к вебу. Для начала нам нужно прикрутить к проекту шаблонный движок. Особой разницы между ними нет, так что возьмем хорошо всем знакомый Velocity, легкий и шустрый. Надо будет написать класс TemplateManager такого примерно содержания:
Как мы видим, в таком виде менеджер шаблонов получается у нас практически независимым от Velocity, то есть при желании его можно будет переделать под любую технологию рендеринга, включая XSLT и JSP. Опять же, можно запросто оформить это дело как интерфейс, а для Velocity написать реализацию VelocityTemplateManager. Но я напоминаю, мы не делаем библиотеку общего назначения, мы делаем просто сайт. Подчеркиваю это специально для niasilil, который, похоже, несколько излишне загрузился вопросом: niasilil, я там отписался в твоей теме. Шаблоны предлагаю хранить в отдельной папке {home_dir}/template. |
Автор: diablero 16.7.2007, 21:31 | ||
Я всеми руками за него. |
Автор: diablero 16.7.2007, 22:41 | ||||
А строку мы из Writer'а получаем? |
Автор: Stampede 16.7.2007, 23:13 |
Используй StringWriter. > private Logger logger = Logger.getLogger(Infinite.class); Вообще-то идея была в каждом классе заводить логгер, используя свой собственный класс (сорри за тавтологию). То есть передавать TemplateManager.class. Далее: обработка исключений. Везде, где встречаем библиотеко-зависимые чекнутые исключения - ловим, заворачиваем в рантаймное и пробрасываем дальше. А не глотаем, как у тебя. Хорошо было бы еще на содержимое velocity.properties посмотреть. Ну и тест какой-нить прогони и расскажи как получается. |
Автор: diablero 16.7.2007, 23:18 | ||
Пока так.
|
Автор: Stampede 16.7.2007, 23:47 |
> file.resource.loader.path = templates Тут есть одна тонкость. Это у тебя работает до тех пор, пока ты запускаешь тест из корня проекта (который у тебя совпадает с домашней директорией). При запуске из контейнера это сломается. К сожалени, Velocity не понимает переменные окружения. Один из выходов заключается в том, чтобы вручную загрузить проперти, получить значение file.resource.loader.path, подменить в нем переменную домашней директории (по шаблону) реальным значением, и засунуть обратно в проперти. Потом инициализировать движок методом VelocityEngine.init(Properties props). То есть в конфиге будет примерно такое: file.resource.loader.path = {home_dir}/templates Так как там с тестом? |
Автор: diablero 17.7.2007, 00:29 | ||||||
Готово.
test.shtml
|
Автор: Stampede 17.7.2007, 01:15 | ||
Ништяк! ![]() Теперь ты видишь, к чему мы клоним? Практически сайт у тебя уже готов. Ну, не совсем конечно... Нам щас нужно сделать одну важную вешь: запустить все это дело из-под контейнера. Для этого придется реорганизовать структуру директорий. Все, что у тебя в корне (кроме сырцов), нужно поместить в ROOT/WEB-INF. Убедись, что скомпилированные классы лежат в WEB-INF/classes, а либы - в WEB-INF/lib. Если нужно, настрой проект как веб-приложение. После этого пропиши в server.xml:
Можешь для удобства добавить в etc/hosts запись: 127.0.0.1 infinite А если еще сменишь порт томката с 8080 на стандартный 80, то вообще сможешь ходить по адресу http://infinite/ Изобрази какой-нибудь web.xml по аналогии с примерами, чтоб *.jsp мапилось на JspServlet. Положи в ROOT простенькую JSPшку и попробуй вызвать ее из браузера. Саму прогу пока не дергай. |
Автор: diablero 17.7.2007, 01:41 |
Все сделал. Запускаю и работает простой сервлет. P.S. ушел спать... |
Автор: Stampede 17.7.2007, 16:43 |
Так, продолжаем. Начнем потихоньку слздавать наш класс-контроллер, букву C в MVC (хотя, если кто помнит, термин CLMV представляется мне гораздо более адекватным). Пускай он наследует от HttpServlet и дополнительно реализует ServletContextListener. Можно, конечно, сделать это и в разных классах, но в одном будет компактнее. Update: Внимание! Как по ходу выяснилось, из-за того что листенер и сервлет имеют свой собственный жизненный цикл, объединить их в одном классе не получается. То есть делаем отдельно класс-листенер, отдельно класс-сервлет. По contextInitialized() надо поднять Infinite. Для этого надо знать путь к домашней директории. Получаем так: ServletContext.getRealPath("/WEB-INF"); В обработке запроса, метод service(), можно для начала проанализировать УРЛ запрашиваемого ресурса, и если он соответствует одному из (раздел, статья, аккаунт), то выдать содержимое соответствующего шаблона. Следующим шагом мы приведем в порядок логику обработки запроса, но сейчас нам просто нужно убедиться, что, грубо говоря, все, что мы делали ранее в тестах, включая работу с транзакциями, точно так же работает и из сервлета. Заодно ты увидишь, что задавая разные адреса, ты можешь получать разные странички. Да, маппинг к сервлету-контроллеру в web.xml, естественно, удобнее всего прописать по расширению .shtml |
Автор: diablero 17.7.2007, 21:21 | ||
При инициализации класса Infinite вываливается эксепшен. Никак home dir получить не могу, все время он null. В чем может быть ошибка?
|
Автор: diablero 19.7.2007, 19:06 | ||
Пролопатил пол инета. У многих проблема с getRealPath(). Пока есть только такое решение:
|
Автор: Stampede 19.7.2007, 20:34 | ||
Чет какие-то новости. Никогда про такое не слышал. Какой контейнер используешь и какой версии? Вариант получать корень тоже, конечно, прокатит, но хотелось бы разобраться. |
Автор: diablero 19.7.2007, 21:09 |
Tomcat 5.5 servletContextEvent равен null, может я чего-то не так делаю? Причем если реализовывать интерфейс Servlet, то тогда в методе public void init(ServletConfig servletConfig), можно получить домашнюю директорию. |
Автор: Stampede 19.7.2007, 21:58 |
Знаешь, это, по-видимому, из-за различия жизненных циклов у листенера и сервлета. Об этом я как-то не подумал. Попробуй сделать их как два разных класса. |
Автор: diablero 19.7.2007, 22:44 |
Так работает ![]() Infinite поднялся. Два дня напрягался, а оказалось как обычно. Все гениальное просто. ![]() |
Автор: Stampede 19.7.2007, 23:03 | ||
Мда, это я сам себя попытался перехитрить... Ведь никогда же так не делал раньше. В общем, век живи - век учись... (дальше не надо ![]() Ну хорошо, давай теперь сервлет сделай. |
Автор: diablero 20.7.2007, 16:02 |
Тут у меня загвоздка, я чего-то не могу сообразить как программно это реализовать. В теории вроде все понятно. Точка входа у нас будет одна на все приложение, которая будет выступать в роли диспетчера. И набор классов, которые будут обрабатывать запросы. |
Автор: Stampede 20.7.2007, 17:47 |
А что именно непонятно? Ну скопируй для начала код из последнего теста (с Velocity) внутрь service(), а в конце выведи получившийся текст через response.getWriter().write(). Убедимся, что все работает, и пойдем дальше. |
Автор: diablero 20.7.2007, 18:28 | ||
Так это я давно сделал. Все работает. Давай мой вопрос отложим, пойдем дальше. А там может из твоих указаний будет мне все понятно. |
Автор: Stampede 20.7.2007, 19:32 | ||||
Хорошо, двинем дальше. Следующим шагом мы реализуем букву L (Logic) в идиоме CLMV. Для этого я предлагаю ввести понятие воркера. Что такое воркер? Это компонент, который отвечает за построение программного представления страницы определенного типа. Например, воркер раздела, или воркер главной страницы. Возьмем, к примеру, воркер статьи. Мы как разработчики сайта имеем вполне четкое представление о том, что должно присутствовать на этой странице: это инфа об авторе плюс инфа о статье плюс сам текст статьи. Фсе! Как получить всю эту инфу по id статьи мы знаем. Возникает вопрос: а куда складывать все эти данные? Ответ: так прямо в контекст Velocity! Представим себе такой интерфейс:
Отсюда уже нетрудно представить, как будет выглядеть реализация ArticleWorker. Если мы теперь соорудим нечто вроде внутреннего диспетчера, который по УРЛу будет выдавать нам экземпляр воркера, то все становится вообще тривиально. На первых порах логику разрешения УРЛов в воркеры можно засунуть просто в отдельный метод в нашем сервлете-контроллере:
Понятно, что со временем все это оформится в отдельный конфигурируемый компонент, ну а пока нам нужно просто проверить идею. Просю. ЗЫ. Расширения шаблонов лучше поменять на стандартное .vm Это поможет избежать путаницы на последующих этапах. |
Автор: diablero 20.7.2007, 23:56 |
Я чего-то не могу сообразить. Как у нас взаимодействуют между собой MainController, воркеры и TemplateManager. |
Автор: Stampede 22.7.2007, 05:49 | ||
Хорошо, смотри:
Вот так вот примерно. |
Автор: diablero 22.7.2007, 15:55 |
Готово. |
Автор: Stampede 22.7.2007, 18:09 | ||
Дык и? Что получается? Сколько и каких страниц нааял? Все ли отрабатывает как должно? Надо же хоть что-то рассказать. Или ты такой этот, брат краткости? ![]() Вот еще какую штуку можешь сделать. Чтобы ходить на главную страницу по http://infinite/, нужно прописать соответствующий маппинг. К сожалению, в синтаксисе web.xml такое не предусмотрено. Поэтому приходится делать финт ушами - по счастью, совсем несложный. В корень кладется файл index.jsp такого содержания:
Надо только не забыть в web.xml прописать маппинг для JspServlet и определить welcome-file-list. В общем, жду рассказа. После этого внесем ряд полезных улучшений. |
Автор: diablero 22.7.2007, 18:39 |
Все работает, по УРЛу выдаются запрашиваемые страницы. Повыводил статьи, пользователей, в различных комбинациях. Полностью страницы я не доделал. Я еще нахожусь в стадии обдумывания и подбора вариантов. Кучу времени потратил на поиски готового шаблона, и как итог, делаю сам ![]() Все бы это протекало быстрее, если бы у меня не было врожденного отвращения к html и скриптам. Это в скором времени преодолею. Сделаю простенький как три копейки шаблон, чтобы мое обучение не стояло на месте. А по ходу дела, буду его дорабатывать. сделал. |
Автор: Ulysses4j 22.7.2007, 19:09 | ||
А так не пойдет:
web.xml ? |
Автор: Stampede 23.7.2007, 03:04 |
Увы, нет. При обращении по УРЛу, который заканчивается на слэш (т. е. представляет директорию), контейнер таки пытается физически залезть в эту директорию и найти один из файлов, прописанных в welcome-file-list. 2 diablero: Предлагаю для простоты обойтись для начала шаблоном, в котором будут постоянные шапка, левое меню навигации и подвал. Чтобы сильно не страдать, можешь сверстать это таблицей. И это, я надеюсь, твой HTML содержит только структурную разметку, а внешний вид ты задаешь через CSS. |
Автор: Ulysses4j 23.7.2007, 13:36 |
Скажите, а i18n не будет? Очень бы хотелось посмотреть. |
Автор: diablero 23.7.2007, 17:44 | ||
Так и сделал. |
Автор: diablero 23.7.2007, 18:46 |
И то это не главная страница. Нам нужно выделить в отдельный класс создание каркаса. Т.е. как бы статическое содержимое для всех страниц, кроме тех которые касаются учетной записи. Т.е. два каркаса. И нужно еще создать таблицу в базе section |
Автор: Maksym 23.7.2007, 19:30 |
diablero Скажи пожалуйста, куда ты положил persistence.xml, так чтобы при инициализации из сервлет-контейнера после деплоймента EntityManagerFactory его нашла? И, если несложно, всопроизведи структуру каталогов, которая сейчас получилась в проекте.. |
Автор: Stampede 23.7.2007, 19:33 | ||||||||||
Так, отлично! (на самом деле есть замечания, но пока ладно) Теперь внимание: сейчас мы резко упростим код и уберем повторяющиеся элементы в шаблонах. Для этого мы воспользуемся конструкцией Velocity #parse. Там, где у тебя стоит элемент <td> для основного контента, пишем:
Теперь обязанность каждого воркера - положить в контекст Velocity имя соответствующего шаблона под ключом "content". Например, в ArticleWorker:
Соответственно, из article.vm убираем все лишнее и оставляем только то, что составляет информационный блок страницы. Сам шаблон страницы (который ты запостил под именем home.vm), переименовываем в, скажем, template.vm - это будет наш единый шаблон для всех страниц сайта. Также поменяем сигнатуру метода Template getTemplate() в интерфейсе Worker на вот такое:
Поскольку, как мы только что решили, шаблон у нас будет один и тот же, есть смысл реализовать этот метод раз и навсегда для всех воркеров. Для этого создаем базовый абстрактный класс:
И тогда в остальных воркерах (наследуемых от AbstractWorker), нам останется только реализовать метод execute(). При этом работу по получению экземпляра Template по имени придется, естественно, перенести в метод service() сервлета-контроллера, но это как раз хорошо: нечего воркерам иметь дело с TemplateManager. Все, мы уже почти готовы подвести четвертую жЫрную черту. Таблицу для секций пока не создавай. Определи простой бин Section, напиши SectionManager, а в его методе инит зашей прямо в коде создание нескольких экземпляров Section. Да, вот еще: во всех бинах определи метод getUrl(), примерно такого содержания:
После всего этого у тебя получится практически действующий сайт (правда, пока только в режиме просмотра). 2 Ulysses4j: Тут вот какое дело. Необходимость заморачиваться i18n в обычных, десктопных программах вызвана тем, что там все гуевые компоненты отрисовываются программно, и надо каким-то образом хранить строки для них в языко-зависимых хранилищах - например, в ресурсах. В вебном же приложении нетрудно сделать так, чтобы Java код вообще не содержал никаких текстовых констант, а вся текстовка содержалась в шаблонах. Ну а уж сделать так, чтобы в зависимости от профиля пользователя брались шаблоны на том или ином языке - это вопрос проще пареной репы. |
Автор: diablero 23.7.2007, 19:53 |
to Maksym В META-INF, посмотри во вложении структуру |
Автор: Maksym 23.7.2007, 20:06 |
diablero Спасибо, все понятно, все работает. |
Автор: Stampede 23.7.2007, 20:17 |
Maksym, а ты, выходит, тоже активно следишь за сериалом? Интересно было бы услышать твое мнение - с учетом предыдущего (if any) опыта веб разработки и в свете твоих вебных планов. |
Автор: Maksym 23.7.2007, 20:24 | ||
Stampede Когда ты говорил о http://forum.vingrad.ru/index.php?showtopic=124877&view=findpost&p=1197482 ты держал в уме некоторый механизм, который позволил бы реализовать его внутри нашего приложения? Если да, то опиши его, пожалуйста, в двух словах. Добавлено @ 20:25
Слежу, можно сказать дышу diablero в затылок. Мнение пока формируется, как только созреет -- обязательно поделюсь. |
Автор: Stampede 23.7.2007, 20:46 | ||
Там на самом деле все достаточно просто. Представим, как могли бы выглядеть некоторые УРЛы:
Нагрузка тут ложится на два места:
В остальном логика останется практически без изменений. Но это все можно сделать факультативно. А для целей нашего проекта принятая нами простая схема подходит как нельзя лучше. |
Автор: Maksym 23.7.2007, 21:03 | ||
Stampede Спасибо. Исходя из своего понимания того что ты сказал, только что попробовал на примитивном уровне, все красиво ложиться. Интересно, что с использованием, например, jsf подобный механизм так прозрачно не встроить. Но мы еще не видели контроллера (буква C)... ![]() ![]() Добавлено через 9 минут и 15 секунд Но для этого прийдется прописать такой мэпинг:
Как тогда быть с картинками, css-ами и прочими ресурсами, которые обычно отдаются напрямую.. тоже анализировать url..? |
Автор: Stampede 23.7.2007, 21:46 | ||
Молодец, верно подметил. Один из выходов - в web.xml явно замапить картинки, css, js и пр. статические ресурсы на дефолтный контроллер, например, по расширениям. Другой выход - забить вообще на встроенные средства и отдавать всю статику самому. Правда, для этого придется конфигурировать content-type. Зато появляются плюсы, такие как возможность при желании логировать, в рамках единого подхода, запросы к статике, тонко управлять кэшированием на клиенте, гзиповать вывод, и пр. Да в общем-то, можно сказать, уже и видели: класс сервлета-контроллера и метод getWorkerClass(String url). |
Автор: diablero 23.7.2007, 21:51 | ||
Вот тут мне не понятно. Наш алгорим действий: 1. В service() определяем имя шаблона, от воркера получаем контекст. И сливаем их вместе. 2. Полученный Template мы должны слить с template.vm. Как выполнить второй пункт? |
Автор: Stampede 23.7.2007, 22:08 | ||||
Нет, там все проще, чем ты думаешь:
А воркер нам как раз и вернет "template.vm". |
Автор: diablero 23.7.2007, 22:17 | ||
Не, я не это имел ввиду. template.vm это наш базовый шаблон. Где есть такая строчка
Мы запрашиваем статью, в воркере статьи, мы заполняем контескт и сливаем его с post.vm. Теперь нам нужно слить post.vm с template.vm. Вот я не пойму как это сделать |
Автор: Stampede 23.7.2007, 22:27 | ||
Так а это делается не сливанием в смысле merge. Просто когда движок Velocity дойдет до строчки #parse("$content"), он полезет в контекст, найдет там переменную "content", и использует ее как имя шаблона, который нужно включить в данном месте. Как я уже говорил ранее, это забота воркера - заранее положить в контекст имя нужного шаблона:
|
Автор: diablero 23.7.2007, 22:27 |
Вопрос снят. ![]() Добавлено @ 22:28 А... Ту уже ответил ![]() Сейчас все выложу Релиз кандидат четвертой жЫрной черты |
Автор: Stampede 23.7.2007, 22:48 |
Поздравляю! Разбор полетов - завтра. А пока - всем отдыхать ![]() Да и мне бы еще по работе чуток поработать ![]() |
Автор: Stampede 24.7.2007, 16:26 | ||||||
Ну знач поехали. 1. Пора бы нам уже начать группировать классы по пакетам, а то куча уже большая получается. Напрмер, явно напрашиваются к выделению менеджеры, воркеры и data бины. 2. Есть несколько не совсем удачных имен, которые привносят путаницу.
3. Использование переменных в шаблоне. Вот смотри, как у тебя сделан post.vm:
А ведь Velocity-то позволяет обращаться к полям бинов (имеющим соответствующие геттеры) напрямую! И получается, что нам всего-то нужно засунуть в контекст две переменные: user и article. Это резко сокращает вероятность ошибок типа недосмотров, опечаток и наложения имен. Шаблон будет выглядеть вот так:
Я убрал "версию для печати", потому что в наше время это делается гораздо проще, средствами CSS. Ну и по-хорошему $user.name надо бы сделать ссылкой:
4. Ну и по мелочам:
Может, еще что замечу/вспомню. Предлагаю подкорректировать по своему усмотрению и перезалить. |
Автор: diablero 25.7.2007, 20:30 |
Создал бин News. И что бы не было путанницы, везде теперь фигурирует Post, а не Article. Классы по пакетам сгруппировал, может быть имя не совсем удачное выбрал? Интерфейс EngineManager убрал, не думаю что откажусь от Velocity. Еще посмотрю и проанализирую... Перезалил. Теперь уже вырисовыватся вполне ясная картина, и структура сайта. Рано или поздно я бы дошел до этого, или бы забил на пол пути ![]() З.Ы. Попутно научился засыпать раньше чем закроются глаза ![]() |
Автор: Stampede 25.7.2007, 21:53 | ||
Да, и это заметно - например, по тому, как ты ловко добавил сущности Section и News, со всеми сопутствующими компонентами. В целом структура кода уже почти окончательная. Единственное, мы немного изменим сигнатуру метода Worker.execute() и введем дополнительно несколько новых понятий - для пущего удобства. А сейчас приступаем к следующему этапу - аутентификации и авторизации доступа. Как мы уже многократно убеждались, "книжные" подходы для нас, простых сайтостроителей, не годятся. Поэтому мы пойдем другим путем. А именно, возьмем на вооружение простую технику, веками используемую в мире ПХП - технику, основанную на куках. Для этого важно понимать, что такое куки, кем и когда они создаются, и как участвуют в коммуникации по HTTP. Чтобы иметь полную картину происходящего, рекомендуется скачать и установить http://www.getfirebug.com/. Тогда на закладке Net будет видно, с точностью до буквы, что поылал браузер, и что приходило в ответ. Итак, первым делом нам нужно залогиниться. Для этого где-нибудь в шапке страницы пропиши форму для ввода с полями login, password и кнопкой "Войти", метод POST. Атрибут action пусть указывает на такой УРЛ: /submit/login.do Здесь .do оправданно, поскольку это будет у нас невизуальный ресурс. Соответственно, для него надо будет прописать маппинг в web.xml (на ControllerServlet) и в диспетчере воркеров (на LoginWorker.class). В воркере надо прочитать параметры запроса login и password, пробить их через UserManager, и положить в VelocityContext объект типа User. ПДалее показать факт успешного логина (или ошибки) через вставной подшаблон confirmation.vm. Пока этого будет достаточно. |
Автор: diablero 25.7.2007, 23:17 | ||||||
Как его воткнуть грамотно вв шапку страницы, подумаю завтра. А так проверил работоспособность, работает.
template.vm
web.xml
|
Автор: Stampede 26.7.2007, 01:02 | ||||||||||||||
Ну, я бы точно по другому сделал. У тебя там бины данных, менеджеры и воркеры в одной куче оказались. Но дело твое.
У нас еще много будет невизуальных обработчиков всяких сабмитов, и все они будут проходить через тот же сервлет-контроллер, так что имеет смысл задать маппинг по расширению *.do. С формой более-менее все в порядке, с обработчиком тоже. Так что перейдем к следующей задаче: запоминанию. Но сначала обещанный рефакторинг. Речь идет о возвращаемом значении метода execute(). Он имеет тип VelocityContext. Это не совсем удобно, ибо это весьма общий тип. Коль скоро мы имеем дело с представлением вебной страницы, хорошо было бы, чтобы оно предоставляло нам прямой доступ к ряду существенных вебных штучек. Поэтому я предлагаю завести класс (назовем его Page), наследующий от VelocityContext, примерно такого содержания:
Соответственным образом изменится и сигнатура метода execute(). И вот что это нам сразу дает: мы сейчас легко и просто реализуем паттерн PRG (Post-Redirect-Get). Пару слов о том, что это за паттерн и для чего он нужен. Если в ответ на сабмит формы возвращать отображаемый текст, то это чревато многими путаницами: например, если юзер решит походить кнопками браузера Взад/Вперед, или нажмет рефреш. Чтобы избежать этих неприятностей, есть достаточно простое решение: по сабмиту всегда возвращать HTTP код статуса 302 (ресурс временно перемещен), заодно в заголовке ответа Location указывается адрес для переадресации. В Servlet API для этой цели есть удобное сокращение: response.sendRedirect(String url); Так вот, я предлагаю для всех обработчиков сабмитов выставлять переменную page.redirectUrl, а в коде сервлета ее анализировать и соответственно что-то делать. Возникает вопрос: а почему не вызывать sendRedirect() непосредственно в коде воркера? А потому, что в этом случае ответ сразу уйдет браузеру, а нам бы хотелось поманипулировать заголовками ответа, в том числе кукисами. Итак, в LoginWorker пишем примерно так:
Вообще надо сказать, работа с куками в Servlet API сделана очень неудачно - приходится делать много лишних телодвижений для реализации самых простых вещей. Выходом может быть написание собственных удобных функций. Например, мы могли бы в Page предусмотреть очень простой метод setLongLivedCookie(String name, String value), и то, что мы тут делали в четыре строки, делалось бы в одну. Аналогичным образом можно было бы написать метод resetCookie(String name). Впрочем, все это со времени мы и сделаем. Не забываем, что в сервлете-контроллере мы должны проверить значение переменной redirectUrl:
Теперь смотри, diablero: мы сейчас опять применим объектный подход и снова разом упростим структуру кода. Юзера нам надо определять при каждом входящем запросе, потому что от этого зависит, что показывать, что не показывать, и показывать ли вообще. Чтобы не дублировать код чтения кукисов во всех воркерах, мы в базовом AbstractWorker можем написать такое:
Теперь, когда общая для всех воркеров часть работы закодирована, мы в производных классах пишем так:
Из предыдущего фрагмента особенно хорошо заметно, насколько убоги штатные средства работы с куками. Например, Cookie[] request.getCookies() еще нужно не забыть проверит на null, не говоря уж о выуживании кук по имени. Поэтому, diablero, будет хорошо, если ты реализуешь удобные методы работы с куками в классе Page. После этого у тебя не должно возникнуть затруднений, чтобы сделать в шапке избирательное приветствие: либо "Привет, diablero! | Выход", либо "Имя: | Пароль: | Войти". |
Автор: diablero 26.7.2007, 09:31 | ||
А как бы ты сделал? |
Автор: Maksym 26.7.2007, 13:16 |
Тем, кто, возможно, захочет пройти путем diablero:
|
Автор: Stampede 26.7.2007, 16:13 | ||
А ты угадай ![]() Maksym, спасибо за ссылки. |
Автор: diablero 26.7.2007, 18:57 |
Думаю угадаю ![]() Мне осталось с Cookie разобраться. Завтра доделаю, а сейчас арбайтен по стахановски |
Автор: Maksym 28.7.2007, 13:33 | ||
По поводу Cookies. Чтобы не писать лишнего, а сосредоточится на архитектуре, можно взять вот такой утилитный класс (спионерено http://www.nextapp.com/platform/echo1/echo/doc/api/1.0/private/server/nextapp/echoservlet/CookieManager.html):
|
Автор: diablero 28.7.2007, 21:10 | ||||||||||
Всетаки возникло. Я чего-то никак не доганю как выход сделать.
Во втором if'е надо плюшки сбросить и перегрузить главную страницу. Если даже сбросить вручную, то это ничего не дает. Потому, что при редиректе на home.shtml ничего не происходит.
|
Автор: Stampede 29.7.2007, 01:24 |
Ага, посмотрю попозже. Щас только с пейнтбола, указательный палец все еще спусковой крючок пытается на мышке нащупать ![]() |
Автор: diablero 29.7.2007, 11:48 |
Я понял почему такая проблема. Кукисы не удалялись. Все работает только при одном условии, если после того как "выйти", нажать обновить. |
Автор: batigoal 29.7.2007, 12:04 |
OFF: смотрю на вас, и скриплю зубами от бессильной ярости. Тоже хочется параллельным курсом идти, но еще целый месяц не смогу с вами быть. |
Автор: Stampede 30.7.2007, 19:27 | ||||
Вот, вот это очень важный момент. Почему я и категорически настаиваю, чтобы по POST обязательно шел редирект именно на страницу подтверждения. Дело в том, что у разных браузеров - различная умолчальная политика кэширования страниц, и если мы хотим предсказуемого поведения, то должны явно управлять кэшированием на клиенте. Делается это выставлением заголовков ответа HTTP. Нам нужно, чтобы страница подтверждения никогда не кэшировалась. Для этого в соответствующем воркере (да-да, нам понадобится специальный воркер) нужно будет выставить такие заголовки:
Страницу подтверждения следует сделать макимально простой, чтобы с нее никуда нельзя было перейти, кроме как на тот линк, который на ней будет указан. Сейчас для простоты пропиши там статический линк на главную страницу. Потом, когда научимся работать с сессией, сделаем как надо. Для выхода лучше написать отдельный LogoutWorker, потому что логика там совсем другая, и не нужно мешатьь ее в одну кучу с логикой для логина. Да, и вот еще. Метод Worker.getTemplateName() был введен нами в интерфейс Worker вынужденно, за неимением лучшего места. Но теперь, с введением класса Page, будет намного логичнее переместить этот метод имено туда. Я понимаю, что все это выглядит сейчас несколько запутанно. Ничего не поделаешь: такова природа вебной коммуникации. Но зато ты сейчас работаешь фактически напрямую с HTTP, а не прячешься от него за ширмой туманных директив JSP, и мало-помалу у тебя в голове уляжется, что и как там происходит. В общем, потерпи - обещаю, просветление уже близко ![]() 2 batigoal: Дак а нет ничего страшного. Ведь пойнт превращения этой темы в сериал как раз и заключался в том, чтобы протоколы учебного курса сохранились в веках для всех желающих приобщиться, в любое время. |
Автор: Maksym 30.7.2007, 20:17 | ||
Я, наблюдая за сериалом, получаю огромное удовольствие именно от этого ![]() ![]() и насколько результирующая архитектура будет компонентной.. вот, например, захочу я внедрить в страницы элемент с ajax-поведением.. но, боюсь, я забегаю вперед.. ![]() |
Автор: diablero 30.7.2007, 20:56 | ||||
Тут я не понял куда ![]() Сделал.
Сделал, добавил сеттер. Чтобы можно было подменять шаблон. Терпения у меня вагон и маленькая тележка ![]() Добавлено через 7 минут и 36 секунд to модераторам Какая-то ерунда иногда происходит при загрузке страницы форума, она иногда грузиться, грузиться, закачивается примерно пол метра и все. Нажимаешь обновить и она грузиться нормально. |
Автор: Stampede 30.7.2007, 22:03 | ||||
Знач объясняю. Там на самом деле все достаточно просто. У нас будет специальная страница, доступная по адресу /confirmation.shtml. Это не совсем обычная страница, так как она будет использоваться во всех сценариях обработки форм (то, что называется web flow). Основное ее назначение - свести к минимуму сюрпризы, связанные с особенностями вебного взаимодействия: причуды браузеров, причуды пользователей, перемещения по кнопкам взад-вперед, работа из нескольких окон/закладок, и пр. Так вот, чтобы все это исключить (или хотя бы свести к минимуму), нужно сделать так, чтобы содержимое /confirmation.shtml не кэшировалось в браузере, а каждый раз запрашивалось с сервера. С этой целью мы заводим специальный ConfirmationWorker, который делает две вещи: переопределяет templateName на confirmation.vm, и выставляет заголовки управления кэшированием. Пару слов о содержимом confirmation.vm. Понятно, что текст подтверждения будет зависеть от выполненного юзером действия. Например:
Но я повторяю, мы это сделаем чуть позже. А пока что пропишем статически: Действие выполнено. Перейти на главную страницу. Далее, еще один момент. Я пытался сэкономить тебе один набор УРЛ-маппинг-воркер-шаблон, но сейчас вижу, что получилось в итоге хуже. Поэтому давай поправим, пока не поздно. Идея в том, чтобы сделать форму для логина отдельной страницей, а не помещать ее в шапку основного шаблона. Предлагаю сделать так: УРЛ: /form/login.shtml Воркер: ru.selfexpression.workers.LoginFormWorker Шаблон: templates/form/login.vm Логики в воркере практически никакой не будет, кроме задания шаблона. Действия по сабмиту (LoginWorker) остаются в основном без изменений, но в случае неуспешного логина выставляется redirectionUrl равный /form/login.shtml. То есть юзеру предлагается повторить ввод. Попутно отмечу, что раз мы задаем адрес для переадресации, никаких шаблонов указывать не надо, поскольку до рендеринга дело все равно не дойдет.
Будет, все будет. Но предупреждаю: разговор об Ajax предстоит очень долгий, и скорее всего потянет на отдельный сериал. Ибо пространства для маневра в этом деле даже еще больше, чем в традиционном вебе. |
Автор: diablero 30.7.2007, 23:05 | ||||||||||
А у меня сделанно отдельной страницей.
Мы же договаривались:
Поэтому в HomeWorker'е:
Полный текст на предыдущей странице. В связи с последними изменениями confirmation.vm теперь у меня login.vm
Т.е. после того как юзер ввел имя и пароль или вышел его отправляем к ConfirmationWorker. Но это дело не меняет, на главной странице нужно жмакать обновить. |
Автор: Stampede 31.7.2007, 00:32 | ||||||
Посмотрел сейчас, как у меня сделано в разных проектах. Тут дело в том, что смысл разных заголовков, http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9, остается довольно туманным. Поэтому многие вещи приходится постигать методом проб и ошибок, а также путем долгого лазания в инете. Так вот, я пытался найти такую комбинацию заголовков, чтобы обычные страницы всегда брались с сервера при обращении к ним по адресу, но при навигации кнопками вперед-назад - брались таки из кэша. В результате множества экспериментов с разными браузерами получилось так, что искомое поведение реализует такой заголовок: Cache-Control: no-cache Соответственно, предлагаю в методе ControllerServlet.service(), непосредственно перед началом вывода текста HTML добавить:
А в ConfirmationWorker исправить на:
Таким образом у нас все без исключения страницы будут динамическими, но страница подтверждения кроме того будет перезапрашиваться при навигации кнопками вперед-назад. LoginWorker, как ты его привел в последнем посте, сделан не по заданию. Я ведь говорил, что это невизуальный ресурс: по нему не должно возвращаться никакого ХТМЛа, а должен только идти редирект: или на страницу подтверждения, или обратно на форму логина. Перечитай пожалуйста внимательно мой предыдущий пост. |
Автор: diablero 31.7.2007, 15:58 | ||
Сделал как ты предложил, IE адекватно и правильно все обрабатывает, в отличии Opera'ы 8.5. Если открывать оперой, то как и раньше нужно при выходе обновлять главную страницу. LoginWorker сделал по заданию, как оказалось, так код логичнее и правильнее ![]() |
Автор: Stampede 31.7.2007, 16:41 | ||
А попробуй, в порядке эксперимента, заменить Cache-Control: no-cache на Pragma: no-cache. Есть у меня одно подозрение. Как только с этим разберемся, добавим еще немного косметики и будем подводить пятую жЫрную черту. Добавлено через 3 минуты и 23 секунды Так, минуточку, а ты в Firefox'е тестируешь? Firebug поставил? Если нет, то надо обязательно. Все будешь видеть как на ладони. |
Автор: diablero 31.7.2007, 19:14 | ||
Нет, так не работает, даже IE. да нет |
Автор: Stampede 31.7.2007, 19:53 |
Ну так поставь. Делов-то - один гугл-сёрч и один тык мышкой. Заодно проверишь, как выглядит ответ сервера, и в частности, меня интересует заголовок Date. Он вообще присутствует? Если нет, попробуй выставить его вручную (например, в самом начале метода service()). |
Автор: Stampede 31.7.2007, 21:30 | ||
Вот, кажЫсь нашел:
Так что попробуй вот это: Cache-Control: private, no-cache Но убедись, что страница не перезагружается по кнопкам вперед/назад. Если перезагружается, будем искать другие комбинации. ЗЫ. Терпеть не могу оставлять взведенные грабли за спиной. Никогда не знаешь, когда рванут ![]() Update: Вот еще похоже в чем дело. Досмотрел до конца статью по ссылке: там чувак упоминает заголовок Last-Modified. Ну точно! Я щас глянул на свой сайт из Оперы: он возвращает Cache-Control: no-cache. НО! У меня кроме того выставляется также Last-Modified: <текущее время>! Короче, попробуй в разных вариантах и под разными браузерами. Как чего хорошего получится - пиши. |
Автор: diablero 1.8.2007, 01:07 | ||||
Не помню уже откуда взял, но кто-то писал что даже такая комбинация из под оперы не работает.
Я перебрал уже кучу вариантов. Пока все дохло.
Может еще что есть? Днем на свежую голову продолжу... |
Автор: diablero 1.8.2007, 22:31 |
День и свежая голова результатов не дали. Нашел кучу тем с аналогичной проблемой. И те решения которые в них предлагали у меня не работают. |
Автор: Stampede 2.8.2007, 00:13 | ||||
Я сейчас тоже поэкспериментировал с разными вариантами, выявил туеву хучу новых нюансов по сравнению с временами, когда делал предыдущие проекты. Короче, предлагаю такое окончательное решение. Для всех страниц:
В воркере ConfirmationWorker дополнительно:
У меня в таком варианте получается так:
Короче, вердикт: ну их всех нахер. Мы не собираемся гробить еще кучу времени на поиск идеального решения, которого, по всему выдать, все равно не существует. Будем ориентироваться на Firefox, как на наиболее стандартопослушный браузер. А кто не спрятался - мы не виноваты. На всех все равно не угодить. В том виде, в котором я привел, работать худо-бедно будет во всех трех испробованных. Вот и ладушки. Возражений нет? Тогда прошу зип в студию. |
Автор: diablero 2.8.2007, 14:20 |
А у меня получаеться так: Firefox 1.0 RC1 (такой вот древний) работает как и опера. Все берут из кэша. IE 6.0 работает также как и твой 7.0 Я вообще всегда думал, что по части веба, все равняются на IE... Как самый массовый и правильный по отображению кода браузер. |
Автор: batigoal 2.8.2007, 15:11 | ||
В толпе разработчиков такая фраза вызывает бурный смех ![]() Насчет массовости - возможно, но вот в следовании стандартам его упрекнуть сложно. Зачастую ребята из MS пытаются создать свои, а не поддерживать существующие. |
Автор: diablero 2.8.2007, 18:59 |
А какой браузер не вызывает смех? Опера, которая отображает простой html код не так как положенно. Я конечно уважаю этот браузер, но косяков за ним достаточно. |
Автор: Stampede 2.8.2007, 19:16 | ||||
Тут на самом деле вот какое дело. IE - это один из самых глючных браузерв, в плане следования стандартам. НО! - так уж сложилось - до сих пор самый массовый. Потому что рядовым пользователям, как правило, нет никакого дела до всяких там стандартов: что пришло с виндой, то и ладно. Процент посетителей, использующих IE, по разным прикидкам (и для разных целевых аудиторий) составляет 75-90%. Хотя надо сказать, с появлением Firefox эта цифра маленько ушла вниз, но все равно не до такой степени, чтобы серьезно подвинуть позиции Мелкософтного браузера. Что остается разработчикам в такой ситуации? Правильно, "мыши плакали, кололись, но продолжали есть кактусы". То есть при любых раскладах необходимо кровь из носу обеспечить, чтобы в IE работало, как задумывалось. Но тут есть два принипиально разных подхода.
Так вот, второй подход - он идеологически более правильный. Потому что идет от общемировых, так сказать, принципов, закрепленных в спецификациях. [голосом пьяного Мягкова] "Это документ, между прочим" ![]() Но ладно, это хоть и методологическая, но в общем-то лирика. Теперь по заданию. 1. Ты в коде воркеров везде объявляешь и инициализируешь классовую переменную page. А ведь она у нас специально объявлена как protected в AbstractWorker, и там же и инициализируется. А ты ее, получается, перебиваешь. А что если у нас со временем добавятся дополнительные действия? Например, я хотел тебе предложить добавить в Page такой метод:
И чтобы этот метод вызывался при аутентификации по кукисам, то есть в общей для всех воркеров части кода, AbstractWorker.execute(). А иначе у тебя получается, что Velocity-переменная $user попадает в контекст только в HomeWorker . Соответственно, из-за этого на других страницах приветствие отображается неправильно (кстати, ты там зря вводишь дополнительно $flag="login", достаточно самой переменной $user). 2. Я в предыдущем посте намеком предложил тебе завести в Page переменную startDate. Ее смысл в том, чтобы использовать единое значение времени во всех местах, где оно понадобится (а оно таки еще понадобится многократно). Это предотвратит бессистемное использование несогласованых дат и сэкономит немножко времени на создание множественных new Date(). Инициализировать startDate, понятное дело, лучше в конструкторе Page. Заодно ее же можно положить в контекст, чтобы обеспечить доступ к ней из шаблонов. В остальном вроде все нормально. Следующим шагом мы создадим мини-фреймворк для работы с формами, и с его помощью приделаем регистрацию новых пользователей и ввод/редактирование постов. Но сначала нужно внести коррективы по списку (особенно п. 1) и перезалить. |
Автор: batigoal 2.8.2007, 21:14 |
Обычно - FF. |
Автор: diablero 2.8.2007, 23:30 |
Изменил и перезалил З.Ы. Из-за всяких халявщиков, отпускников, работаю сутки через сутки. Так что пока, времени мало... |
Автор: Stampede 3.8.2007, 00:11 | ||||
Нет, все равно немного не так. У тебя после исправления:
А надо бы:
Но да ладно, не бери в голову. У меня созрела мысля, как все это дело оформить в менее запутанном виде. Но это все завтра. Щас - отдыхать ![]() |
Автор: diablero 3.8.2007, 00:23 |
Вопрос на засыпку. А нашу задачу, по обновлению страниц, можно решить с помощью javascript? Может кто подкинет решение? |
Автор: Stampede 3.8.2007, 00:51 | ||
Нет, нельзя. Даже если ты по клику подменишь переход по ссылке на код типа window.location = '/', браузер в своем решении о том, откуда брать контент, все равно будет руководствоваться своей внутренней политикой кэширования. По этой причине, например, у многих начинающих Аяксоидов возникают непонятки с подгружаемым динамическим контентом. Приходится в итоге все равно решать через заголовки. |
Автор: diablero 3.8.2007, 13:30 |
Вот нашел небольшой туториал http://www.javaworld.com/javaworld/jw-10-2006/jw-1006-logout.html?page=3. Там все через сесии, используется фильтр, и с logout у них все нормально. Я попробывал использовать фильтр, но он у меня не заработал. Посмотри, может мы найдем решение... |
Автор: Stampede 3.8.2007, 19:15 | ||
diablero, я должен развеять перед тобой одно заблуждение. Ты полагаешь, что статьи в Java World публикуют какие-то гуры и небожители. Спешу тебя разочаровать: нет, это подчас обычные девелОперы, со своейственными каждому человеку ограничениями и заблуждениями. Возьмем, к примеру, статью товарища Хуанг Ли, ссылку на которую ты приводишь. В чем суть его, тыкскыть, решения? Да все в той же идиоме PRG (Post-Redirect-Get)! Просто чувак, похоже, не знал такого слова, а то бы постеснялся с умным видом расписывать на трех страницах такие элементарные и всем известные вещи. Обрати внимание, что саму по себе идею PRG я тебе растолковал http://forum.vingrad.ru/forum/topic-124877/225.html#, Реализация этой идеи у нас тоже получилась простая и логичная: /form/login.shtml -> /submit/login.do -> /confirmation.shtml То, что Опера и старый Firefox не понимают каких-то заголовков - это, конечно, плохо, но видишь ли, от того, что мы закодируем ту же самую PRG логику в виде кривых и запутанных рецептов от товарища Хуанг Ли, понимаемость браузерами заголовков, увы, не улучшится. Чтобы тебе это наглядно продемонстрировать, я предлагаю тебе зайти по адресу, где у сего аффтара находится онлайновая демонстрация описанного решения для Logout: http://pragmaticobjects.org/properLogoutDemo/. Как нетрудно убедиться, чуда не произошло. Если Опера не понимает директиву Cache-Control: no-store, то никакие Хуанг Ли тут не помогут. Чтобы тебя окончательно стало ясно, что тут и к чему, открою еще один маленький секрет. Я эту статью уже видел раньше. Более того, я видел и еще более раннюю статью этого же аффтара, на эту же тему. Вот она: http://www.javaworld.com/javaworld/jw-09-2004/jw-0927-logout.html. Так вот, в этой первой статье он вообще пытался по логину перейти сразу на авторизованный ресурс через директиву jsp:forward, то есть посредством внутрисерверной переадресации, а не через ответ 302. Потом его ткнули носом, что это типа не работает, и тогда он выступил со второй статьей. Которую я в общем уже прокомментировал. Ну и чтобы развеять последние остатки сомнений, покажу пальцем на один кусочек кода. В статье, которую ты приводишь, вся "магия" на самом деле сосредоточена вот в этих строчках:
Заметили ошибку? Правильно, setHeader("Cache-Control","no-store") забивает значение, выставленное в предыдущей строчке! Если он хотел его добавить, то должен был использовать addHeader("Cache-Control","no-store"). Тогда с сервера приехало бы Cache-Control: no-cache, no-store. Вот такие вот у нас писатели. Короче, все: тема закрыта. Возвращаясь к заданию. Нам предстоить нарисовать несколько дополнительных страниц, в том числе форм. Если ты будешь делать это в том же ключе, как у тебя сделана form/login.vm, то мы очень скоро перестанем понимать, что там наворочено в разметке. Отсюда подготовительный этап:
Лучше все это сделать в статике, то есть прям завел на диске файл template.htm, и пишешь в нем. Потом открываешь браузером с диска и смотришь, что получилось. Файл style.css можешь положить в эту же директорию. Верстать надо семантоично, чтобы код отражал только структуру документа, но не его оформление. То есть чтобы никаких bgcolor, font и пр. Чтобы было за что зацепиться CSS'ом, используй атрибуты id и class. Если будут вопросы - не стесняйся. |
Автор: diablero 5.8.2007, 01:46 |
Подготовительный этап выполнил... Выложил шаблоны. |
Автор: Stampede 6.8.2007, 09:47 |
diablero, я посмотрел разметку. Не идеально, конечно, но для начала пойдет. Я сейчас пока маленько занят, давай отложим до вторника. Между делом можешь нарисовать форму для создания/редактирования поста. Старайся придерживаться тех же правил семантичности. Да, пока не забыл. У тебя встречаются элементы с одинаковым id. Это впоследствии может послужить источником больших непоняток. Для справки: id назначаются только тем элементам, про которые точно известно, что они всегда будут в единственном экземпляре. Например, id="navigation", id="content", id="footer", и т. д. Для повторяющихся (в пределах одной страницы) элементов одного типа используется понятие класса: например, class="post-title" на странице раздела (для списка постов). Как запостишь форму, пойдем дальше. |
Автор: diablero 6.8.2007, 15:05 |
Немного переделал и перезалил. Пока шаблоны будут в таком стиле. У меня пока катострафически не хватает времени, чтобы во всем разобраться и сделать все основательно. Но я все обязательно переделаю. Во вторник вечером сделаю страницу для регистрации пользователя. Потом уже для добавления и редактирования постов. Надо найти скриптовый редактор, я вот когда-то давно видел чуть ли не ворд. Я думаю что нужно добавить еще один бин, это подраздел. Еще несколько вопросов: 1. ты уже говорил о вариантах передачи css и прочих ресурсов, так как мы будем делать? 2. знаешь ли ты бесплатный хостинг, чтобы туда влез наш проект? 3. какой ты можешь порекомендовать из платных? |
Автор: diablero 7.8.2007, 22:04 |
Сделал страницу регистрации, она конечно еще требует доработок и проверок корректности данных. Но продолжать уже можно. Этим я займусь походу. |
Автор: Stampede 7.8.2007, 22:44 | ||
Посмотрел форму регистрации. Замечания:
По вопросам:
Первый вопрос имеется в виду, как выдавать статику? Да обычно, дефолтным сервлетом. Просто ты сгоряча прописал у себя в web xml маппинг "/" на ControllerServlet, а надо бы ло только по расширениям '.shtml и *.do. Как только исправишь, Томкат начнет выдавать картинки и прочую статику. Бесплатных хостингов не знаю. Сам использую платный 4java.ca. Завтра начнем делать обработчик форм. |
Автор: Stampede 8.8.2007, 18:56 |
Так, ладно, давай делать обработчик форм. Снечала предварительные замечания. Если бы мы делали мощное веб приложение с развернутой функциональностью, мы бы наваяли соответствующий фреймворк, обеспечивающий автоматическую валидацию: через рефлексию, по аннотациям, возможно с применением Javascript, и прочими штучками. Но поскольку форм у нас будет мало - пальцев одной руки хватит, чтоб посчитать - то мы всем этим заморачиваться не будем. Просто для каждой формы будет свой обработчик по типу LoginWorker, в котором и будет зашита логика валидации. Итак, форма регистрации. Я предлагаю сделать ее с прицелом на то, чтобы впоследствии ее же можно было использовать для редактирования профиля. Но все по порядку. Примем такую схему: УРЛ: /form/registration.shtml Воркер: RegistrationFormWorker Шаблон: forms/profile.vm | (по сабмиту) V УРЛ: /submit/register.do Воркер: RegisterWorker --> (если ошибка, то возврат на стр. формы) | (если все ОК, редирект на стр. подтверждения) V УРЛ: /confirmation.shtml Воркер: ConfirmationWorker Шаблон: confirmation.vm RegistrationFormWorker пока будет без логики (только положи в контекст $content = "forms/profile.vm"). В RegisterWorker проверь, чтобы все обязательные поля были на месте, чтобы пароль в обоих полях был одинаковым, и чтобы юзера с таким именем в базе не было. Если что-то из этого не выполняется, футболь обратно: page.setRedirectionUrl("/form/registration.shtml"). Потом создай нового юзера, пропиши для него куки по аналогии с LoginWorker, и отсылай на страницу подтверждения. Если есть вопросы - не стесняйся. |
Автор: diablero 9.8.2007, 01:12 | ||||||
Сделал, работает до того момента, как заливать юзера в базу.
Думается плохо, продолжаю разбираться ![]() |
Автор: Shaggie 9.8.2007, 09:05 | ||||||
diablero, Stampede, один момент. В коде класса TemplateManager метод mergeTemplate из класса org.apache.velocity.app.VelocityContext вызывается таким способом:
Этот метод deprecated, и несмотря на то, что он хорошо работает под Tomcat, Jetty отчаянно рушил мне все русские слова в шаблонах, что стоило немалой головной боли. Заранее неизвестно, где выплывут новые грабли, поэтому рекомендую заменить его на более современный вариант:
То есть:
|
Автор: diablero 9.8.2007, 13:48 |
Shaggie посмотрел доки к пакету, и не увидел что метод этот deprecated. Посмотри исходники, и с кодировками все прозрачно будет |
Автор: diablero 9.8.2007, 19:01 | ||
Вылетает тут:
Пока никак не могу понять, по отдельности работает... |
Автор: Stampede 9.8.2007, 19:27 | ||
Дак ты же пытаешься заново начать транзакцию. Ты к этому моменту уже в транзакции! Так что просто убери все что у тебя там относится к транзакции, включая commit() и rollback(), и все будет пучком. Обо всех этих вещах уже "позабочено" в методе service(). |
Автор: diablero 9.8.2007, 21:24 |
Я это понял, и чтобы загладить свою глупость хотел пост подредактировать. А ты уже ответил![]() |
Автор: Stampede 9.8.2007, 21:32 | ||
Не надо этого делать. На заглаженной глупости может поскользнуться кто-то другой ![]() |
Автор: diablero 9.8.2007, 21:47 | ||
Что мы сейчас имеем. 1. проеверяем что пользователя такого нет. 2. что пароли совпадают, но не проверяем сложность. Сразу вскрылась недоработка, нам нужно создать бин group. Я еще не нашел как прикрутить шаблон ввода к дате. Что делаем дальше? До конца прорабатываем форму? |
Автор: Stampede 9.8.2007, 23:01 | ||||
Нет, не надо. Сейчас мы добавим в нашу картину вселенной вебсайта один полезный элемент. Суть вот в чем. Когда юзер ошибается во вводе данных, мы должны перенаправить его обратно на страницу формы. Но на ней уже должны быть заполнены те поля, которые он ввел ранее, плюс каким-то образом обозначено, в чем заключалась ошибка. Возникает вопрос: как это сделать? Прежде чем предложить решение, я хотел бы напомнить, что все, абсолютно все формные данные приезжают к нам на сервер в виде текста, а если точнее, то в виде пар parameter=value. Для удобства работы с этими парами в Servlet API есть метод request.getParameter(String name) и другие. Отсюда напрашивается решение: если мы каким-то образом сохраним эти значения между обращениями к /form.registration.shtml, то сможем при рисовании формы заполнять поля теми значениями, которые поступили по сабмиту! Вопрос: где сохранять эти данные? Дак, вестимо, в сессии! Остается еще один вопрос: а в каком виде это делать? Вот тут мы и подходим к тому решению, которое я хотел предложить. Мы заведем достаточно простенький класс - назовем его FormData - унаследованный для удобства от HashMap. Можно, конечно, организовать связь с мапом и через containment, но это потребует больше писанины. Класс примерно такой:
В принципе можно было бы вообще брать request.getParameterMap() и сохранять все мапом, но имея специализированный класс, мы сможем впоследствии добавлять всякие удобные штучки. Например, как видно из данного примера, мы можем сразу же добавить в обработчик такой код:
Теперь о том, как этим классом пользоваться. Во-первых, у нас изменится код RegistrationFormWorker. Если раньше мы просто клали в контекст имя шаблона, forms/registration.vm, то теперь работы будет побольше. Чтобы сделать код шаблона проще, мы будем всегда передавать ему данные через объект FormData (например, в переменной контекста $formData). А объект этот сначала искать в атрибутах сессии, а если отсутствует - то создавать новый. Замечу, что при регистрации этот новый объект FormData так и будет передаваться пустым, а вот когда мы ту же самую форму задействуем для редактирования профиля юзера, то мы этот новый экземпляр FormData сначала заполним значениями из объекта User. Сначала все может показаться запутанным, но по мере того как будешь делать, все постепенно прояснится. |
Автор: diablero 10.8.2007, 19:21 | ||||||||
Если передавать в класс FormData request.getParameterMap(), то выскакивает такое исключение. Как я понял, возникает когда нет значение у ключа.
Поэтому сделал следующим образом:
Соответсвующим образом изменю LoginWorker'ы |
Автор: Stampede 10.8.2007, 20:07 | ||||||
Два замечания. Первое: ты преждевременно заводишь и инициализируешь объект FormData. Если регистрация проходит успешно, то он нам и не понадобится вовсе. Это раз. Второе: трюки с перебором параметров неоправданно загромождают код воркеров, тем более что это дело можно запросто инкапсулировать. Так что все-таки используй конструктор new FormData(request.getParameterMap()), а уж в конструкторе делай всякие переборы с проверками. Далее, вот здесь:
добавь все-таки еще одну строчку:
Это обезопасит тебя от ошибок в шаблоне. И еще парочка предложений для реализации. 1. Текст сообщений об ошибке Сейчас мы вынужденно пишем это сообщение по-английски. А нам бы надо по нашенски. Можно, конечно, сделать по i18n-ному, через ResourceBundle и пр. Но можно и по простому. Например, передавать в качестве мессаджа не текст сообщения, а некую текстовую константу, скажем, "DUPLICATE_NAME" или "PASSWORD_MISMATCH", а в теле шаблона анализировать и выводить соответствующую фразу по-русски. 2. Текст подтверждения Теперь ты уже можешь представить, как сделать так, чтобы на странице подтверждения выводился текст, зависящий от выполненного действия. Подсказка: нужно в сессию положить некий объект, в котором будет содержаться информация о типе действия и адрес линка для перехода. Предлагаю класс для этой инфы разработать самостоятельно. |
Автор: diablero 11.8.2007, 01:43 | ||||||||
Добавил в класс AbstractWorker:
1. Текст сообщений об ошибке Тут выбор на мой взгляд прост, если сайт многоязычный, то проще иметь несколько вариантов шаблонов. Поэтому передаю текстовую константу. 2. Текст подтверждения Сделал так:
|
Автор: Tony 11.8.2007, 10:37 |
Mетод getParameterMap содержимое заменить на @SuppressWarnings("unchecked") public Map<String,String> getParameterMap(){ return request.getParameterMap(); } |
Автор: goodday1941 12.8.2007, 02:08 |
Извиняюсь за оффтоп, но это важно! Сейчас читая эту тему пытаюсь реализовать свой проект (пока до 11 странички добрался с JPA), в общем большая просьба к диаблеро - не править код в предедущих постах а создавать новые посты с исправлениями. Может к 20 страничке ситуация изменилась, но на 11 пока постоянные правки предедущих постов, непонятки получаються ![]() PS... за затею большое спасибо ![]() |
Автор: Stampede 12.8.2007, 05:09 |
Welcome to the club, goodday1941! Приятно видеть, что люди интересуются вопросом. По поводу правки постов - да, я уже несколько раз говорил об этом diablero, вроде ситуация исправилась к лучшему. |
Автор: diablero 12.8.2007, 06:42 |
Исправилась, исправилась ...![]() |
Автор: goodday1941 12.8.2007, 15:10 |
ну еще малек оффтопа.. сейчас с Velocity разбираюсь... данный инструмент оЧень понравился, но что то эта тема слабо раскрытой получилась в ваших топиках и нагуглить я толкового ничего не смог, что самое обидное (пол часа искал либы которые нужно подключить, кстатии в списке либ в приатаченом зип архиве их небыло, и сайт джакарты что то не выдал мне даунлоад ссылку на либы)... в общем то можете подсказать толковых текстовичков по Velocity? заранее благодарен ![]() |
Автор: diablero 12.8.2007, 16:04 |
Ссылка на Velocity: http://velocity.apache.org/index.html. Документации на сайте, на мой взгляд достаточно. Список подправлю. |
Автор: Maksym 13.8.2007, 13:28 |
Stampede Скажи, пожалуйста, знаком ли ты с технологей facelets? Какие преимущества и недостатки предлагаемого здесь подхода против facelt'ных шаблонов? |
Автор: Stampede 14.8.2007, 00:33 |
Нет, не знаком, но поскольку само название Facelets предполагает его связь с "лицами", а "лица" продвигают люди, чей образ веб-программистского мышления был сформирован под влиянием JSP, то скажу честно: я отношусь к Facelets с большим предубеждением. Вот я сейчас глянул обзорную статью http://www.ibm.com/developerworks/java/library/j-facelets/, и что я там увидел? Все тот же корявый синтаксис кастом тэгов, куча обязательных объявлений, невнятная связь шаблона с моделью... Товарищи, вы поймите, кастом тэги - это, по большому счету, всего лишь способ организовать обратные вызовы (callbacks) из шаблона в некие Java-компоненты. Но для этого совсем не обязательно городить такой огород! Maksym, я понимаю, тебя интересует, как у нас будет решен вопрос с компонентностью. Обещаю, очень положительно будет решен ![]() 2 diablero: Посмотрел твой код. В принципе все нормально. Замечания: 1. Метод AbstractWorker.getParameterMap() Я под инкапсуляцией имел в виду немного другое. Понимаешь, воркерам должно быть вообще до балды, какие там проблемы возникают при кастинге параметров. Скорее всего, проблема вообще решается правильным описанием дженериков в классе FormInfo. Но даже если не решается, то можно вручную перебрать все эти параметры в конструкторе FormInfo(Map map), и вот это и будет искомой инкапсуляцией. 2. Класс ConfirmData Я ведь говорил о текстовых константах, а ты используешь числовые. Тогда ты мог бы прямо в коде Velocity сравнивать со строками, например #if($action == "LOGIN_ACTION"), и не надо было бы объявлять их по десять раз. Кроме того, в классе можно было бы предусмотреть переменную типа Object, в которой передавать любой произвольный объект. Например, при подтверждении логина - объект User, а при подтверждении поста - объект Post. Это позволило бы выводить более конкретизированный текст сообщения (например, включающий названия поста), а в качестве ссылки - прописывать соответствующий УРЛ. 3. Воркер ConfirmationWorker И еще одна важная вещь. В классе ConfirmationWorker нужно обязательно добавить код, который подчищает сессию: удаляет атрибуты formData и confirmData. Это нужно для того, чтобы отслеживать ситуации, когда юзер попал на страницу подтверждения неестественным образом: например, по ссылке извне, по кнопкам взад/вперед или через закладку браузера. Соответственно, в шаблоне должна стоять проверка: если переменная $confirmData отсутствует, то говорить юзеру, чтобы он не лез куда не следует, и пернаправлять его на домашнюю страницу. Следующим шагом предлагаю сделать ввод новых постов: УРЛ: /form/post.shtml Воркер: PostFormWorker Шаблон: forms/post.vm | V /submit/post.do SubmitPostWorker А вслед за этим сразу сделаем редактирование постов. И еще, по поводу подразделов. Ты, кажется, собирался ввести для них новую сущность. А что если тебе впоследствии понадобится под-подраздел. А потом под-под-подраздел, и так далее? Нет уж, вот есть у тебя сущность Section, вот в ней и надо сразу заложить иерархичность. Если самостоятельно не получится, будем решать вместе. |
Автор: Maksym 14.8.2007, 18:06 |
Stampede, спасибо за ответ. |
Автор: diablero 14.8.2007, 20:49 | ||
чего-то не находиться решение... Почти доделал. Сдесь нужно сразу привести все в порядок. Я имею ввиду сущность Section, подразделы. Завтра после работы, доэксперементирую, и если понравиться реализация, все запостю. |
Автор: ouks 15.8.2007, 22:55 |
Привет! слежу за вашей темой уже несколько недель. Спасибо за столь полезный топик. Пытаюсь вас догнать. Опыта как такового в java нет. Застрял вот на сервлете, запуская Infinite1[1].1.3.4 второй день. Да только выдает упорно "error page" из шаблона ![]() ок..продолжаю.. попробую еще раз все по пунктам.. эх, как же на пхп все попроще ![]() |
Автор: Shaggie 16.8.2007, 08:02 | ||||
Было такое. Долго боролся. Код MainController.java из Infinite1.1.3.4:
В этом коде, скорее всего, барахлят пути к классам, которые засовываются в mapping. Проверить пути можно так:
Посмотри пути, а потом либо впиши их в mapping, либо надо будет поизвращаться с настройками сервера. Удачи. ![]() |
Автор: goodday1941 16.8.2007, 15:42 |
вопрос в тему... Stampede, предлагал вынести стили в отдельный css файл... собсно вопрос в следующем где его размещать, что прописать в web.xml и какой путь к css файлу прописывать в самой веб страничке? ПС... только познакомился с css... и то что я первым вразумил: деражть всю инфу о стилях в отдельном файле имеет смысл в плане быстродействия, так как css файлы кешируються. Поправте если я не прав ![]() |
Автор: Maksym 16.8.2007, 15:57 | ||
Нормально будет разместить его в папке css, созданной на одном уровне со стартовой страницей. Ничего не нужно. Относительный.
Это так. Но главный плюс css в возможности отделить логические структуры страниц от деталей оформления и централизовать управление внешним видом системы. |