Create a custom annotation to set a bunch of other annotation - java

I have some repeated annotations in multiple classes. It looks like this -
#Configuration
#Data
#Slf4j
#ConfigurationProperties(prefix = "hello")
#PropertySource(value = "classpath:hello.yml", factory = YamlSourceFactory.class)`
I would like to combine and make a single Annotation class out of it.
I want to write something like this -
#Configuration
#Data
#Documented
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#PropertySource(value = this.value, factory = this.factory)
#ConfigurationProperties(prefix = this.key)
public #interface CustomAnnotation {
String name() default "";
String[] key();
String[] value();
Class<? extends PropertySourceFactory> factory() default YamlSourceFactory.class;
}
so that in all my other files I can write only one line and it is taken care of by my Custom Annotation -
#CustomAnnotation(key = "hello", value = "classpath:hello.yml")
Is this something possible to achieve ?

I faced similar problem and this solution works for me:
This is the case:
package com.something.something;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.Scope;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Primary
#Scope
#Component
#Lazy
public #interface CustomComponent{
/**
* Setting the scope of the beans
* Means: #Scope(value = "scope")
* #return
*/
#AliasFor(
annotation = Scope.class,
attribute = "value"
)
String scope() default SCOPE_PROTOTYPE;
/**
* Setting the name of the beans.
* I don't know why but if I renamed this, it didn't work. I could rename the others.
* Means: #Component(value = "name")
* #return
*/
#AliasFor(
annotation = Component.class,
attribute = "value"
)
String value() default "";
/**
* Setting the bean is lazy or not
* Means: #Lazy(value = true)
* #return
*/
#AliasFor(
annotation = Lazy.class,
attribute = "value"
)
boolean isLazy() default false;
}
How to use:
#CustomComponent( scope = "SCOPE_SINGLETON", value = "bean-name", isLazy = true);
I hope you can transform it to your case.

Related

How to create custom #ConditionalOnProperty for simpler usage?

In a Spring-Boot project, I use #ConditionalOnProperty to choose whether some Beans get loaded or not. It looks like the following:
#ConditionalOnProperty(
prefix = "myservice",
name = "implversion",
havingValue = "a"
)
#Service
public class MyServiceImplA implements MyService {
// ...
}
This allows me to choose with specific profiles which Bean should be loaded, for example different implementations of an interface, depending on the value of myservice.implversion being a or b or whatever other value.
I'd like to achieve the same effect with a user-friendlier annotation like such:
#OnMyServiceVersion(value = "a")
#Service
public class MyServiceImplA implements MyService {
// ...
}
How can one do this?
I've tried annotating my custom annotation with #Conditional and implementing the Condition interface but I don't understand how to check properties that way. The Spring-Boot OnPropertyCondition extends SpringBootCondition is not public so I cannot start from there, and extending annotations isn't allowed, so I'm kind of stuck.
I've also tried the following with no success:
// INVALID CODE, DO NOT USE
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#ConditionalOnProperty(
prefix = "myservice",
name = "implversion",
havingValue = OnMyServiceVersion.value()
)
public #interface OnMyServiceVersion {
String value();
}
You can annotate your #OnMyServiceVersion annotation with #ConditionalOnProperty and alias the value of your annotation to the havingValue attribute of #ConditionalOnProperty:
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#ConditionalOnProperty(prefix = "myservice", name = "implversion")
public #interface OnMyServiceVersion {
#AliasFor(annotation = ConditionalOnProperty.class, attribute = "havingValue")
String value() default "";
}
Here's a complete example that shows this in action:
package com.example.demo;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.annotation.AliasFor;
import org.springframework.stereotype.Service;
#SpringBootApplication
public class CustomPropertyConditionApplication {
public static void main(String[] args) {
SpringApplication.run(CustomPropertyConditionApplication.class, "--myservice.implversion=b");
}
#Service
#OnMyServiceVersion("a")
static class ServiceA {
ServiceA() {
System.out.println("Service A");
}
}
#Service
#OnMyServiceVersion("b")
static class ServiceB {
ServiceB() {
System.out.println("Service B");
}
}
#Documented
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
#ConditionalOnProperty(prefix = "myservice", name = "implversion")
static #interface OnMyServiceVersion {
#AliasFor(annotation = ConditionalOnProperty.class, attribute = "havingValue")
String value() default "";
}
}
This will output Service B when run. If you change the arguments in the main method to --myservice.implversion=a it will output Service A. If you remove the argument, it won't output either.
#Bean(name = "emailNotification")
#ConditionalOnProperty(prefix = "notification", name = "service")
public NotificationSender notificationSender() {
return new EmailNotification();
}
for reference
https://www.baeldung.com/spring-conditionalonproperty

