Creating a pointcut for all the classes in package? - java

I have to create a configurable pointcut. Can anyone help for achieving the dynamic pointcut.
DynamicPointcut.class
public class DynamicPointcut extends DynamicMethodMatcherPointcut {
#Value("${custom.logging.basepackage}")
String basePackage;
#Override
public ClassFilter getClassFilter() {
return new ClassFilter() {
#Override
public boolean matches(Class<?> clazz) {
List<Class<?>> classList = ClassFinder.find(basePackage);
return classList.stream().anyMatch(x -> x.equals(clazz));
}
};
}
#Override
public boolean matches(Method method, Class<?> targetClass, Object... args) {
if(args.length>0){
return true;
}
return false;
}
}
ConfigurableAdvisorConfig.class
#Configuration
public class ConfigurableAdvisorConfig {
#Autowired
private ProxyFactoryBean proxyFactoryBean;
#Autowired
DefaultPointcutAdvisor defaultPointcutAdvisor;
DynamicPointcut pointcut = new DynamicPointcut();
NonProductionLoggingAspect advice = new NonProductionLoggingAspect();
String[] advisor;
List<Advisor> advisorList = new ArrayList<Advisor>();
#Bean
public String[] defaultPointcutAdvisor(){
defaultPointcutAdvisor.setAdvice(new NonProductionLoggingAspect());
defaultPointcutAdvisor.setPointcut(new DynamicPointcut());
advisor = new String[]{"defaultPointcutAdvisor"};
return advisor;
}
#Bean
public ProxyFactoryBean proxyFactoryBean(){
proxyFactoryBean.setInterceptorNames(advisor);
return proxyFactoryBean;
}
}

