I am trying to understand the sequence of static initialization, instance initialization, InitializingBean and BeanPostProcessor, so I created below example, everything is working except that BeanPostProcessor are not getting invoked, am I missing something?
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.stereotype.Component;
#Component
public class SpringTest {
#Autowired
Person person;
public static void main(String[] args) {
testApplicationContext();
}
private static void testApplicationContext() {
// here static and instance initializers of Person class will be invoked right away, even when we are not calling getBean method
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("C:\\E_Drive\\Projects\\Workspace\\Test\\CS101\\src\\com\\learn\\stackoverflow\\general\\bean.xml");
ApplicationContext applicationContext2 = new FileSystemXmlApplicationContext("C:\\E_Drive\\Projects\\Workspace\\Test\\CS101\\src\\com\\learn\\stackoverflow\\general\\bean.xml");
// notice that above you are creating 2 ioc containers, and since they are different ioc containers so it is possible to have 2 singletons in JVM.
SpringTest springTest = (SpringTest) applicationContext.getBean("springTest");
}
}
Person class:
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
#Component
#Scope(value="singleton")
public class Person implements InitializingBean, BeanPostProcessor {
#Autowired
SpringTest springTest;
static{
System.out.println("Static initialization");
}
{
System.out.println("Instance initialization");
}
public void sayHello(){
System.out.println("Hello");
}
#Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean.afterPropertiesSet");
}
#Override
public Object postProcessAfterInitialization(Object arg0, String arg1) throws BeansException {
System.out.println("BeanPostProcessor.postProcessAfterInitialization : " + arg1);
return arg0;
}
#Override
public Object postProcessBeforeInitialization(Object arg0, String arg1) throws BeansException {
System.out.println("BeanPostProcessor.postProcessBeforeInitialization : " + arg1);
return arg0;
}
}
Bean XML file:
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.learn.stackoverflow.general"/>
<context:annotation-config />
<!-- <bean id = "person" class = "com.learn.stackoverflow.general.Person" scope="singleton">
</bean> -->
<bean id = "springTest" class = "com.learn.stackoverflow.general.SpringTest" scope="singleton">
</bean>
</beans>
Output:
Static initialization
Instance initialization
InitializingBean.afterPropertiesSet
Instance initialization
InitializingBean.afterPropertiesSet
Is there anything else required for BeanPostProcessor methods to be invoked?
A bean cannot self-post-process, the post-processor needs to be a separate bean; see tutorial.
Related
I'm learning the bean AOP with Annotation and xml config but Spring does not create the beans.
Interface
package com.spring.studying.SpringAop.FrameworkService.AspectJAnnotation;
public interface Artist {
void perform();
}
Guitar Class
package com.spring.studying.SpringAop.FrameworkService.AspectJAnnotation;
import org.springframework.stereotype.Component;
#Component("grammyGuitarist")
public class GrammyGuitarist implements Artist{
#Override
public void perform() {
System.out.println("Start to play guitar");
}
public void playGuitar(String brand)
{
System.out.println("Get Guitar "+brand);
}
public void rest()
{
System.out.println("zzZZ!!!!");
}
}
Documentarist
package com.spring.studying.SpringAop.FrameworkService.AspectJAnnotation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component(value="documentaries")
public class Documentarist {
private GrammyGuitarist guitarist;
public void executed()
{
guitarist.playGuitar("LakeWood");
guitarist.perform();
guitarist.rest();
}
#Autowired
public void setGuitarist(GrammyGuitarist guitarist) {
this.guitarist = guitarist;
}
}
Advice Class
package com.spring.studying.SpringAop.FrameworkService.AspectJAnnotation;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Component("annotatedAdvice")
#Aspect
public class AnnotatedAdvice {
#Pointcut("execution(* rest*(..)) && args(brand)")
public void restExecution(String brand)
{
}
#Pointcut("bean(grammyGuitarist)")
public void isGrammyGuitarist()
{
}
#Before("restExecution(brand) && isGrammyGuitarist")
public void simpleBeforeAdvice(JoinPoint joinPoint , String brand)
{
System.out.println("Before Advice processing");
System.out.println("Playing guitar :" + brand);
System.out.println("Executing :"+ joinPoint.getSignature().getDeclaringTypeName() +" "+joinPoint.getSignature().getName());
}
}
Demo Class
package com.spring.studying.SpringAop.FrameworkService.AspectJAnnotation;
import org.springframework.context.support.GenericXmlApplicationContext;
import java.util.Arrays;
public class AspectJAnnotationDemo {
public static void main(String[] args) {
GenericXmlApplicationContext ctx = new GenericXmlApplicationContext();
ctx.load("aop-aspectj-annotation-style.xml");
ctx.refresh();
Arrays.stream(ctx.getBeanDefinitionNames()).forEach(System.out::println);
Documentarist documentarist = ctx.getBean("documentaries",Documentarist.class);
documentarist.executed();
}
}
My Xml Config File
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
">
<context:annotation-config/>
<context:component-scan base-package="com.spring.studying.SpringAop"/>
<aop:aspectj-autoproxy/>
</beans>
None of my beans were created just 6 default beans from spring.
I have try to create bean with tag and it worked fine . Just don't know why my code did not work with the annotation.
restExecution #Pointcut is wrong, because rest method of GrammyGuitarist doesn't take any arguments. playGuitar method takes brand argument, so you need to change your pointcut a bit:
#Component("annotatedAdvice")
#Aspect
public class AnnotatedAdvice {
#Pointcut("execution(* play*(..)) && args(brand)")
public void playExecution(String brand) {
}
#Pointcut("bean(grammyGuitarist)")
public void isGrammyGuitarist() {
}
#Before("playExecution(brand) && isGrammyGuitarist()")
public void simpleBeforeAdvice(JoinPoint joinPoint, String brand) {
System.out.println("Before Advice processing");
System.out.println("Playing guitar :" + brand);
System.out.println("Executing :" + joinPoint.getSignature().getDeclaringTypeName() + " " + joinPoint.getSignature().getName());
}
}
When you do this, aspects will indeed be created, but you will start getting exceptions:
Bean named 'grammyGuitarist' is expected to be of type 'com.example.demo.aspect.GrammyGuitarist' but was actually of type 'jdk.proxy2.$Proxy14'
This is because GrammyGuitarist implements an interface, and in this case Spring AOP uses JDK dynamic proxies
The simplest solution would be to force the use of a CGLIB proxy. To do this, you need to add an annotation above the aspect:
#EnableAspectJAutoProxy(proxyTargetClass = true)
After that, the program will give the expected result:
annotatedAdvice
documentaries
grammyGuitarist
org.springframework.aop.config.internalAutoProxyCreator
Before Advice processing
Playing guitar :LakeWood
Executing :com.example.demo.aspect.GrammyGuitarist playGuitar
Get Guitar LakeWood
Start to play guitar
zzZZ!!!!
In Spring Boot, CGLIB enforcement is enabled by default. Check this question for better understanding
I am looking at some existing application code and I am confuse because of the code over there, here is the scenario:
In the class ABC, I have autowiring as:
#Autowired
RestTemplate restTemplate;
Then in my spring bean config file, I bean definition as:
<bean id="restTemplate" class="org.springframework.web.client.RestTemplate"/>
In the class ABC I am using rest template as:
restTemplate.postForObject(url, requestObject, String.class);
My questions are:
when I am using the rest template then the object which I am using is the one which is because of autowiring or the one which is defined in bean config file?
In case of autowiring since I have not specified the scope, so it will be singleton, right?
As per comments and answers, below is minimalistic reproducible code which shows that even when I have auto-wiring and bean definition in bean config file, I can run the program and there are no issues.
Main class:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.stereotype.Component;
#Component
public class SpringTest {
#Autowired
Person person;
public static void main(String[] args) {
generalTest();
}
private static void generalTest() {
testApplicationContext();
}
private static void testApplicationContext() {
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("bean.xml");
SpringTest springTest = (SpringTest) applicationContext.getBean("springTest");
if(springTest.person == null){
System.out.println("person is NULL");
} else{
System.out.println("person is not NULL");
}
}
}
Bean file:
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.learn.stackoverflow.general"/>
<!-- as such below is useless because The use of <context:component-scan> implicitly enables the functionality of <context:annotation-config>.
There is usually no need to include the <context:annotation-config> element when using <context:component-scan>. -->
<context:annotation-config />
<bean id = "person" class = "com.learn.stackoverflow.general.Person" scope="singleton">
</bean>
</beans>
Person class:
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
#Component
#Scope(value="singleton")
public class Person implements InitializingBean, Human {
#Autowired
SpringTest springTest;
static{
System.out.println("Static initialization from Person");
}
{
System.out.println("Instance initialization from Person");
}
public Person(){
System.out.println("Constructor from Person");
}
public void sayHello(){
System.out.println("Hello");
}
#Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean.afterPropertiesSet from Person");
}
#Override
public void breathe() {
System.out.println("person is breathing...");
}
}
Output is person is not NULL
The config file tells Spring to create a bean with an id of "restTemplate" which is of type org.springframework.web.client.RestTemplate. The default scope is a single instance of the bean.
Then in the code, since there exists a bean of type RestTemplate, the bean (which happens to have an id of "restTemplate" is auto wired.
"restTemplate" as an id or a class attribute that also has "restTemplate" as its name is not important. The bean can be injected since it's of same type. (And also beceause there's only 1 of that type.)
#Autowired doesn't create bean, it will take the existing bean declaration that match with the expected type and put it in the field. In the XML you have your bean declaration, without it your bean won't exist (unless you are using Spring Boot, in which case it might get automatically created if it doesn't exists).
You can use now #Inject instead of #Autowired since JSR-299 and if you have a recent version of Spring and javax.inject dependencies. Both do exactly the same thing but #Inject is clearer.
When IoC container finds a bean definition defined in the bean config file then same definition is used for injecting the instance, and if IoC container doesn't find one then it creates an instance and injects the same.
Below is a little tweaked example which demonstrates above concept, you can play around with it my commenting and uncommenting the bean definition mentioned in bean config file. Please read carefully my code comments.
SpringTest:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import org.springframework.stereotype.Component;
#Component
public class SpringTest {
#Autowired
Person person;
public static void main(String[] args) {
generalTest();
}
private static void generalTest() {
testApplicationContext();
}
private static void testApplicationContext() {
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("C:\\E_Drive\\Projects\\Workspace\\Test\\CS101\\src\\com\\learn\\stackoverflow\\general\\bean.xml");
SpringTest springTest = (SpringTest) applicationContext.getBean("springTest");
if(springTest.person == null){
System.out.println("person is NULL");
} else{
System.out.println("person is not NULL");
springTest.person.sayHello(); // here output will be “Hello, ppj” if you keep the config file bean definition and when you remove that bean definition then output is “Hello, null”
}
}
}
Bean config:
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.learn.stackoverflow.general"/>
<!-- once you get comment out below, IoC will instatiate a bean and will inject the same in SpringTest's autowiring of Person class -->
<!-- <bean id = "person" class = "com.learn.stackoverflow.general.Person" scope="singleton">
<constructor-arg name="name" value="ppj"></constructor-arg>
</bean> -->
</beans>
Person:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
#Component
#Scope(value="singleton")
public class Person {
private String name;
#Autowired
SpringTest springTest;
public Person(){
System.out.println("Constructor from Person");
}
public Person(String name){
System.out.println("String Constructor from Person");
this.name = name;
}
public void sayHello(){
System.out.println("Hello, " + name);
}
}
I am getting familiar with Spring. Strangely code below invokes constructor twice whereas I expected it to be called once. Can someone help?
package com.tutorialspoint;
import java.util.List;
import org.springframework.context.event.ContextStartedEvent;
import org.springframework.context.ApplicationListener;
public class DemoClass implements ApplicationListener<ContextStartedEvent> {
private String message;
private int nrOfMessages;
public DemoClass(String mes, int nr) {
message = mes;
nrOfMessages = nr;
System.out.println("Demo class constructor. Parameters: " + mes + " " + nr);
}
// a setter method to set List
public void setNrOfMessages(int nr) {
this.nrOfMessages = nr;
}
// Message setter
public void setMessage(String message) {
this.message = message;
}
// prints and returns all the elements of the list.
public void dumpContents() {
System.out.println("Message: " + message + " Nr of messages: " + nrOfMessages);
}
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("ContextStartedEvent Received");
}
}
The bean and main:
package com.tutorialspoint;
import java.io.FileNotFoundException;
import java.util.List;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
public class MainApp {
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
context.start();
// Create an object
DemoClass obj = (DemoClass) context.getBean("democlassBean");
// Dump contents
obj.dumpContents();
}
}
bean
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<bean id="democlassBean" class="com.tutorialspoint.DemoClass"
scope="prototype">
<constructor-arg value="Hello world beans."/>
<constructor-arg value="300"/>
</bean>
<bean class="com.tutorialspoint.InitHelloWorld"></bean>
</beans>
This is the output:
Demo class constructor. Parameters: Hello world beans. 300
BeforeInitialization : democlassBean
AfterInitialization : democlassBean
ContextStartedEvent Received
Demo class constructor. Parameters: Hello world beans. 300
BeforeInitialization : democlassBean
AfterInitialization : democlassBean
Message: Hello world beans. Nr of messages: 300
You can see constructor is being called twice - why???
Here is also code for initHelloWorld:
package com.tutorialspoint;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.BeansException;
public class InitHelloWorld implements BeanPostProcessor {
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("BeforeInitialization : " + beanName);
return bean; // you can return any other object as well
}
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("AfterInitialization : " + beanName);
return bean; // you can return any other object as well
}
}
It seems, it is not good idea to have prototype bean to act as ApplicationListener.
Check http://forum.spring.io/forum/spring-projects/container/35965-applicationlistener-interface-makes-beans-eagerly-instantiated
for details.
You can make it work, but with some exta steps are required (described in lined post). Note that linked post is somewhat old (year 2007) and it is likely that some of the implementation details covered there are not valid anymore.
If you really care about number of instances being created- what about creating two classes- one as prototype, and another (singlelton) that acts as ApplicationListener?
Friends below is my code, I am trying to run dependency Injection with Spring
I have an interface, two class implementations of that interface.
One bean.xml and One main method class.
Interface IWriter.java
package DI;
public interface IWriter {
public void writer(String s);
}
Class Writer.java
package DI;
import org.springframework.stereotype.Service;
#Service
public class Writer implements IWriter {
public void writer (String s){
System.out.println(s);
}
}
Class NiceWriter.java
package DI;
import org.springframework.stereotype.Service;
#Service
public class NiceWriter implements IWriter {
public void writer (String s){
System.out.println("The string is " + s);
}
}
Another class
package DI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
#Service
public class MySpringBeanWithDependency {
#Autowired
private IWriter writer;
public void run() {
String s = "This is my test";
writer.writer(s);
}
}
Main.java
package DI;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import DI.MySpringBeanWithDependency;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
BeanFactory factory = context;
MySpringBeanWithDependency test = (MySpringBeanWithDependency) factory.getBean("mySpringBeanWithDependency");
test.run();
}
}
bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="DI" />
</beans>
When I run the code Spring container gives the output of the method of Writer.java class. I haven't anywhere specified which implementation to pick. How is Spring picking up the implementation of Writer.java??
change your code as follows.
Class Writer.java
package DI;
import org.springframework.stereotype.Service;
#Service("writer")
public class Writer implements IWriter {
public void writer (String s){
System.out.println(s);
}
}
Class NiceWriter.java
package DI;
import org.springframework.stereotype.Service;
#Service("niceWriter")
public class NiceWriter implements IWriter {
public void writer (String s){
System.out.println("The string is " + s);
}
}
Another class
package DI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
#Service
public class MySpringBeanWithDependency {
#Autowired
#Qualifier("writer")//if you need to autowire Writer service
private IWriter writer;
#Autowired
#Qualifier("niceWriter")//if you need to autowire NiceWriter service
private IWriter niceWriter
public void run() {
String s = "This is my test";
writer.writer(s);
}
}
When there is more than one implementation of interface and you use #Autowired in that case spring bind any of the class. but if you want to autowire specific implementation then you can use
#Qualifier( "<implementing class name>" )
#Qualifier documentation
Few things that you must know about Spring is
All spring beans are managed - they "live" inside a container, called "application context".
Each application has an entry point to that context. Also, there is a place where the application context is bootstrapped and all beans - autowired. In web applications this can be a startup listener.
Autowiring happens by placing an instance of one bean into the desired field in an instance of another bean. Both classes should be beans, i.e. they should be defined to live in the application context.
Try this one.
Class Writer.java
package DI;
import org.springframework.stereotype.Service;
#Service("writer")
public class Writer implements IWriter {
public void writer (String s){
System.out.println(s);
}
}
Class NiceWriter.java
package DI;
import org.springframework.stereotype.Service;
#Service("niceWriter")
public class NiceWriter implements IWriter {
public void writer (String s){
System.out.println("The string is " + s);
}
}
Another class
package DI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
#Service
public class MySpringBeanWithDependency {
#Autowired
private IWriter writer;
#Autowired
private IWriter niceWriter
public void run() {
String s = "This is my test";
writer.writer(s);
}
}
I'd like to show one more option using application.properties.
Benefits:
You don't need to change code when you add/change implementation of the interface
Works well with unit tests and other environments
Sample code based on #ConditionalOnProperty attribute
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
#Service
#ConditionalOnProperty(value = "writer.type", havingValue = "default")
public class Writer implements IWriter {
#Override
public void writer(String s) {
System.out.println("The string is " + s);
}
}
And
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
#Service
#ConditionalOnProperty(value = "writer.type", havingValue = "nice")
public class NiceWriter implements IWriter {
#Override
public void writer(String s) {
System.out.println("Nice string is " + s);
}
}
When application.properties contains writer.type=nice NiceWriter will be instantiated for IWriter interface.
Instead of #ConditionalOnProperty there are other options like Conditional, #ConditionalOnExpression.
It depends on the use-case, you can select implementation of the interface using spring profiles, custom annotations or as mentioned by other answers using #Qualifier (injection by name) which is equivalent to JSR-330's #Named annotation
I have this situation:
I have one interface Service which aggregates all service interfaces. So for example if I have two interfaces ILoginService1 and ILoginService2 the Service interface looks like this
Service extends ILoginService1,ILoginService2.
I need this interface to be accessible in a given context like this:
service.login();
This is my solution (something similar to http://artofsoftwarereuse.com/tag/dynamic-proxy/):
I create one annotation ServiceFacade, which I put on Service interface, then I have BeanPostProcessor in which I create DynamicProxy for the Service interface.
But the problem is that Service interface isn't pick up from spring component scan, even in the case I put #Component on it, but other components are put in Spring container.
How can I fix my solution so far or I'm missing something or is there other solutions?
Here is source code:
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config/>
<context:component-scan base-package="org.finki.auction.ui.application"/>
<context:component-scan base-package="org.finki.auction.services"/>
</beans>
Annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface ServiceFacade{}
Invocation Handler for Dynamic Proxy:
/**
*
*/
package org.finki.auction.services;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
/**
*
*/
#Component("serviceLayer")
public class ServiceLayer implements InvocationHandler, ApplicationContextAware
{
private static ApplicationContext applicationContext = null;
private static Map<String, String> serviceMap = new HashMap<>();
#Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
{
Object result;
try
{
String searchKey = method.getName();
String beanName = serviceMap.get(searchKey);
Object methodObject = applicationContext.getBean(beanName);
result = method.invoke(methodObject, args);
} catch (InvocationTargetException e)
{
throw e.getTargetException();
} catch (Exception e)
{
throw new RuntimeException("unexpected invocation exception: " + e.getMessage());
}
return result;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
ServiceLayer.applicationContext = applicationContext;
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Service.class);
for (Map.Entry<String, Object> entryBean : beans.entrySet())
{
String beanName = entryBean.getKey();
Object beanObject = entryBean.getValue();
Method[] beanMethods = beanObject.getClass().getDeclaredMethods();
for (Method bMethod : beanMethods)
{
serviceMap.put(bMethod.getName(), beanName);
}
}
}
}
BeanPostProcessor class:
/**
*
*/
package org.finki.auction.services.annotation;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import org.finki.auction.services.Service;
import org.finki.auction.services.ServiceLayer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
*
*/
#Component("serviceFacadeProcessor")
public class ServiceFacadeProcessor implements BeanPostProcessor, ApplicationContextAware
{
private static ApplicationContext applicationContext = null;
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException
{
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException
{
Class<?> clz = bean.getClass();
Class<?>[] tmpInterfaces = clz.getInterfaces();
System.out.println("ServiceFacadeProcessor : " + bean);
if (tmpInterfaces != null && tmpInterfaces.length == 1
&& tmpInterfaces[0].isAnnotationPresent(ServiceFacade.class))
{
System.out.println("Find serviceFacade >>>>");
Class<?>[] interfaces = Arrays.copyOf(tmpInterfaces, tmpInterfaces.length + 1);
interfaces[tmpInterfaces.length] = Service.class;
ClassLoader cl = bean.getClass().getClassLoader();
ServiceLayer serviceLayerBean = applicationContext.getBean("serviceLayer", ServiceLayer.class);
Object t = Proxy.newProxyInstance(cl, interfaces, serviceLayerBean);
System.out.println("Find serviceFacade <<<<");
return t;
}
return bean;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
ServiceFacadeProcessor.applicationContext = applicationContext;
}
}
So, my problem is not the configuration, my problem is how to attach Service interface to spring container in order to be caught by BeanPostProcessor and create dynamic proxy for it. It's is my solution so far maybe I'm missing something, but if someone have better way doing it, just let me now.
Thanks in advance
Solution:
/**
*
*/
package org.finki.auction.services.annotation;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import org.finki.auction.services.Service;
import org.finki.auction.services.ServiceLayer;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
/**
* #author
*
*/
#Component
public class ServiceFactoryBean implements FactoryBean<Service>, ApplicationContextAware
{
private static ApplicationContext applicationContext = null;
#Override
public Service getObject() throws Exception
{
Class<?>[] tmpInterfaces = Service.class.getInterfaces();
Class<?>[] interfaces = Arrays.copyOf(tmpInterfaces, tmpInterfaces.length + 1);
interfaces[tmpInterfaces.length] = Service.class;
ServiceLayer serviceLayerBean = applicationContext.getBean("serviceLayer", ServiceLayer.class);
ClassLoader cl = serviceLayerBean.getClass().getClassLoader();
Object t = Proxy.newProxyInstance(cl, interfaces, serviceLayerBean);
return (Service) t;
}
#Override
public Class<?> getObjectType()
{
return Service.class;
}
#Override
public boolean isSingleton()
{
return true;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
ServiceFactoryBean.applicationContext = applicationContext;
}
}
Also need to delete BeanPostProcessor and annotation.
I run into something similar and believe you can get your scenario working using Spring's Java Configuration feature.
#Configuration
public class ServiceConfiguration {
// you can wire your service1 and service2 here
#Bean
Service service() {
// create and return dynamic proxy here
}
}
This way you will end up with a bean of type 'Service' and name 'service' which will be your dynamic proxy with invocation handler etc.
I'm sure Java Configuration will not limit you to the approach outlined above (where you wire your service1 and service2 into the config) - methinks that is implementation detail.