How to use #PropertySource with Java Spring 4.0.4?

I would like to add the source of database.properties which is in ProjectName/src/database.properties to AppConfig.class which is in ProjectName/src/device/spring/configaccording to https://www.journaldev.com/17053/spring-jdbctemplate-example
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import tekranchecklist.model.*;
#Configuration
#ComponentScan("device.spring.dao","device.model","device.spring.config","device.Main")
public class AppConfig {
#Autowired
Environment environment;
private final String URL = "URL";
private final String USER = "root";
private final String DRIVER = "DRIVER";
private final String PASSWORD = "PASSWORD";
#Bean
DataSource dataSource() {
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setUrl(environment.getProperty(URL));
driverManagerDataSource.setUsername(environment.getProperty(USER));
driverManagerDataSource.setPassword(environment.getProperty(PASSWORD));
driverManagerDataSource.setDriverClassName(environment.getProperty(DRIVER));
return driverManagerDataSource;
}
}
I tried to use #PropertySource("classpath:database.properties") but it is syntax error that: class, interface or enum expected. Can someone help me how I should add my .properties file path with #PropertySource?
#PropertySource is an annotation that can be used only on types, that is interface, class, enum :
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Repeatable(PropertySources.class)
public #interface PropertySource {...}
This class, interface or enum expected message is a compilation error that means that you specified the annotation on a target that doesn't match to a type.
So move it at the correct place :
#PropertySource("classpath:database.properties")
public class AppConfig {
....
}
You can use #PropertySource with either #Value or Environment as shown below.
Assuming this is your application property file.
app.value.example=v1
app.environment.example=e1
Using #PropertySource with #Value
#Configuration
#PropertySource("classpath:application.properties")
public class ApplicationContig {
#Value("${app.value.example:defaultValueCanBeHere}")
private String propertyValue;
public void usePropertyValue() {
// You can use it here
}
}
Using #PropertySource with Environment
#Configuration
#PropertySource("classpath:application.properties")
public class ApplicationContig {
#Autowired
private Environment environmentValue;
private void useEnvironmentValue() {
String value = environmentValue.getProperty("app.environment.example");
// You can then use it here.
}
}
With Spring >= 4
#Configuration
#PropertySources({
#PropertySource(value = "classpath:application.properties"),
#PropertySource(value = "classpath:another.properties"),
#PropertySource(value = "classpath:missing-file.properties",
ignoreResourceNotFound = true)})
public class ApplicationContig {
// You can either use #Value or Environment as demonstrated above
}
I hope this will help.

Spring Data JPA - How to programmatically set JpaRepository base packages