You can wire up your aspect using the static aspectOf factory method (you can't see that method, it is added by the aspectj compiler)
<bean class="com.YourAspect" factory-method="aspectOf">
<property name="basePackage"
value="${custom.logging.basepackage}" />
Reference:
autowiring in aspect

Use AspectJ support in Spring for your scenario.
#Aspect
#Component
public class DaoAspect{
#Pointcut("within(com.xyz..dao.*)")
public void allDao(){};
#Before("allDao")
public void runAdvise(){
//some code
}
}
Define DaoAspect
Enable aspectJ support in spring by using #EnableAspectJAutoProxy
Ensure your aspect gets registered as bean via component scanning
There you go, this way you can advise all the classes in a
particular package

Related

Spring choose bean implementation at runtime

I'm using Spring Beans with annotations and I need to choose different implementation at runtime.
#Service
public class MyService {
public void test(){...}
}
For example for windows's platform I need MyServiceWin extending MyService, for linux platform I need MyServiceLnx extending MyService.
For now I know only one horrible solution:
#Service
public class MyService {
private MyService impl;
#PostInit
public void init(){
if(windows) impl=new MyServiceWin();
else impl=new MyServiceLnx();
}
public void test(){
impl.test();
}
}
Please consider that I'm using annotation only and not XML config.
1. Implement a custom Condition
public class LinuxCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").contains("Linux"); }
}
Same for Windows.
2. Use #Conditional in your Configuration class
#Configuration
public class MyConfiguration {
#Bean
#Conditional(LinuxCondition.class)
public MyService getMyLinuxService() {
return new LinuxService();
}
#Bean
#Conditional(WindowsCondition.class)
public MyService getMyWindowsService() {
return new WindowsService();
}
}
3. Use #Autowired as usual
#Service
public class SomeOtherServiceUsingMyService {
#Autowired
private MyService impl;
// ...
}
Let's create beautiful config.
Imagine that we have Animal interface and we have Dog and Cat implementation. We want to write write:
#Autowired
Animal animal;
but which implementation should we return?
So what is solution? There are many ways to solve problem. I will write how to use #Qualifier and Custom Conditions together.
So First off all let's create our custom annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.FIELD, ElementType.TYPE})
public #interface AnimalType {
String value() default "";
}
and config:
#Configuration
#EnableAutoConfiguration
#ComponentScan
public class AnimalFactoryConfig {
#Bean(name = "AnimalBean")
#AnimalType("Dog")
#Conditional(AnimalCondition.class)
public Animal getDog() {
return new Dog();
}
#Bean(name = "AnimalBean")
#AnimalType("Cat")
#Conditional(AnimalCondition.class)
public Animal getCat() {
return new Cat();
}
}
Note our bean name is AnimalBean. why do we need this bean? because when we inject Animal interface we will write just #Qualifier("AnimalBean")
Also we crated custom annotation to pass the value to our custom Condition.
Now our conditions look like this (imagine that "Dog" name comes from config file or JVM parameter or...)
public class AnimalCondition implements Condition {
#Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
if (annotatedTypeMetadata.isAnnotated(AnimalType.class.getCanonicalName())){
return annotatedTypeMetadata.getAnnotationAttributes(AnimalType.class.getCanonicalName())
.entrySet().stream().anyMatch(f -> f.getValue().equals("Dog"));
}
return false;
}
}
and finally injection:
#Qualifier("AnimalBean")
#Autowired
Animal animal;
You can move the bean injection into the configuration, as:
#Configuration
public class AppConfig {
#Bean
public MyService getMyService() {
if(windows) return new MyServiceWin();
else return new MyServiceLnx();
}
}
Alternatively, you may use profiles windows and linux, then annotate your service implementations with the #Profile annotation, like #Profile("linux") or #Profile("windows"), and provide one of this profiles for your application.
Autowire all your implementations into a factory with #Qualifier annotations, then return the service class you need from the factory.
public class MyService {
private void doStuff();
}
My Windows Service:
#Service("myWindowsService")
public class MyWindowsService implements MyService {
#Override
private void doStuff() {
//Windows specific stuff happens here.
}
}
My Mac Service:
#Service("myMacService")
public class MyMacService implements MyService {
#Override
private void doStuff() {
//Mac specific stuff happens here
}
}
My factory:
#Component
public class MyFactory {
#Autowired
#Qualifier("myWindowsService")
private MyService windowsService;
#Autowired
#Qualifier("myMacService")
private MyService macService;
public MyService getService(String serviceNeeded){
//This logic is ugly
if(serviceNeeded == "Windows"){
return windowsService;
} else {
return macService;
}
}
}
If you want to get really tricky you can use an enum to store your implementation class types, and then use the enum value to choose which implementation you want to return.
public enum ServiceStore {
MAC("myMacService", MyMacService.class),
WINDOWS("myWindowsService", MyWindowsService.class);
private String serviceName;
private Class<?> clazz;
private static final Map<Class<?>, ServiceStore> mapOfClassTypes = new HashMap<Class<?>, ServiceStore>();
static {
//This little bit of black magic, basically sets up your
//static map and allows you to get an enum value based on a classtype
ServiceStore[] namesArray = ServiceStore.values();
for(ServiceStore name : namesArray){
mapOfClassTypes.put(name.getClassType, name);
}
}
private ServiceStore(String serviceName, Class<?> clazz){
this.serviceName = serviceName;
this.clazz = clazz;
}
public String getServiceBeanName() {
return serviceName;
}
public static <T> ServiceStore getOrdinalFromValue(Class<?> clazz) {
return mapOfClassTypes.get(clazz);
}
}
Then your factory can tap into the Application context and pull instances into it's own map. When you add a new service class, just add another entry to the enum, and that's all you have to do.
public class ServiceFactory implements ApplicationContextAware {
private final Map<String, MyService> myServices = new Hashmap<String, MyService>();
public MyService getInstance(Class<?> clazz) {
return myServices.get(ServiceStore.getOrdinalFromValue(clazz).getServiceName());
}
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
myServices.putAll(applicationContext.getBeansofType(MyService.class));
}
}
Now you can just pass the class type you want into the factory, and it will provide you back the instance you need. Very helpful especially if you want to the make the services generic.
Simply make the #Service annotated classes conditional:
That's all. No need for other explicit #Bean methods.
public enum Implementation {
FOO, BAR
}
#Configuration
public class FooCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.FOO == implementation;
}
}
#Configuration
public class BarCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
Implementation implementation = Implementation.valueOf(context.getEnvironment().getProperty("implementation"));
return Implementation.BAR == implementation;
}
}
Here happens the magic.
The condition is right where it belongs: At the implementating classes.
#Conditional(FooCondition.class)
#Service
class MyServiceFooImpl implements MyService {
// ...
}
#Conditional(BarCondition.class)
#Service
class MyServiceBarImpl implements MyService {
// ...
}
You can then use Dependency Injection as usual, e.g. via Lombok's #RequiredArgsConstructor or #Autowired.
#Service
#RequiredArgsConstructor
public class MyApp {
private final MyService myService;
// ...
}
Put this in your application.yml:
implementation: FOO
👍 Only the implementations annotated with the FooCondition will be instantiated. No phantom instantiations. 👍
Just adding my 2 cents to this question. Note that one doesn't have to implement so many java classes as the other answers are showing. One can simply use the #ConditionalOnProperty. Example:
#Service
#ConditionalOnProperty(
value="property.my.service",
havingValue = "foo",
matchIfMissing = true)
class MyServiceFooImpl implements MyService {
// ...
}
#ConditionalOnProperty(
value="property.my.service",
havingValue = "bar")
class MyServiceBarImpl implements MyService {
// ...
}
Put this in your application.yml:
property.my.service: foo
MyService.java:
public interface MyService {
String message();
}
MyServiceConfig.java:
#Configuration
public class MyServiceConfig {
#Value("${service-type}")
MyServiceTypes myServiceType;
#Bean
public MyService getMyService() {
if (myServiceType == MyServiceTypes.One) {
return new MyServiceImp1();
} else {
return new MyServiceImp2();
}
}
}
application.properties:
service-type=one
MyServiceTypes.java
public enum MyServiceTypes {
One,
Two
}
Use in any Bean/Component/Service/etc. like:
#Autowired
MyService myService;
...
String message = myService.message()

