A Spring Custom Annotation for a Better DAO – 用于更好的DAO的Spring自定义注释

最后修改: 2015年 10月 21日


1. Overview


In this tutorial, we’ll implement a custom Spring annotation with a bean post-processor.


So how does this help? Simply put – we can reuse the same bean instead of having to create multiple, similar beans of the same type.


We’ll do that for the DAO implementations in a simple project – replacing all of them with a single, flexible GenericDao.


2. Maven


We need spring-core, spring-aop, and spring-context-support JARs to get this working. We can just declare spring-context-support in our pom.xml.

我们需要spring-corespring-aopspring-context-support JARs来实现工作。我们可以在我们的pom.xml中声明spring-context-support


If you want to go for a newer version of the Spring dependency – check out the maven repository.


3. New Generic DAO


Most Spring / JPA / Hibernate implementation use the standard DAO – usually one for each entity.

大多数Spring / JPA / Hibernate的实现都使用标准的DAO–通常每个实体都有一个。

We’re going to be replacing that solution with a GenericDao; we’re going to write a custom annotation processor instead and use that GenericDao implementation:


3.1. Generic DAO


public class GenericDao<E> {

    private Class<E> entityClass;

    public GenericDao(Class<E> entityClass) {
        this.entityClass = entityClass;

    public List<E> findAll() {
        // ...

    public Optional<E> persist(E toPersist) {
        // ...

In a real world scenario, you’ll of course need to wire in a PersistenceContext and actually provide the implementations of these methods. For now – we’ll make this as simple as possible.


Now, lets create annotation for custom injection.


3.2. Data Access


@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
public @interface DataAccess {
    Class<?> entity();

We’ll use the annotation above to inject a GenericDao as follows:


private GenericDao<Person> personDao;

Maybe some of you asking, “How does Spring recognize our DataAccess annotation?”. It doesn’t – not by default.


But we could tell Spring to recognize the annotation via a custom BeanPostProcessor – let’s get this implemented next.


3.3. DataAccessAnnotationProcessor


public class DataAccessAnnotationProcessor implements BeanPostProcessor {

    private ConfigurableListableBeanFactory configurableBeanFactory;

    public DataAccessAnnotationProcessor(ConfigurableListableBeanFactory beanFactory) {
        this.configurableBeanFactory = beanFactory;

    public Object postProcessBeforeInitialization(Object bean, String beanName) 
      throws BeansException {
        this.scanDataAccessAnnotation(bean, beanName);
        return bean;

    public Object postProcessAfterInitialization(Object bean, String beanName) 
      throws BeansException {
        return bean;

    protected void scanDataAccessAnnotation(Object bean, String beanName) {

    private void configureFieldInjection(Object bean) {
        Class<?> managedBeanClass = bean.getClass();
        FieldCallback fieldCallback = 
          new DataAccessFieldCallback(configurableBeanFactory, bean);
        ReflectionUtils.doWithFields(managedBeanClass, fieldCallback);

Next – here’s the implementation of the DataAccessFieldCallback we just used:


3.4. DataAccessFieldCallback


public class DataAccessFieldCallback implements FieldCallback {
    private static Logger logger = LoggerFactory.getLogger(DataAccessFieldCallback.class);
    private static int AUTOWIRE_MODE = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;

    private static String ERROR_ENTITY_VALUE_NOT_SAME = "@DataAccess(entity) "
            + "value should have same type with injected generic type.";
    private static String WARN_NON_GENERIC_VALUE = "@DataAccess annotation assigned "
            + "to raw (non-generic) declaration. This will make your code less type-safe.";
    private static String ERROR_CREATE_INSTANCE = "Cannot create instance of "
            + "type '{}' or instance creation is failed because: {}";

    private ConfigurableListableBeanFactory configurableBeanFactory;
    private Object bean;

    public DataAccessFieldCallback(ConfigurableListableBeanFactory bf, Object bean) {
        configurableBeanFactory = bf;
        this.bean = bean;

    public void doWith(Field field) 
    throws IllegalArgumentException, IllegalAccessException {
        if (!field.isAnnotationPresent(DataAccess.class)) {
        Type fieldGenericType = field.getGenericType();
        // In this example, get actual "GenericDAO' type.
        Class<?> generic = field.getType(); 
        Class<?> classValue = field.getDeclaredAnnotation(DataAccess.class).entity();

        if (genericTypeIsValid(classValue, fieldGenericType)) {
            String beanName = classValue.getSimpleName() + generic.getSimpleName();
            Object beanInstance = getBeanInstance(beanName, generic, classValue);
            field.set(bean, beanInstance);
        } else {
            throw new IllegalArgumentException(ERROR_ENTITY_VALUE_NOT_SAME);

    public boolean genericTypeIsValid(Class<?> clazz, Type field) {
        if (field instanceof ParameterizedType) {
            ParameterizedType parameterizedType = (ParameterizedType) field;
            Type type = parameterizedType.getActualTypeArguments()[0];

            return type.equals(clazz);
        } else {
            return true;

    public Object getBeanInstance(
      String beanName, Class<?> genericClass, Class<?> paramClass) {
        Object daoInstance = null;
        if (!configurableBeanFactory.containsBean(beanName)) {
            logger.info("Creating new DataAccess bean named '{}'.", beanName);

            Object toRegister = null;
            try {
                Constructor<?> ctr = genericClass.getConstructor(Class.class);
                toRegister = ctr.newInstance(paramClass);
            } catch (Exception e) {
                logger.error(ERROR_CREATE_INSTANCE, genericClass.getTypeName(), e);
                throw new RuntimeException(e);
            daoInstance = configurableBeanFactory.initializeBean(toRegister, beanName);
            configurableBeanFactory.autowireBeanProperties(daoInstance, AUTOWIRE_MODE, true);
            configurableBeanFactory.registerSingleton(beanName, daoInstance);
            logger.info("Bean named '{}' created successfully.", beanName);
        } else {
            daoInstance = configurableBeanFactory.getBean(beanName);
              "Bean named '{}' already exists used as current bean reference.", beanName);
        return daoInstance;

Now – that’s quite an implementation – but the most important part of it is the doWith() method:


genericDaoInstance = configurableBeanFactory.initializeBean(beanToRegister, beanName);
configurableBeanFactory.autowireBeanProperties(genericDaoInstance, autowireMode, true);
configurableBeanFactory.registerSingleton(beanName, genericDaoInstance);

This would tell Spring to initialize a bean based on the object injected at runtime via the @DataAccess annotation.


The beanName will make sure that we’ll get an unique instance of the bean because – in this case – we do want to create single object of GenericDao depending on the entity injected via the @DataAccess annotation.


Finally, let’s use this new bean processor in a Spring configuration next.


3.5. CustomAnnotationConfiguration


public class CustomAnnotationConfiguration {}

One thing that important here is that, value of the @ComponentScan annotation needs to point to the package where our custom bean post processor is located and make sure that it scanned and autowired by Spring at runtime.

这里很重要的一点是,@ComponentScan注解需要指向我们的自定义Bean post处理器所在的包,并确保它在运行时被Spring扫描和自动连接。

4. Testing the New DAO


Let’s start with a Spring enabled test and two simple example entity classes here – Person and Account.


public class DataAccessAnnotationTest {

    private GenericDao<Person> personGenericDao;
    private GenericDao<Account> accountGenericDao;
    private GenericDao<Person> anotherPersonGenericDao;


We’re injecting a few instances of the GenericDao with the help of the DataAccess annotation. To test that the new beans are correctly injected, we’ll need to cover:


  1. If injection is successful
  2. If bean instances with the same entity are the same
  3. If the methods in the GenericDao actually work as expected

Point 1 is actually covered by Spring itself – as the framework throws an exception quite early if a bean cannot be wired in.


To test point 2, we need to look at the 2 instances of the GenericDao that both use the Person class:


public void whenGenericDaoInjected_thenItIsSingleton() {
    assertThat(personGenericDao, not(sameInstance(accountGenericDao)));
    assertThat(personGenericDao, not(equalTo(accountGenericDao)));
    assertThat(personGenericDao, sameInstance(anotherPersonGenericDao));

We don’t want personGenericDao to be equal to the accountGenericDao.


But we do want the personGenericDao and anotherPersonGenericDao to be exactly the same instance.


To test point 3, we just test some simple persistence related logic here:


public void whenFindAll_thenMessagesIsCorrect() {
      is("Would create findAll query from Person"));

      is("Would create findAll query from Account"));

public void whenPersist_thenMessagesIsCorrect() {
    personGenericDao.persist(new Person());
      is("Would create persist query from Person"));

    accountGenericDao.persist(new Account());
      is("Would create persist query from Account"));

5. Conclusion


In this article we did a very cool implementation of a custom annotation in Spring – along with a BeanPostProcessor. The overall goal was to get rid of the multiple DAO implementations we usually have in our persistence layer and use a nice, simple generic implementation without loosing anything in the process.


The implementation of all these examples and code snippets can be found in my GitHub project – this is an Eclipse based project, so it should be easy to import and run as-is.

所有这些例子和代码片段的实现都可以在我的GitHub项目中找到 – 这是一个基于Eclipse的项目,所以应该很容易导入并按原样运行。