When defining an EntityManager in a Spring Java Config class, I can add the base packages to scan for Entity classes by calling a method on the corresponding builder:
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder) {
// Some other configuration here
builder.packages("org.foo.bar", "org.foo.baz");
return builder.build();
}
I need something similar for the places where Spring looks for Repository Interfaces. The usual way is using the #EnableJpaRepositories annotation:
#EnableJpaRepositories(basePackages = {"org.foo.barbaz"})
But I would like to have a dynamical way for defining these packages similar to the way above for the Entity locations. Something like this:
public SomeJpaRepositoryFactoryBean entityManagerFactory(JpaRepositoryFactoryBuilder builder) {
// Some other configuration here
builder.packages("org.foo.barbaz");
return builder.build();
}
Is there a way to do this with the current Spring Data JPA release is it simply not meant to be done this way?
You can use #AutoConfigurationPackage annotation to add your child module's package to scan-packages.
Remove all #EnableJpaRepositories from your child module
Add #AutoConfigurationPackage class to the top directory of your child module (similar to #SpringBootApplication, you must put this class to the top-most directory to scan all subpackages):
#AutoConfigurationPackage
public class ChildConfiguration {
}
Create spring.factories file under /resources/META-INF/spring.factories of your child module and add the configuration class:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.child.package.ChildConfiguration
Now you can #Autowired your repository from your core project. (Tested and worked)
Without Spring Boot (plain Spring MVC setup)
#EnableJpaRepositories can be used on more than one #Configuration class. That said, every module can declare its own repositories by having an own configuration class:
#Configuration
#EnableJpaRepositories(basePackages = "package1")
public class ConfigClass1 { /* ... */ }
#Configuration
#EnableJpaRepositories(basePackages = "package2")
public class ConfigClass2 { /* ... */ }
Spring Data JPA then counts in all of them (package1 and package2).
Although this is still not a programmatical way, it solves my problem.
This problem also puzzled me for almost a week.I debug the "spring application context refresh" code and org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar line by line and then I solve the problem.
I customize my EnableJpaRepository annotation and JpaRepositoriesRegistrar,then I can do anything in AbdJpaRepositoriesRegistrar("abd" is my customized classes's prefix).
AbdEnableJpaRepositories.java
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Import;
import org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean;
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.transaction.PlatformTransactionManager;
import javax.persistence.EntityManagerFactory;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* basePackages
* 复制EnableJpaRepositories,Import自定义的AbdJpaRepositoriesRegistrar.
* Copy from EnableJpaRepositories,Import customized AbdJpaRepositoriesRegistrar.
*
* #author Oliver Gierke
* #author Thomas Darimont
* #author ghj
*/
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
#Import(AbdJpaRepositoriesRegistrar.class)
public #interface AbdEnableJpaRepositories {
/**
* Alias for the {#link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
* {#code #EnableJpaRepositories("org.my.pkg")} instead of {#code #EnableJpaRepositories(basePackages="org.my.pkg")}.
*/
String value() default "";
/**
* Base packages to scan for annotated components. {#link #value()} is an alias for (and mutually exclusive with) this
* attribute. Use {#link #basePackageClasses()} for a type-safe alternative to String-based package names.
*/
String basePackages() default "";
/**
* Type-safe alternative to {#link #basePackages()} for specifying the packages to scan for annotated components. The
* package of each class specified will be scanned. Consider creating a special no-op marker class or interface in
* each package that serves no purpose other than being referenced by this attribute.
*/
Class<?>[] basePackageClasses() default {};
/**
* Specifies which types are eligible for component scanning. Further narrows the set of candidate components from
* everything in {#link #basePackages()} to everything in the base packages that matches the given filter or filters.
*/
Filter[] includeFilters() default {};
/**
* Specifies which types are not eligible for component scanning.
*/
Filter[] excludeFilters() default {};
/**
* Returns the postfix to be used when looking up custom repository implementations. Defaults to {#literal Impl}. So
* for a repository named {#code PersonRepository} the corresponding implementation class will be looked up scanning
* for {#code PersonRepositoryImpl}.
*
* #return
*/
String repositoryImplementationPostfix() default "Impl";
/**
* Configures the location of where to find the Spring Data named queries properties file. Will default to
* {#code META-INF/jpa-named-queries.properties}.
*
* #return
*/
String namedQueriesLocation() default "";
/**
* Returns the key of the {#link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
* {#link Key#CREATE_IF_NOT_FOUND}.
*
* #return
*/
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
/**
* Returns the {#link FactoryBean} class to be used for each repository instance. Defaults to
* {#link JpaRepositoryFactoryBean}.
*
* #return
*/
Class<?> repositoryFactoryBeanClass() default JpaRepositoryFactoryBean.class;
/**
* Configure the repository base class to be used to create repository proxies for this particular configuration.
*
* #return
* #since 1.9
*/
Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
// JPA specific configuration
/**
* Configures the name of the {#link EntityManagerFactory} bean definition to be used to create repositories
* discovered through this annotation. Defaults to {#code entityManagerFactory}.
*
* #return
*/
String entityManagerFactoryRef() default "entityManagerFactory";
/**
* Configures the name of the {#link PlatformTransactionManager} bean definition to be used to create repositories
* discovered through this annotation. Defaults to {#code transactionManager}.
*
* #return
*/
String transactionManagerRef() default "transactionManager";
/**
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
* repositories infrastructure.
*/
boolean considerNestedRepositories() default false;
/**
* Configures whether to enable default transactions for Spring Data JPA repositories. Defaults to {#literal true}. If
* disabled, repositories must be used behind a facade that's configuring transactions (e.g. using Spring's annotation
* driven transaction facilities) or repository methods have to be used to demarcate transactions.
*
* #return whether to enable default transactions, defaults to {#literal true}.
*/
boolean enableDefaultTransactions() default true;
}
AbdJpaRepositoriesRegistrar.java
import org.springframework.data.jpa.repository.config.JpaRepositoryConfigExtension;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import java.lang.annotation.Annotation;
class AbdJpaRepositoriesRegistrar extends AbdRepositoryBeanDefinitionRegistrarSupport {
/*
* (non-Javadoc)
* #see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation()
*/
#Override
protected Class<? extends Annotation> getAnnotation() {
return AbdEnableJpaRepositories.class;
}
/*
* (non-Javadoc)
* #see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension()
*/
#Override
protected RepositoryConfigurationExtension getExtension() {
return new JpaRepositoryConfigExtension();
}
}
AbdRepositoryBeanDefinitionRegistrarSupport.java
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport;
import org.springframework.data.repository.config.RepositoryConfigurationDelegate;
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
import org.springframework.data.repository.config.RepositoryConfigurationUtils;
import org.springframework.util.Assert;
/**
*
* #author ghj
*/
abstract class AbdRepositoryBeanDefinitionRegistrarSupport extends RepositoryBeanDefinitionRegistrarSupport implements ImportBeanDefinitionRegistrar,
ResourceLoaderAware, EnvironmentAware {
private ResourceLoader resourceLoader;
private Environment environment;
#Override
public void setResourceLoader(ResourceLoader resourceLoader) {
this.resourceLoader = resourceLoader;
}
#Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
#Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
Assert.notNull(resourceLoader, "ResourceLoader must not be null!");
Assert.notNull(annotationMetadata, "AnnotationMetadata must not be null!");
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
// Guard against calls for sub-classes
if (annotationMetadata.getAnnotationAttributes(getAnnotation().getName()) == null) {
return;
}
// 使用自定义的AbdAnnotationRepositoryConfigurationSource
AbdAnnotationRepositoryConfigurationSource configurationSource = new AbdAnnotationRepositoryConfigurationSource(
annotationMetadata, getAnnotation(), resourceLoader, environment);
RepositoryConfigurationExtension extension = getExtension();
RepositoryConfigurationUtils.exposeRegistration(extension, registry, configurationSource);
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configurationSource, resourceLoader,
environment);
delegate.registerRepositoriesIn(registry, extension);
}
}
AbdAnnotationRepositoryConfigurationSource.java. You can override getBasePackages then you can return any packages you want.
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
*
* #author ghj
*/
class AbdAnnotationRepositoryConfigurationSource extends AnnotationRepositoryConfigurationSource {
private static final String BASE_PACKAGES = "basePackages";
private static final String BASE_PACKAGE_CLASSES = "basePackageClasses";
private final AnnotationMetadata configMetadata;
private final AnnotationAttributes attributes;
private final Environment environment;
AbdAnnotationRepositoryConfigurationSource(AnnotationMetadata metadata, Class<? extends Annotation> annotation, ResourceLoader resourceLoader, Environment environment) {
super(metadata, annotation, resourceLoader, environment);
this.attributes = new AnnotationAttributes(metadata.getAnnotationAttributes(annotation.getName()));
this.configMetadata = metadata;
this.environment = environment;
}
#Override
public Iterable<String> getBasePackages() {
String value = attributes.getStringArray("value")[0];
String basePackages = attributes.getStringArray(BASE_PACKAGES)[0];
Class<?>[] basePackageClasses = attributes.getClassArray(BASE_PACKAGE_CLASSES);
// Default configuration - return package of annotated class
if (StringUtils.isEmpty(value) && StringUtils.isEmpty(basePackages) && basePackageClasses.length == 0) {
String className = configMetadata.getClassName();
return Collections.singleton(ClassUtils.getPackageName(className));
}
String[] packagesFromValue = parsePackagesSpel(value);
String[] packagesFromBasePackages = parsePackagesSpel(basePackages);
Set<String> packages = new HashSet<>();
packages.addAll(Arrays.asList(packagesFromValue));
packages.addAll(Arrays.asList(packagesFromBasePackages));
for (Class<?> typeName : basePackageClasses) {
packages.add(ClassUtils.getPackageName(typeName));
}
return packages;
}
private String[] parsePackagesSpel(String raw) {
if (!raw.trim().startsWith("$")) {
if (StringUtils.isEmpty(raw)) {
return new String[]{};
}
return raw.split(",");
} else {
raw = raw.trim();
String packages = this.environment.getProperty(raw.substring("${".length(), raw.length() - "}".length()));
return packages.split(",");
}
}
}
How to use?Here is configuration file.
PrimaryJpaConfiguration.java
import com.shinow.abd.springjpa2.annotation.AbdEnableJpaRepositories;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import javax.persistence.EntityManager;
import javax.sql.DataSource;
import java.util.Map;
#Configuration
#AbdEnableJpaRepositories(
basePackages = "${spring.jpa.base-packages}",
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager"
)
#EnableAutoConfiguration(exclude = HibernateJpaAutoConfiguration.class)
public class PrimaryJpaConfiguration implements EnvironmentAware {
private Environment env;
#Bean
#ConditionalOnMissingBean(name = "entityManager")
#Primary
public EntityManager entityManager(#Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
return entityManagerFactory.getObject().createEntityManager();
}
#Bean
#ConditionalOnMissingBean(name = "entityManagerFactory")
#Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory(#Qualifier("dataSource") DataSource dataSource) {
Map<String, Object> properties = JpaProperties.get("", env);
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaPropertyMap(properties);
entityManagerFactoryBean.setPackagesToScan(env.getProperty("spring.jpa.base-packages").split(","));
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
return entityManagerFactoryBean;
}
#Bean
#ConditionalOnMissingBean(name = "dataSource")
#Primary
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource() {
return DataSourceBuilder.create().build();
}
#Bean
#ConditionalOnMissingBean(name = "transactionManager")
#Primary
public PlatformTransactionManager transactionManager(#Qualifier("entityManagerFactory") LocalContainerEntityManagerFactoryBean entityManagerFactory) {
JpaTransactionManager transactionManager
= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
entityManagerFactory.getObject());
return transactionManager;
}
#Override
public void setEnvironment(Environment environment) {
this.env = environment;
}
}
You can add spring.jpa.base-packages config to your application.properties.
For example:spring.jpa.base-packages=com.foo.a,com.bar.b,and the repositories and entities under those packages "com.foo.a" and "com.bar.b" will be added to the spring Ioc container.
Answer by 高慧觉 Spring Data JPA - How to programmatically set JpaRepository base packages worked for me, but I've come up with a simpler and more reliable AnnotationRepositoryConfigurationSource implementation: just allow Spring Data to collect the packages in its way, and then post-process them and expand property placeholders to package names.
class AbdAnnotationRepositoryConfigurationSource extends AnnotationRepositoryConfigurationSource {
private final Environment environment;
ExpressionsSupportingAnnotationRepositoryConfigurationSource(AnnotationMetadata metadata, Class<? extends Annotation> annotation,
ResourceLoader resourceLoader, Environment environment, BeanDefinitionRegistry registry) {
super(metadata, annotation, resourceLoader, environment, registry);
this.environment = environment;
}
#Override
public Streamable<String> getBasePackages() {
Streamable<String> rawPackages = super.getBasePackages();
return Streamable.of(() -> rawPackages.stream()
.flatMap(raw -> parsePackagesSpel(raw).stream())
);
}
private List<String> parsePackagesSpel(#Nullable String rawPackage) {
Objects.requireNonNull(rawPackage, "Package specification cannot be null");
if (!rawPackage.trim().startsWith("$")) {
return Collections.singletonList(rawPackage);
}
rawPackage = rawPackage.trim();
String propertyName = rawPackage.substring("${".length(), rawPackage.length() - "}".length());
String packages = this.environment.getProperty(propertyName);
if (!StringUtils.hasText(packages)) {
throw new IllegalStateException(
String.format("Could not resolve the following packages definition: %s", rawPackage));
}
return Arrays.stream(packages.split(","))
.map(String::trim)
.filter(StringUtils::hasText)
.collect(Collectors.toList());
}
}
I've implemented a way to invoke #EnableJpaRepositories purely programmatically. For the purpose I've created a class which simulates the Annotation data passed to a config class annotated with the #EnableJpaRepositories annotation. I called my class EnableJpaRepositoriesData. It is confirmed working with Spring 5.1.5, SpringBoot 2.1.3. I guess it must be compatible with earlier and later versions alike, with none or very little changes at most.
Below follows the class code, and a sample usage code for it.
EnableJpaRepositoriesData.java:
package org.patladj.jpa.config.multijpa;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.AnnotationMetadataReadingVisitor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Indexed;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.annotation.TransactionManagementConfigurationSelector;
import java.lang.reflect.Method;
import java.util.*;
public class EnableJpaRepositoriesData extends AnnotationMetadataReadingVisitor implements AnnotationMetadata {
private Map<String, ?> data;
private void initIt() throws NoSuchMethodException, ClassNotFoundException {
//################## protected final Set<String> annotationSet = new LinkedHashSet<>(4);
annotationSet.add(Configuration.class.getCanonicalName());
annotationSet.add(EnableTransactionManagement.class.getCanonicalName());
annotationSet.add(EnableJpaRepositories.class.getCanonicalName());
//################## protected final Map<String, Set<String>> metaAnnotationMap = new LinkedHashMap<>(4);
metaAnnotationMap.put(Configuration.class.getCanonicalName(),
new LinkedHashSet<>(Arrays.asList(
Component.class.getCanonicalName(),
Indexed.class.getCanonicalName()
)));
metaAnnotationMap.put(EnableTransactionManagement.class.getCanonicalName(),
new LinkedHashSet<>(Arrays.asList(
Import.class.getCanonicalName()
)));
metaAnnotationMap.put(EnableJpaRepositories.class.getCanonicalName(),
new LinkedHashSet<>(Arrays.asList(
Import.class.getCanonicalName()
)));
//################## protected final LinkedMultiValueMap<String, AnnotationAttributes> attributesMap = new LinkedMultiValueMap<>(4);
attributesMap.put(Configuration.class.getCanonicalName(),
new LinkedList<AnnotationAttributes>() {{
add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
put("value", defaultFor(Configuration.class, "value"));
}}));
}});
attributesMap.put(Component.class.getCanonicalName(),
new LinkedList<AnnotationAttributes>() {{
add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
put("value", defaultFor(Component.class, "value"));
}}));
}});
attributesMap.put(Indexed.class.getCanonicalName(),
new LinkedList<AnnotationAttributes>() {{
add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
}}));
}});
attributesMap.put(EnableTransactionManagement.class.getCanonicalName(),
new LinkedList<AnnotationAttributes>() {{
add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
put("order", defaultFor(EnableTransactionManagement.class, "order"));
put("mode", defaultFor(EnableTransactionManagement.class, "mode"));
put("proxyTargetClass", defaultFor(EnableTransactionManagement.class, "proxyTargetClass"));
}}));
}});
attributesMap.put(Import.class.getCanonicalName(),
new LinkedList<AnnotationAttributes>() {{
add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
put("value", new Class<?>[]{TransactionManagementConfigurationSelector.class});
}}));
add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
put("value", new Class<?>[]{Class.forName("org.springframework.data.jpa.repository.config.JpaRepositoriesRegistrar")});
}}));
}});
attributesMap.put(EnableJpaRepositories.class.getCanonicalName(),
new LinkedList<AnnotationAttributes>() {{
add(new AnnotationAttributes(new LinkedHashMap<String, Object>() {{
put("repositoryBaseClass", data.get("repositoryBaseClass"));
put("basePackages", data.get("basePackages"));
put("value", defaultFor(EnableJpaRepositories.class, "value"));
put("excludeFilters", new AnnotationAttributes[]{});
put("includeFilters", new AnnotationAttributes[]{});
put("basePackageClasses", defaultFor(EnableJpaRepositories.class, "basePackageClasses"));
put("bootstrapMode", defaultFor(EnableJpaRepositories.class, "bootstrapMode"));
put("transactionManagerRef", data.get("transactionManagerRef"));
put("considerNestedRepositories", defaultFor(EnableJpaRepositories.class, "considerNestedRepositories"));
put("namedQueriesLocation", defaultFor(EnableJpaRepositories.class, "namedQueriesLocation"));
put("queryLookupStrategy", defaultFor(EnableJpaRepositories.class, "queryLookupStrategy"));
put("entityManagerFactoryRef", data.get("entityManagerFactoryRef"));
put("enableDefaultTransactions", defaultFor(EnableJpaRepositories.class, "enableDefaultTransactions"));
put("repositoryImplementationPostfix", defaultFor(EnableJpaRepositories.class, "repositoryImplementationPostfix"));
put("repositoryFactoryBeanClass", defaultFor(EnableJpaRepositories.class, "repositoryFactoryBeanClass"));
}}));
}});
//##################
}
public EnableJpaRepositoriesData(#Nullable ClassLoader classLoader, Map<String, ?> data) throws NoSuchMethodException, ClassNotFoundException {
super(classLoader);
this.data = data;
this.initIt();
}
private Object defaultFor(Class<?> clazz, String methodName) throws NoSuchMethodException {
Method method = clazz.getDeclaredMethod(methodName);
return method.getDefaultValue();
}
}
Sample usage of the Class to actually invoke enableJpaRepositories
programmatically:
AnnotationMetadata enableJpaRepositoriesData = null;
try {
enableJpaRepositoriesData = new EnableJpaRepositoriesData(this.getClass().getClassLoader(), new HashMap<String, Object>() {{
put("repositoryBaseClass", MyJPAConnectorImpl.class);
put("basePackages", new String[] {
//This is where you set the repositories base packages to scan.
//... Having your best programmatic convenience
"org.patladj.connector.multijpa.common",
"org.patladj.connector.multijpa.anotherrepositoriespackage",
"org.patladj.connector.multijpa.morepackagestoscanforrepositories..."
});
//Ofcourse you need to create by yourself the custom transactionManagerFactory,
// ... entityManagerFactory, JPAVendorAdapter and Datasource beans for your
// ... custom persistence contexts using unique bean names (different than
// ... the default Spring data names: 'entityManagerFactory', etc...)
// ... Then "send" these bean names as references to each and every ...
// and as many as you want persistence contexts programmatically here
put("transactionManagerRef", myCustomPersistanceContextName + "_transactionManager");
put("entityManagerFactoryRef", myCustomPersistanceContextName + "_entityManagerFactory");
}});
} catch (NoSuchMethodException e) {
throw new ExceptionInInitializerError(e);
} catch (ClassNotFoundException e) {
throw new ExceptionInInitializerError(e);
}
AnnotationRepositoryConfigurationSource configurationSource = new AnnotationRepositoryConfigurationSource(
enableJpaRepositoriesData, EnableJpaRepositories.class, applicationContext, environment, registry
);
RepositoryConfigurationExtension extension = new JpaRepositoryConfigExtension();
RepositoryConfigurationUtils.exposeRegistration(extension, registry, configurationSource);
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(configurationSource, applicationContext, environment);
delegate.registerRepositoriesIn(registry, extension);
And finally, in case if you don't know, you can use the following #Configuration class
public class MultiJPABeanDefinitionRegistrar implements BeanDefinitionRegistryPostProcessor, ApplicationContextAware
... to override 2 of its methods to have the post process bean registry and applicationContext at your disposal to register bean definitions dynamically.
Methods to override:
- public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
- public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException
I referred this post only and created a module aware multi database aware spring data library for mysql(compatible with spring boot) in github.Some application properties need to be added and you are done .
Documentation and other details could be found at :-
https://github.com/yatharthamishra0419/spring-boot-data-multimodule-mysql
What your looking for is #EntityScan but it's only available in Spring Boot. The configuration you can annotate in Spring Data JPA is documented here https://docs.spring.io/spring-data/jpa/docs/2.0.8.RELEASE/reference/html/#jpa.java-config
#Configuration
#EnableJpaRepositories
#EnableTransactionManagement
class ApplicationConfig {
#Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setGenerateDdl(true);
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setJpaVendorAdapter(vendorAdapter);
factory.setPackagesToScan("com.acme.domain");
factory.setDataSource(dataSource());
return factory;
}
#Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager txManager = new JpaTransactionManager();
txManager.setEntityManagerFactory(entityManagerFactory);
return txManager;
}
}