How to inject ANY information about test in spring testing?

I would like some of my beans know something about test. SOMETHING. May be test class name or some of it's methods.
For example, suppose my test class has a method
public String getTestName() {
return getClass().getSimpleName();
}
This method returns test name and can be overridden.
Is it possible to inject this name into some beans of Spring context, to use during test?
For example, with autowire feature:
#Autowired
public String testName;
not only in test class, but in other beans too.
UPDATE
Below are two (failed) attempts to implement injecting testInstance. May be there are some convenient ways to do that?
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestClassAwareTry._Config.class)
#TestExecutionListeners(value = { TestClassAwareTry._Listener.class },
mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
public class TestClassAwareTry {
/**
* Interface to tag beans, who want to know if they are in test
*/
public interface TestInstanceAware {
void setTestInstance(Object value);
}
/**
* Sample bean, which would like to know if it is in test
*/
public static class MyBean implements TestInstanceAware {
private Object testInstance;
{
System.out.println("MyBean constructed");
}
public void setTestInstance(Object value) {
this.testInstance = value;
System.out.println("testInstance set");
}
public Object getTestInstance() {
return testInstance;
}
}
/**
* Attempt to inject testInstance with a bean, implementing {#link BeanPostProcessor}
*/
public static class TestInstanceInjector implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if( bean instanceof TestInstanceAware ) {
TestInstanceAware aware = (TestInstanceAware) bean;
// we don't have access to test instance here
// otherwise I would write
//Object testInstance = getTestInstance();
//aware.setTestInstance(testInstance);
}
return bean;
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
/**
* Attempt to inject testInstance with test execution listener
*/
public static class _Listener extends AbstractTestExecutionListener {
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
Object testInstance = testContext.getTestInstance();
ApplicationContext context = testContext.getApplicationContext();
// we don't have setBean() method
// I would write if I have
// context.setBean("testInstance", context);
}
}
/**
* Java-based configuration
*/
#Configuration
public class _Config {
#Bean
public MyBean myBean() {
return new MyBean();
}
#Bean
public TestInstanceInjector testInstanceInjector() {
return new TestInstanceInjector();
// I would acquire test instance here and pass it to constructor, if I can
}
}
#Autowired
public MyBean myBean;
#Test
public void testInjected() {
assertSame( this, myBean.getTestInstance());
}
}
I've ended up creating ContextCustomizerFactory that registers BeanPostProcessor
package com.company.testing.base.spring;
import java.util.List;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.test.context.ContextConfigurationAttributes;
import org.springframework.test.context.ContextCustomizer;
import org.springframework.test.context.ContextCustomizerFactory;
public class TestAwareContextCustomizerFactory implements ContextCustomizerFactory {
#Override
public ContextCustomizer createContextCustomizer(
Class<?> testClass, List<ContextConfigurationAttributes> configAttributes) {
return (context, mergedConfig) -> {
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
beanFactory.addBeanPostProcessor(
new TestInstanceAwareBeanPostProcessor(mergedConfig.getTestClass()));
};
}
}
TestInstanceAwareBeanPostProcessor
public class TestInstanceAwareBeanPostProcessor implements BeanPostProcessor {
private final Class<?> testClass;
TestInstanceAwareBeanPostProcessor(Class<?> testClass) {
this.testClass = testClass;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof TestClassAware) {
((TestClassAware) bean).setTestClass(testClass);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
resources/META-INF/spring.factories
# ContextCustomizerFactories for the Spring TestContext Framework
org.springframework.test.context.ContextCustomizerFactory = \
com.company.testing.base.spring.TestAwareContextCustomizerFactory
The only way I've been able to do this is by delaying creation of the subject until you are in the test method and to have the bean in the prototype scope.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { LiveConfig.class, DevConfig.class})
#ActiveProfiles("Dev")
public class MeTest {
#Autowired
public ApplicationContext context;
#Autowired
DevConfig devConfig;
#Rule
public TestName nameRule = new TestName();
#Before
public void setName() {
devConfig.setSettings(nameRule.getMethodName());
}
#Test
public void test() {
Bean subject = context.getBean(Bean.class);
System.out.println(subject.settings);
assertThat(subject.settings, is(nameRule.getMethodName()));
}
#Test
public void test2() {
Bean subject = context.getBean(Bean.class);
System.out.println(subject.settings);
assertThat(subject.settings, is(nameRule.getMethodName()));
}
}
#Configuration
class LiveConfig {
#org.springframework.context.annotation.Bean
public String getSettings() {
return "/some/real/file.txt";
}
#org.springframework.context.annotation.Bean
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Bean getBean() {
return new Bean();
}
}
#Configuration
class DevConfig {
private String settings;
#org.springframework.context.annotation.Bean
#Profile("Dev")
#Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public String getSettings() {
return settings;
}
public void setSettings(String settings) {
this.settings = settings;
}
}
class Bean {
public Bean() {
System.out.println("Bean");
}
String settings;
#Autowired
void setSettings(String settings) {
System.out.println("Settings: " + settings);
this.settings = settings;
}
}
This uses Profiles to change what Live sees and what the tests see, and the a NameRule to get the name. It is clunky.
I would NOT use the TestName rule, but rather the TemporaryFolder rule and use that to set whatever setting your application uses for the output folder. I'd also only use DI in a test in very rare cases (i.e. full blown integration tests).
Do you mean like this?
public class MyTest {
#Test
public void testName() {
MyBean b = new MyBean(MyTest.class.getSimpleName());
b.doSomething();
}
}
You can achieve this in a more elegant way using Spring Boot Auto configuration feature by making yours, this way:
define a Configuration class that exposes or registers your bean this way:
#Configuration
public class MyBeanProviderConfiguration {
#ConditionalOnMissingBean
#Bean
public MyBean myBean() {
// return a fully initialised MyBean instance
}
}
Then define a custom annotation Spring Boot like, say #AutoConfigureMyBean this way:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#ImportAutoConfiguration(MyBeanProviderConfiguration.class)
public #interface AutoConfigureMyBean {}
Then you can use this in your Spring test, here is an example:
#RunWith(SpringRunner.class)
#AutoConfigureMyBean
public class MyTest {
#Autowired
MyBean myBean;
}
Or also declare your MyBean #Autowired dependent bean in a regular Spring test (using a Config class), A MyBean instance will be automatically injected into it.

Spring Formatter for field annotation not work in Web Flow

Using spring web flow 2,
Formatter by field type is effective
but Formatter for Field Annotation is not.
getPrint and getParser not called.
(by field type, they are called)
I've spent much time about this,
but have no good results.
Bean for page
public TestBean {
#TestFormat
private String test;
...
}
Annotation
#Target({ElementType.TYPE,ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface TestFormat {}
AnnotationFormatterFactory
public class TestFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<TestFormat>,Serializable {
#Override
public Set<Class<?>> getFieldTypes() {
Set<Class<?>> set = new HashSet<Class<?>>();
set.add(TestFormat.class);
return set;
}
#Override
public Printer<?> getPrinter(TestFormat annotation, Class<?> fieldType) {
return new TestFormatter();
}
#Override
public Parser<?> getParser(TestFormat annotation, Class<?> fieldType) {
return new TestFormatter();
}
}
Formatter
public class TestFormatter implements Formatter<String>{
#Override
public String print(String str, Locale locale) {
return str.substring(0, str.indexOf("parsed")); // example
}
#Override
public String parse(String input, Locale locale) throws ParseException {
return input + "parsed"; // example
}
}
ApplicationFormatterRegistrar
public class ApplicationFormatterRegistrar implements FormatterRegistrar {
#Override
public void registerFormatters(FormatterRegistry registry) {
registry.addFormatterForFieldAnnotation(new TestFormatAnnotationFormatterFactory());
}
}
SpringMVC Configuration
<bean id="applicationConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<property name="formatterRegistrars">
<set>
<ref local="applicationFormatterRegistrar"/>
</set>
</property>
</bean>
<bean id="applicationFormatterRegistrar" class="package.ApplicationFormatterRegistrar"/>
Spring Webflow Configuration
<bean id="defaultConversionService" class="org.springframework.binding.convert.service.DefaultConversionService" >
<constructor-arg ref="applicationConversionService"/>
</bean>
<webflow:flow-builder-services id="flowBuilderServices" conversion-service="defaultConversionService"/>
this might be related, but I could not find a
solution
Spring Web Flow 2.4.1
Spring 4.1.6
Thymeleaf 2.1.4
When implementing the AnnotationFormatterFactory then its getFieldTypes method should return the type of the fields the annotation applies to. With your current configuration you are saying TestFormat can be annotated with TestFormat.
I suspect however that you want to specify String can be annotated with TestFormat.
Change your implementation to return String.class instead.
public class TestFormatAnnotationFormatterFactory implements AnnotationFormatterFactory<TestFormat>,Serializable {
#Override
public Set<Class<?>> getFieldTypes() {
return Collections.singleton(String.class);
}
...
}

Spring Dependency Injection: How to Instantiate Class based on system property?

consider this example
I have interface
interface FSClient
String getMeData()
and classes implementing it
class MockFSClient implements FSClient {
public String getMeData() {
return “I am mock”;
}
}
class RestFSClient implements FSClient {
public String getMeData() {
final String data = // from web service
return data;
}
}
and a manager which looks like
class FSManager {
private FSClient fsclient;
#Autowired
public void FSManager(#Nonnull final FSClient fsClient) {
this.fsclient = fsClient;
}
}
I want to instantiate fsclient based on a system property
com.fs.mock=true
meaning if com.fs.mock=true, fsclient should be MockFSClient else fsClient should be RestFSClient
How can I do that?
Why do I need it?
so that I can decouple and do testing
Please help, I am new to Spring
Are you asking how you get the value of com.fs.mock because the answer is use the #Value annotation and a PropertyPlaceholderConfigurer bean.
If you are asking how do you create the actual object then as #Jakkra says use a factory that contains an if statement around the value of com.fs.mock. Its not the most elegant solution but it would work.
Example
public class ClientFactory {
#Value("${com.fs.mock}")
private boolean mockFlag;
public static returnClientInstance(){
if(mockFlag){
return new MockFSClient();
}
else{
return new RestFSClient();
}
}
}
Use profiles
...
<beans profile="dev">
<bean id="b1" class="MockFSClient" />
</beans>
<beans profile="uat,prod">
<bean id="b1" class="RestFSClient" />
</beans>
...
... -Dspring.profiles.active=dev ...
Probably the best approach is to use profiles as described by Evgeniy Dorofeev in which case you wouldn't need your custom System property. But here's another solution if you were to stick to your custom System property using Conditional Bean Configuration.
In this approach you would need two custom Conditions to encapsulate your conditions:
public class MockFSCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String fsMockProperty = context.getEnvironment().getProperty("com.fs.mock");
return fsMockProperty!=null && fsMockProperty.toLowerCase().equals("true");
}
}
public class RestFSCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String fsMockProperty = context.getEnvironment().getProperty("com.fs.mock");
return fsMockProperty==null || !fsMockProperty.toLowerCase().equals("true");
}
}
In which case your configuration class would look like this:
#Configuration
#ComponentScan(basePackages = {"your.beans.package"})
public class Config {
#Bean
public FSClient mockFSClient(){
return new MockFSClient();
}
#Bean
public FSClient restFSClient(){
return new RestFSClient();
}
#Bean(name="fsManager")
#Conditional(MockFSCondition.class)
public FSManager mockFsManager() {
return new FSManager(mockFSClient());
}
#Bean(name="fsManager")
#Conditional(RestFSCondition.class)
public FSManager restFsManager() {
return new FSManager(restFSClient());
}
}
Notice that we have created two beans with the same name fsManager each one of which is annotated with the two conditions above.
So now at runtime Spring would look for that custom System prop and given whether it's there/correct or not it will instantiate the correct version of FSManager constructor-injected with the right FSClient.

