1概要
このチュートリアルでは、Beanポストプロセッサを使用した カスタムSpring注釈を実装します 。
それでこれはどのように役立ちますか?簡単に言うと、同じタイプの複数の類似したBeanを作成しなくても、同じBeanを再利用できます。
DAOの実装については、単純なプロジェクトで行います。それらすべてを単一の柔軟な GenericDao に置き換えます。
2 Maven
これを機能させるには、 spring-core 、 spring-aop 、および spring-context-support JARが必要です。 pom.xml で spring-context-support を宣言するだけです。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>4.2.2.RELEASE</version>
</dependency>
Springの依存関係のもっと新しいバージョンを探したいのなら - https://search.maven.org/classic/#search%7Cgav%7C1%7Cg%3A%22org.springframework%22%20AND%20a%3A %22spring-context-support%22[The mavenリポジトリ]。
** 3新ジェネリックDAO
**
ほとんどのSpring/JPA/Hibernate実装は標準DAOを使用します - 通常は各エンティティに1つです。
そのソリューションを GenericDao に置き換えます。代わりにカスタム注釈プロセッサを作成し、その GenericDao 実装を使用します。
3.1. 一般的な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) {
//...
}
}
実際のシナリオでは、もちろんhttp://docs.oracle.com/javaee/7/api/javax/persistence/PersistenceContext.html[PersistenceContext]に接続して、実際にこれらのメソッドの実装を提供する必要があります。 。今のところ - これはできるだけ単純にします。
それでは、カスタムインジェクション用のアノテーションを作成しましょう。
3.2. データアクセス
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Documented
public @interface DataAccess {
Class<?> entity();
}
上記のアノテーションを使用して、以下のように GenericDao を挿入します。
@DataAccess(entity=Person.class)
private GenericDao<Person> personDao;
「Springは DataAccess アノテーションをどのように認識しますか?」と尋ねる人もいるでしょう。そうではありません - デフォルトではありません。
しかし、カスタム BeanPostProcessor - を介してアノテーションを認識するようにSpringに伝えることができます次にこれを実装しましょう。
3.3. DataAccessAnnotationProcessor
@Component
public class DataAccessAnnotationProcessor implements BeanPostProcessor {
private ConfigurableListableBeanFactory configurableBeanFactory;
@Autowired
public DataAccessAnnotationProcessor(ConfigurableListableBeanFactory beanFactory) {
this.configurableBeanFactory = beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
this.scanDataAccessAnnotation(bean, beanName);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
protected void scanDataAccessAnnotation(Object bean, String beanName) {
this.configureFieldInjection(bean);
}
private void configureFieldInjection(Object bean) {
Class<?> managedBeanClass = bean.getClass();
FieldCallback fieldCallback =
new DataAccessFieldCallback(configurableBeanFactory, bean);
ReflectionUtils.doWithFields(managedBeanClass, fieldCallback);
}
}
次に - 先ほど使用した DataAccessFieldCallback の実装です。
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;
}
@Override
public void doWith(Field field)
throws IllegalArgumentException, IllegalAccessException {
if (!field.isAnnotationPresent(DataAccess.class)) {
return;
}
ReflectionUtils.makeAccessible(field);
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 {
logger.warn(WARN__NON__GENERIC__VALUE);
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);
logger.info(
"Bean named '{}' already exists used as current bean reference.", beanName);
}
return daoInstance;
}
}
今 - それはかなりの実装です - しかしそれの最も重要な部分は doWith() メソッドです:
genericDaoInstance = configurableBeanFactory.initializeBean(beanToRegister, beanName);
configurableBeanFactory.autowireBeanProperties(genericDaoInstance, autowireMode, true);
configurableBeanFactory.registerSingleton(beanName, genericDaoInstance);
これにより、Springは @ DataAccess アノテーションを介して実行時に注入されたオブジェクトに基づいてBeanを初期化するようになります。
beanName を使用すると、Beanの一意のインスタンスを確実に取得できます。この場合、 @ DataAccess アノテーションを介して挿入されたエンティティに応じて GenericDao の単一のオブジェクトを作成する必要があるためです。
最後に、次にこの新しいBeanプロセッサをSpring構成で使用しましょう。
3.5. CustomAnnotationConfiguration
@Configuration
@ComponentScan("com.baeldung.springcustomannotation")
public class CustomAnnotationConfiguration {}
ここで重要なのは、 @ComponentScan アノテーションの値には、カスタムBeanポストプロセッサが配置されているパッケージを指定し、実行時にSpringによってスキャンおよび自動配線されたことを確認します。
** 4新しいDAOをテストする
**
ここでは、Spring対応のテストと、2つの簡単なエンティティクラスの例( Person と Account )から始めましょう。
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={CustomAnnotationConfiguration.class})
public class DataAccessAnnotationTest {
@DataAccess(entity=Person.class)
private GenericDao<Person> personGenericDao;
@DataAccess(entity=Account.class)
private GenericDao<Account> accountGenericDao;
@DataAccess(entity=Person.class)
private GenericDao<Person> anotherPersonGenericDao;
...
}
DataAccess アノテーションを使用して GenericDao のいくつかのインスタンスを注入しています。新しいBeanが正しくインジェクトされていることをテストするには、次のことを説明する必要があります。
-
注射が成功したら
-
同じエンティティを持つBeanインスタンスが同じ場合
-
GenericDao のメソッドが実際に期待通りに機能する場合
ポイント1は、実際にはSpring自体によってカバーされています - Beanが接続できない場合、フレームワークは非常に早く例外をスローします。
ポイント2をテストするには、両方とも Person クラスを使用する2つの GenericDao のインスタンスを確認する必要があります。
@Test
public void whenGenericDaoInjected__thenItIsSingleton() {
assertThat(personGenericDao, not(sameInstance(accountGenericDao)));
assertThat(personGenericDao, not(equalTo(accountGenericDao)));
assertThat(personGenericDao, sameInstance(anotherPersonGenericDao));
}
personGenericDao を accountGenericDao に等しくしたくないのです。
しかし、 personGenericDao と anotherPersonGenericDao をまったく同じインスタンスにしたいのです。
ポイント3をテストするために、ここで単純な持続性関連ロジックをテストします。
@Test
public void whenFindAll__thenMessagesIsCorrect() {
personGenericDao.findAll();
assertThat(personGenericDao.getMessage(),
is("Would create findAll query from Person"));
accountGenericDao.findAll();
assertThat(accountGenericDao.getMessage(),
is("Would create findAll query from Account"));
}
@Test
public void whenPersist__thenMessagesIsCorrect() {
personGenericDao.persist(new Person());
assertThat(personGenericDao.getMessage(),
is("Would create persist query from Person"));
accountGenericDao.persist(new Account());
assertThat(accountGenericDao.getMessage(),
is("Would create persist query from Account"));
}