Custom annotation validation does no validation on #pathParam in spring

I wrote a custom validation to validate an id (Which is a path Param) as UUID in my #GET method of #RestController but this validation doesn't seem to be working. Even during the debug the control doesn't go to custom validation.
#RestController
#RequestMapping("/rateplan")
#Validated
public class RatePlanServiceController {
#RequestMapping(value = "/{ratePlanId}", method = RequestMethod.GET)
#ResponseStatus(HttpStatus.OK)
public void get(#UUID #PathVariable("ratePlanId") String ratePlanId) {
loggerFactory.warn("Get with Rate plan id {}", ratePlanId);
loggerFactory.info("Get with Rate plan id {}", ratePlanId);
loggerFactory.error("Get with Rate plan id {}", ratePlanId);
loggerFactory.debug("Get with Rate plan id {}", ratePlanId);
// return iRatePlanService.getRatePlan(ratePlanId);
}
}
I wrote the custom annotation for validation of UUID as follow.
import org.springframework.stereotype.Component;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.regex.Pattern;
#Target(ElementType.PARAMETER)
#Constraint(validatedBy = {UUID.IdValidator.class})
#Retention(RetentionPolicy.RUNTIME)
public #interface UUID {
String message() default "{invalid.uuid}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
#Component
public class IdValidator implements ConstraintValidator<UUID, String> {
private static final Pattern id_PATTERN =
Pattern.compile("^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$");
public boolean isValid(String value, ConstraintValidatorContext context) {
if (!(id_PATTERN.matcher(value).matches())) {
return false;
}
return true;
}
public void initialize(UUID parameters) {
}
}
}
Can anyone let me know why is it not working. Even if I provide a garbage ratePlanId like '345%#7^34' it able to go inside GET method.
Solved this by adding a bean in Application configuration file. To validate a path Param in Spring you need to add this bean in your configuration class.
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}

