Статьи

Магия гобеленов: общие объекты доступа к данным

С помощью современных ORM можно легко создавать общие DAO. Все, что нам нужно от IOC, — это возможность различать две службы (реализующие один и тот же интерфейс) по их универсальному типу. Проблема заключается в том, что tapestry-ioc может различать две службы на основе идентификатора службы или аннотации маркера, а не по универсальному типу. Итак, если у нас есть общая реализация DAO, мы не можем сделать что-то вроде

@Inject
private EntityDAO<User> userDAO;

(Обратите внимание, я использую термин EntityDAO для общего дао.) Одним из решений будет что-то вроде

@InjectDAO(User.class)
private EntityDAO<User> userDAO;

Еще один может быть

@ServiceId("UserDAO")
@Inject
private EntityDAO<User> userDAO;

Последнее может быть реализовано, если мы сможем представить наш DAO как сервис. Первый может быть реализован в терминах последнего с использованием преобразований классов.

Второй вопрос — как зарегистрировать DAO для каждого объекта. Это можно сделать вручную, как

static public EntityDAO<User> buildUserDAO(){
return new EntityDAOImpl(User.class);
}

Но представьте, что вы делаете это для базы данных с сотнями сущностей (особенно, если вы ленивый пользователь гобелена). Решением будет загрузка объектов при запуске и создание соответствующих DAO. Это было бы замечательно, но гобелен не обеспечивает динамичный способ предоставления новых услуг. Да, вы можете добавить ObjectProvider в MasterObjectProvider, но с этим вы создаете объект, а не сервис. Некоторые из функций, которые вы теряете, — это возможность напрямую применять советы к вашим услугам и невозможность использования аннотации @InjectService.

Итак, вот трюк для динамического создания сервиса. Мы создаем новое определение модуля, которое принимает список определений сущности в качестве аргумента и создает определение сервиса для каждого. Определение сервиса не создает экземпляр сервиса, а делегирует его другому сервису EntityDAOSource. Это может быть сервис по построению цепочек, в который вы можете легко добавлять услуги.

Итак, вот наше определение модуля

public class EntityModuleDef implements ModuleDef {
private Map<String, ServiceDef> serviceDefs = new HashMap<String, ServiceDef>();

public EntityModuleDef(EntityLocator locator){
for(EntityDef entityDef: locator.getEntityDefs()){
serviceDefs.put(entityDef.getServiceId(), new EntityDAOServiceDef(entityDef));
}
}

public Set<String> getServiceIds() {
return serviceDefs.keySet();
}

public ServiceDef getServiceDef(String serviceId) {
return serviceDefs.get(serviceId);
}

public Set<DecoratorDef> getDecoratorDefs() {
return CollectionFactory.newSet();
}

public Set<ContributionDef> getContributionDefs() {
return CollectionFactory.newSet();
}

@SuppressWarnings("rawtypes")
public Class getBuilderClass() {
return null;
}

public String getLoggerName() {
return EntityModuleDef.class.getName();
}

}

Интерфейс EntityLocator предоставляет определения сущностей.

//Entity definition
public interface EntityDef {

//Get the service id
String getServiceId();

//Get the entity type
Class<?> getType();

}

public interface EntityLocator {
//Gets the list of entity definitions
Set<EntityDef> getEntityDefs();
}

Простая реализация EntityLocator будет:

public abstract class SimpleEntityLocator implements EntityLocator {
private Set<EntityDef> entityDefs = new HashSet<EntityDef>();

public AbstractEntityLocator(Set<String> packageNames){
ClassNameLocator locator = new ClassNameLocatorImpl(new ClasspathURLConverterImpl());
for(String packageName: packageNames){
for(String className: locator.locateClassNames(packageName)){
try {
final Class<?> entityClass = Class.forName(className);
if(isEntity(entityClass)){
entityDefs.add(new EntityDef(){

public String getServiceId() {
return entityClass.getSimpleName() + "DAO";
}

public Class<?> getType() {
return entityClass;
}

@Override
public String toString(){
return "Entity Definition for " + getServiceId();
}

});
}
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
}
}

public boolean isEntity(Class<?> entityClass){
return entityClass.getAnnotation(javax.persistence.Entity.class) != null ||
entityClass.getAnnotation(javax.persistence.MappedSuperClass.class) != null;
}

public Set<EntityDef> getEntityDefs() {
return entityDefs;
}

}

Определение сервиса

public class EntityDAOServiceDef implements ServiceDef {

private EntityDef entityDef;

public EntityDAOServiceDef(EntityDef entityDef) {
this.entityDef = entityDef;
}

public ObjectCreator createServiceCreator(final ServiceBuilderResources resources) {
return new ObjectCreator() {

public Object createObject() {
Object object = resources.getService(EntityDAOSource.class).get(entityDef.getType());

if(object == null){
throw new EntityDAONotFoundException(
"Could not find EntityDAO implementation for " + entityDef.getType());
}
return object;
}

};
}

public String getServiceId() {
return entityDef.getServiceId();
}

@SuppressWarnings("rawtypes")
public Set<Class> getMarkers() {
return CollectionFactory.newSet();
}

@SuppressWarnings("rawtypes")
public Class getServiceInterface() {
return EntityDAO.class;
}

public String getServiceScope() {
return ScopeConstants.DEFAULT;
}

public boolean isEagerLoad() {
return false;
}

}

Определение сервиса делает все, кроме фактического создания сервиса, который делегирован EntityDAOSource.

В качестве примера рассмотрим общий DAO, реализованный в Hibernate.

public interface EntityDAO<E> {
List<E> list();
void save(E entity);
void saveOrUpdate(E entity);
void update(E entity);
void remove(E entity);
int count();
E find(Serializable id);
}

public class HibernateEntityDAOImpl<T> implements EntityDAO<T> {
private Class<?> type;

public HibernateEntityDAOImpl(Class<T> type) {
this.type = type;
}

protected Session getSession() {
return //Get current session...
}

public T find(Serializable id) {
return (T) getSession().get(type, id);
}

public void save(T entity) {
getSession().save(entity);
}

public void update(T entity) {
getSession().update(entity);
}

public void remove(T entity) {
getSession().delete(entity);
}

public void saveOrUpdate(T entity) {
getSession().saveOrUpdate(entity);
}

public List<T> list(){
return (List<T>)getSession().createCriteria(type).query();
}

public int count(){
return (int)getSession().createCriteria(type).setProjection(Projections.rowCount()).uniqueResult();
}

}

Для этой реализации EntityDAOSource может быть реализован как

public class HibernateEntityDAOSource implements EntityDAOSource {
public <E> EntityDAO<E> get(Class<E> entityClass) {
return new HibernateEntityDAOImpl<E>(type);
}
}

Наконец, это определение модуля может быть передано путем переопределения метода provideExtraModuleDefs () в TapestryFilter.

public class TapestryTawusFilter extends TapestryFilter {
private static final String MODEL_PACKAGES = "tawus-model-packages";

@Override
protected ModuleDef [] provideExtraModuleDefs(ServletContext context){
String packages = context.getInitParameter(MODEL_PACKAGES);
if(packages == null){
return new ModuleDef[]{};
}

EntityLocator entityLocator = new AbstractEntityLocator(CollectionFactory.newSet(
TapestryInternalUtils.splitAtCommas(packages))){
@SuppressWarnings("unchecked")
public boolean isEntity(@SuppressWarnings("rawtypes") Class entityType){
return entityType.getAnnotation(Entity.class) != null;
}
};

return new ModuleDef[]{ new EntityModuleDef(entityLocator)};
}
}

От http://tawus.wordpress.com/2011/05/28/tapestry-magic-13-generic-data-access-objects/