Книга 1 - Начальные сведения
Книга 2 - Более профессиональный подход |
Студенческий отдел кадров Необходимые возможности
Рассматривая код StudentDAO в предыдущих частях вы не
могли не обратить внимание на ситуацию, что "заходя" внутрь методов мы каждый раз
создавали объект типа Session, открывали транзакцию,
производили какие-то действия с базой данных и закрывали транзакцию. Это приходится
делать случае использования метода getCurrentSession.
Чуть ли не единственным положительным моментом здесь было то, что сессия закрывалась
автоматически. Все остальные моменты выглядят не столь радужно. И постоянный вызов
getCurrentSession, beginTransaction, commit. И
невозможность выйти за рамки метода. Ведь в случае, если хотелось сделать несколько
вызывов методов StudentDAO в рамках одной транзакции
мы это сделать просто не могли.
После некоторого отступления (но оно нам тоже понадобится) вернемся к обсуждению проблем,
связанных с работой с Session. Как видим, было бы хорошо
иметь механизм, который позволял бы указывать некоторое стандартное/постоянное/однообразное
действие при вызове каких-либо методов какого-либо класса. (Это не плохой слог, я думал
именно так - каких-либо методов какого-либо класса). Иными словами - было бы здорово,
если бы существовал способ, который автоматически начинал транзакцию и заканчивал ее
(в начале метода и в конце - соответственно). Нам надо только указать в каких методах это
надо сделать - и пусть все происходит автоматически.
Как мы видим, наличие системы, которая могла бы взять на себе такой круг задач, достаточно
востребовано. И как мы знаем, спрос рождает предложение.
Но на момент создания Spring недостатки EJB
были слищком велики и Spring победно зашагал по планете Java.
Библиотека
Для текущего проекта нам понадобятся следующие библиотеки: Описание функциональностиНачнем с того, что опишем необходимые функции, которые нам потребуются для работы с Profession. После этого укажем классы и интерфейсы, которые нам помогут реализовать необходимый набор функций. Данный список не претендует на абсолютную наполненность, но для примера нам и этого хватит. Итак:
С учетом того, что нам нужен класс для реализации бизнес-логики, интерфейс для DAO и хотя бы одна реализация DAo получаем 2 класса и один интерфейс:
ProfessionFacade.java package students.facade; import students.view.ProfessionView; import java.io.Serializable; import java.util.LinkedList; import java.util.List; import students.dao.ProfessionDAO; import students.entity.Profession; public class ProfessionFacade { private ProfessionDAO dao; public void setDao(ProfessionDAO dao) { this.dao = dao; } public Serializable addProfession(ProfessionView pv) { return dao.addProfession(createProfessionFromView(pv)); } public void updateProfession(ProfessionView pv) { dao.updateProfession(createProfessionFromView(pv)); } public void deleteProfession(ProfessionView pv) { dao.deleteProfession(createProfessionFromView(pv)); } public List<ProfessionView> findProfession() { List<ProfessionView> pvList = new LinkedList<ProfessionView>(); List<Profession> pList = dao.findProfession(); for(Profession p : pList) { pvList.add(new ProfessionView(p)); } return pvList; } private Profession createProfessionFromView(ProfessionView pv) { Profession p = new Profession(); p.setProfessionId(pv.getProfessionId()); p.setProfessionName(pv.getProfessionName()); return p; } } ProfessionDAO.java package students.dao; import java.io.Serializable; import java.util.List; import students.entity.Profession; public interface ProfessionDAO { public Serializable addProfession(Profession p); public void updateProfession(Profession p); public void deleteProfession(Profession p); public List<Profession> findProfession(); } ProfessionDAOImpl.java package students.dao; import java.io.Serializable; import java.util.LinkedList; import java.util.List; import students.entity.Profession; public class ProfessionDAOImpl implements ProfessionDAO { public Serializable addProfession(Profession p) { System.out.println("addProfession called"); return null; } public void updateProfession(Profession p) { System.out.println("updateProfession called"); } public void deleteProfession(Profession p) { System.out.println("deleteProfession called"); } public List<Profession> findProfession() { System.out.println("findProfession called"); List<Profession> list = new LinkedList<Profession>(); list.add(new Profession()); return list; } } ProfessionDAOImpl2.java package students.dao; import java.io.Serializable; import java.util.LinkedList; import java.util.List; import students.entity.Profession; public class ProfessionDAOImpl2 implements ProfessionDAO { public Serializable addProfession(Profession p) { System.out.println("addProfession called for 2"); return null; } public void updateProfession(Profession p) { System.out.println("updateProfession called for 2"); } public void deleteProfession(Profession p) { System.out.println("deleteProfession called for 2"); } public List<Profession> findProfession() { System.out.println("findProfession called for 2"); List<Profession> list = new LinkedList<Profession>(); list.add(new Profession()); return list; } } Profession.java package students.entity; public class Profession { private Long professionId; private String professionName; public Long getProfessionId() { return professionId; } public void setProfessionId(Long professionId) { this.professionId = professionId; } public String getProfessionName() { return professionName; } public void setProfessionName(String professionName) { this.professionName = professionName; } } ProfessionView.java package students.view; import students.entity.Profession; public class ProfessionView { private Long professionId; private String professionName; public ProfessionView() { } public ProfessionView(Profession p) { this.professionId = p.getProfessionId(); this.professionName = p.getProfessionName(); } public Long getProfessionId() { return professionId; } public void setProfessionId(Long professionId) { this.professionId = professionId; } public String getProfessionName() { return professionName; } public void setProfessionName(String professionName) { this.professionName = professionName; } } Единственный момент, на который я прошу обратить ваше внимание - поле в классе ProfessionFacade - dao. Оно имеет тип, который у нас является интерфейсом - ProfessionDAO. А реализации я сделал аж две штуки - ProfessionDAOImpl и ProfessionDAOImpl2. Чуть позже мы увидим насколько просто нам заменить одну реализацию на другую - исходный код не изменится. Его даже перекомпилировать не придется. Inversion of ControlПока мы не будем создавать код для работы с базой данных и применения аспектов. Усложним в соответствующее время. Здесь же рассмотрим только IoC-часть. И даже здесь мы рассмотрим очень-очень-очень простой пример. Если вас заинтересуют возможности Spring для инициализации - обращайтесь к документации к пакету - если вы читаете на английском, то лучше документации я не знаю. Можно конечно говорить, что где-то примеры интереснее или объяснение понятнее, но вряд ли где-то вы найдете более полное описание. Так что садитесь ... и читайте. Но в другой раз. А пока мы посмотрим каким образом можно загрузить сразу набор объектов для нашей несложной задачи. Если рассматривать грубо, то Spring внутрь своего контекста загружает все необходимое и вы можете получить это по имени. Получается что-то вроде контейнера, внутри которого "живут" объекты, указанные в конфигурации.
Наиболее важным в данном случае являются два момента: <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean name="professionFacade" class="students.facade.ProfessionFacade"> <property name="dao" ref="professionDAO_1"/> </bean> <bean name="professionDAO_1" class="students.dao.ProfessionDAOImpl"> </bean> <bean name="professionDAO_2" class="students.dao.ProfessionDAOImpl2"> </bean> </beans>
Здесь все достаточно несложно - я описываю объект (bean) с
определенным именем, по которому я смогу получить его из контекста Spring. package students; import org.springframework.context.support.FileSystemXmlApplicationContext; import students.facade.ProfessionFacade; import students.view.ProfessionView; public class Main { public static void main(String[] args) { FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext(new String[]{"src/StudentExample.xml"}); ProfessionFacade pf = (ProfessionFacade)context.getBean("professionFacade"); ProfessionView pv = new ProfessionView(); pf.addProfession(pv); pf.updateProfession(pv); pf.deleteProfession(pv); pf.findProfession(); } }
Наш самый первый вызов - new FileSystemXmlApplicationContext. В качестве
параметра мы передаем список файлов, в которых прописана конфигурация. Как вы уже догадались, файлов
может быть несколько - если конечно вам это надо. Ради эксперимента вы можете сделать два свойства у класса ProfessionFacade - для двух DAO. Одному присвоить значение professionDAO_1, а второму professionDAO_2. Дальше читайте документацию и экспериментируйте с нашим примером. А мы перейдем к следующей возможности Spring - AOP. Aspect Oriented ProgrammingВ реализации Spring вы можете создать специальные классы, которые называются интерсепторы (interceptor). Их задача - сделать что-то вроде обертки вокруг вызова метода. Когда вы посмотрите на код нашего примера - вы достаточно его легко поймете. ProfessionInterceptor.java package students.dao; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; public class ProfessionInterceptor implements MethodInterceptor { public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("Before: invocation=[" + invocation + "]"); Object rval = invocation.proceed(); System.out.println("Invocation returned"); return rval; } } Как видите я не стал много придумывать - просто сделал два вывода на экран - до и после метода Object rval = invocation.proceed();. Если внимательно посмотреть в документации, что из себя представляет класс MethodInvocation, то можно найти у него метод getMethod, который в свою очередь возвращает объект типа java.lang.reflect.Method. А уж этот стандартный класс дает полную информацию о том, что и как в нашем методе происходит. Теперь давайте обратим внимание каким образом мы конфигурируем наши классы в файле Spring. Смотрим обновленный StudentExample.xml. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean name="professionFacade" class="students.facade.ProfessionFacade"> <property name="dao" ref="professionDAO"/> </bean> <bean name="professionDAO" class="students.dao.ProfessionDAOImpl"> </bean> <bean name="professionInterceptor" class="students.dao.ProfessionInterceptor"> </bean> <bean name="professionFacadeProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target" ref="professionFacade"/> <property name="interceptorNames"> <list> <value>professionInterceptor</value> </list> </property> </bean> </beans> Я убрал второй DAO из нашего файла, но добавил более сложные вещи. Первым я описал наш класс интресептора - students.dao.ProfessionInterceptor. Его код мы только что посмотрели. Далее я ввел специальный класс - professionFacadeProxy. По сути он представляет из себя копию методов того бина, который мы указали в качестве target - а именно professionFacade. В двух словах: Spring "на лету" создает свой собственный класс, который унаследует все методы своего target, но к тому же при вызове какого-либо метода "протаскивает" этот вызов через все интерсепторы, которые зарегистрированы. В нашем случае он всего один, но ради эксперимента можно скопировать строку дважды (трижды, ...) <value>professionInterceptor</value> и посмотреть на результат. Осталось привести пример вызова нашего нового бина - professionFacadeProxy. package students; import org.springframework.context.support.FileSystemXmlApplicationContext; import students.facade.ProfessionFacade; import students.view.ProfessionView; public class Main { public static void main(String[] args) { FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext( new String[]{"src/StudentExample.xml"}); ProfessionFacade pf = (ProfessionFacade)context.getBean("professionFacadeProxy"); ProfessionView pv = new ProfessionView(); pf.addProfession(pv); pf.updateProfession(pv); pf.deleteProfession(pv); pf.findProfession(); } } Пример находится в проекте Spring_02. Здесь важно отметить два момента:
Работа с базами данныхКак мы видели раньше, Spring предоставляет нам практически все необходимое. Конечно же мы рассмотрели достаточно простой вариант работы интерсептора. Он может быть гораздо сложнее - мы можем указать методы, которые мы хотим "окружить заботой", можем указать не один, а несколько интерсепторов и т.д. Но опять повторюсь - данная статья не претендует на роль глубокого исследования возможностей Spring - я хочу показать основные возможности. Если немного порассуждать, то мы придем к следующим выводам: нам нужен специальный интерсептор, который будет начинать транзакцию. Причем эта транзакция должна быть настраиваемая. Также нам нужен специальный класс, который умел бы работать с базой данных и одновременно умел бы использовать уже открытую транзакцию (которую нам открыл интерсептор). Еще лучше, если бы класс для работы с базой данных был совместим с широко распространенными пакетами типа Hibernate или TopLink. И вы наверняка не удивитесь, когда узнаете, что Spring имеет все необходимое. Начнем мы с кода нашего главного класса - Main.java package students; import org.springframework.context.support.FileSystemXmlApplicationContext; import students.facade.ProfessionFacade; import students.view.ProfessionView; public class Main { public static void main(String[] args) { FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext( new String[]{"src/StudentExample.xml", "src/StudentDatabase.xml"}); ProfessionFacade pf = (ProfessionFacade)context.getBean("professionFacade"); ProfessionView pv = new ProfessionView(); pv.setProfessionName("Java Developer"); Long id = pf.addProfession(pv); pv.setProfessionId(id); pf.updateProfession(pv); pf.deleteProfession(pv); pf.findProfession(); } } Как вы можете видеть мы внесли несколько изменений, главным из которых является добавление еще одного конфигурационного файла - src/StudentDatabase.xml. Он гораздо интереснее, чем код класса Main. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean name="studentDS" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:10001/db_applicant" /> <property name="username" value="root" /> <property name="password" value="root" /> </bean> <bean name="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean"> <property name="dataSource" ref="studentDS" /> <property name="annotatedClasses"> <list> <value>students.entity.Profession</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQLInnoDBDialect hibernate.show_sql=true </value> </property> </bean> <bean name="txManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="sessionFactory" /> </bean> <bean name="abstractTransactionProxy" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" abstract="true"> <property name="transactionManager" ref="txManager" /> <property name="transactionAttributes"> <props> <prop key="find*">PROPAGATION_REQUIRED, readOnly </prop> <prop key="get*">PROPAGATION_REQUIRED, readOnly </prop> <prop key="add*">PROPAGATION_REQUIRED,-Exception </prop> <prop key="update*">PROPAGATION_REQUIRED,-Exception </prop> <prop key="delete*">PROPAGATION_REQUIRED,-Exception </prop> </props> </property> </bean> <bean name="hibernateTemplate" class="org.springframework.orm.hibernate3.HibernateTemplate"> <property name="sessionFactory" ref="sessionFactory" /> </bean> </beans> Чуть позже мы рассмотрим схему взаимодействия всех наших классов. А пока рассмотрим все элементы по порядку:
Теперь в коде нашего ProfessionDAO мы используем свойство template для выполнения запросов к базе данных. Это свойство использует уже созданные бином abstractTransactionProxy транзакцию и сессию Hibernate. Осталось только более внимательно посмотреть на бин sessionFactory. Думаю, что многие из свойств этого бина вам достаточно очевидны - мы используем dataSource, который определен - это параметры нашего коннекта. Также у нас есть список entity классов - в данном случае это students.entity.Profession. Ну и под конец у нас есть параметры для подключения к MySQL - hibernateProperties. Думаю, что еще немного внимательно посмотрев на все бины вы сложите себе картинку, как это все работает. Несколько слов надо посвятить второму файлу конфигурации - StudentExample.xml. <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd"> <bean name="professionDAO" class="students.dao.ProfessionDAOImpl"> <property name="template" ref="hibernateTemplate" /> </bean> <bean name="professionFacade" parent="abstractTransactionProxy"> <property name="target"> <bean class="students.facade.ProfessionFacade"> <property name="dao" ref="professionDAO" /> </bean> </property> </bean> </beans> Здесь надо обратить внимание на следующий момент - конфигурация бина professionFacade. Во-первых в нем мы видим атрибут parent, в котором находится наш важный бин-посредник abstractTransactionProxy. И второй момент - мы инициализовали свойство dao для класса students.facade.ProfessionFacade сразу внутри описания. Вот в принципе и все. Остальной код для запуска есть в архиве (пример находится в проекте Spring_03) и я советую вам быстрее скачивать его, запускать и исследовать новые возможности, о которых вы узнали. В очередной раз призываю вас почитать документацию к Spring - это очень полезное занятие. В данной статье мы только прикоснулись к пакету, смогли понять самое начало. Дорогу осилит идущий. А теперь мы переходим к еще одной возможности Spring - шаблону MVC для Web. Добро пожаловать в Часть 20 - Spring. Переходим на Web. Более современный Spring
В исходник включен более современный вариант создания прокси вокруг фасада - его можно посмотреть
в примере Spring_03a. Попробуйте разобраться в нем сами - там изменены только файлы конфигурации.
Рассматривайте это в качестве самостоятельной работы. Единственное замечание - там потребуется еще одна
библиотека - aspectjweaver.jar. Архив с исходными кодами: Исходный код |