How to use annotation in an annotation?

Given:
public #interface MyAnnotation(){
public SomeType[] value();
}
in Java 7 is it possible to do something like:
#MyAnnotation({
value1,
#MyAnnotation({subValue1, subvalue2, ...}) value2,
...
valueN
})
public Object someProperty;
?
You can. This is an example from Jackson library (leaving out the comments):
package com.fasterxml.jackson.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.ANNOTATION_TYPE, ElementType.TYPE, ElementType.FIELD,
ElementType.METHOD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
#JacksonAnnotation
public #interface JsonSubTypes {
public Type[] value();
public #interface Type {
/**
* Class of the subtype
*/
public Class<?> value();
/**
* Logical type name used as the type identifier for the class
*/
public String name() default "";
}
}
And here is an example usage:
#JsonSubTypes({
#JsonSubTypes.Type(value = TestSystemEvent.class, name = "TestEvent"),
})
public interface SystemEvent {
}
How to use annotation in an annotation?
Maybe like this
public #interface MyAnnotation(){
public SomeType[] value();
public MyAnnotation[] refine();
}
#MyAnnotation(
{value1, value2},
refine={ #MyAnnotation({valueX, valueY}), #MyAnnotation(valueM) }
)
public Object someProperty;
Also, in Java 8, you can have Repeatable annotations - so you may refine or add to your 'primary' (e.g. the first) other refinements brought in by subsequent repetitions of the same annotation.

Categories

Resources