Injecting a service via a InvocationHandler

Is there a clean way in Spring (with no XML) to have an interface wired to an invocation handler? Currently I have to do something like this:
#Inject
private ServiceProxyCreator services;
private MyServiceInterface service;
private MyServiceInterface getService() {
if ( service == null )
service = services.createProxy( MyServiceInterface.class );
return service;
}
Where #createProxy is simply an implementation of something like this:
#SuppressWarnings( "unchecked" )
public <T> T createProxy( Class<T> type ) {
JobRpcHandler handler = new JobRpcHandler();
handler.setServiceName( type.getSimpleName() );
return (T) Proxy.newProxyInstance(
type.getClassLoader(), new Class[]{type}, handler );
}
But with all this DI functionality in Spring it seems like I should be able to do this all automatically so that I can simply do the following:
#Inject
private MyService service;
With the injection customized in some way that I don't know to create the Proxy behind the scenes without having to call #createProxy.
Any suggestions on a more elegant approach?
Take a look at FactoryBean. You can write your own this way:
public class ServiceProxyFactoryBean implements FactoryBean<Object>
private Class<T> type;
public DutySetFactoryBean(Class<?> type) {
this.type = type;
}
#Override
public synchronized Object getObject() {
JobRpcHandler handler = new JobRpcHandler();
handler.setServiceName(type.getSimpleName());
return Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, handler);
}
#Override
public Class<?> getObjectType() {
return type;
}
#Override
public boolean isSingleton() {
return true;
}
}
and use it in your configuration file:
<bean class="package.name.ServiceProxyFactoryBean">
<constructor-arg>
<value type="java.lang.Class">package.name.MyServiceInterface</value>
</constructor-arg>
</bean>
or, using Java configuration, that way:
#Bean
public ServiceProxyFactoryBean myServiceFactoryBean() {
return new ServiceProxyFactoryBean(MyServiceInterface.class);
}
#Bean
public MyServiceInterface myService() {
return (MyServiceInterface)sessionFactoryBean().getObject();
}
If you want automatically create proxies for all the annotated interfaces in a classpath, you can define your own BeanDefinitionRegistryPostProcessor. Here you must scan your classpath with ResourceLoader using the following pattern:
MetadataReaderFactory metadataReaderFactory = new SimpleMetadataReaderFactory();
Resource[] resources = patternResolver.getResources(
"classpath*:" + packageName.replace('.', '/') + "/**/*.class");
for (Resource resource : resources) {
MetadataReader reader = metadataReaderFactory.getMetadataReader(resource);
if (!reader.getAnnotationMetadata().isAnnotated(
MyProxyAnnotation.class.getName())) {
continue;
}
Class<?> cls = Class.forName(reader.getClassMetadata().getClassName(), true,
resourceLoader.getClassLoader());
String factoryBeanName = createNewName();
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(
ServiceProxyFactoryBean.class);
bdb.addConstructorArgValue(cls);
registry.registerBeanDefinition(factoryBeanName, bdb.getBeanDefinition());
bdb = BeanDefinitionBuilder.genericBeanDefinition(cls);
bdb.setFactoryBean(factoryBeanName, "getBean");
registry.registerBeanDefinition(createNewName(), bdb.getBeanDefinition());
}
Now, for all interfaces, annotated with MyProxyAnnotation, you have a proxy, which you can inject into your beans. For example:
#MyProxyAnnotation
public interface MyServiceInterface {
void foo();
}
And
#Component
public class MyBean {
#Autowired
private MyServiceInterface myService;
}
That's all. No configuration needed.
I am not sure this code works or even compiles. It not the final solution, just a general way you should move toward. So you should research and debug a little.

Categories

Resources