When i use ProxyFactoryBean to get proxy object, I get a ClassCastException,but when i use ProxyFactory's getProxy() to get a proxy object, it works properly. I use Spring 4.x.
Definition of Two Beans,WaiterTest and Seller:
public class WaiterTest {
public void greetTo(String name){
System.out.println("waiter greet to " + name +"...");
}
public void serveTo(String name){
System.out.println("waiter serving " + name + "...");
}
}
public class Seller {
public void greetTo(String name){
System.out.println("seller greet to " +name + "...");
}
}
Definition of Advice:
public class GreetingBeforeAdvice implements MethodBeforeAdvice{
public void before(Method method, Object[] args, Object obj) throws Throwable{
System.out.println(obj.getClass().getName() + "." + method.getName());
String clientName =(String) args[0];
System.out.println("How are you! Mr." + clientName +".");
}
}
Definition of Advisor:
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
public boolean matches(Method method, Class clazz) {
return "greetTo".equals(method.getName());
}
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class clazz) {
return WaiterTest.class.isAssignableFrom(clazz);
}
};
}
}
Test Class:
public class TestGreetingBeforeAdvisor {
public static void main(String[] args) {
//method one: use by ProxyFactory
WaiterTest targetWaiterTest = new WaiterTest();
Seller targetSeller = new Seller();
GreetingBeforeAdvice advice = new GreetingBeforeAdvice();
GreetingAdvisor advisor = new GreetingAdvisor();
advisor.setAdvice(advice);
ProxyFactory pf = new ProxyFactory();
pf.setTarget(targetWaiterTest);
pf.addAdvisor(advisor);
pf.setOptimize(true);
WaiterTest proxy = (WaiterTest) pf.getProxy();
proxy.greetTo("John");
proxy.serveTo("Tom");
ProxyFactory pf1 = new ProxyFactory();
pf1.setTarget(targetSeller);
pf1.addAdvisor(advisor);
Seller seller = (Seller) pf1.getProxy();
seller.greetTo("John");
System.out.println("=============");
//method two:Spring xml,use ProxyFactoryBean
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
WaiterTest test = (WaiterTest) ctx.getBean("waiterTest");
test .greetTo("John");
test .serveTo("John");
}
}
Beans.xml:
<bean id="waiterTarget" class="com.xxx.springaop.advisor.WaiterTest"/>
<bean id="sellerTarget" class="com.xxx.springaop.advisor.Seller"/>
<bean id="greetingAdvice1" class="com.xxx.springaop.advisor.GreetingBeforeAdvice"/>
<bean id="greetingAdvisor" class="com.xxx.springaop.advisor.GreetingAdvisor"
p:advice-ref="greetingAdvice1"/>
<bean id="parent" abstract="true" class="org.springframework.aop.framework.ProxyFactoryBean"
p:interceptorNames="greetingAdvisor"
p:proxyTargetClass="true"/>
<bean id="waiterTest" parent="parent" p:target-ref="waiterTarget"/>
<bean id="seller" parent="parent" p:target-ref="sellerTarget"/>
result:
com.xxx.springaop.advisor.WaiterTest.greetTo
How are you! Mr.John.
waiter greet to John...
waiter serving Tom...
seller greet to John...
=============
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy12 cannot be cast to com.xxx.springaop.advisor.WaiterTest
at com.xxx.springaop.advisor.TestGreetingBeforeAdvisor.main(TestGreetingBeforeAdvisor.java:48)
Summarize:
WaiterTest proxy = (WaiterTest) pf.getProxy(); //success
WaiterTest test = (WaiterTest) ctx.getBean("waiterTest");//fail,ClassCastException
Why?
I was not able to reproduce your problem, but I did some refactoring on your code, and this works just fine for me. Maybe it helps you as well at least as a starting point..
Waiter.java
public class Waiter {
public void greetTo(String name) {
System.out.println("waiter greet to " + name + "...");
}
public void serveTo(String name) {
System.out.println("waiter serving " + name + "...");
}
}
GreetingBeforeAdvice.java
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class GreetingBeforeAdvice implements MethodBeforeAdvice {
#Override
public void before(Method method, Object[] args, Object obj) throws Throwable {
String clientName = (String) args[0];
System.out.println("How are you! Mr." + clientName + ".");
}
}
GreetingAdvisor.java
import org.springframework.aop.ClassFilter;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import java.lang.reflect.Method;
public class GreetingAdvisor extends StaticMethodMatcherPointcutAdvisor {
public boolean matches(Method method, Class clazz) {
return "greetTo".equals(method.getName());
}
public ClassFilter getClassFilter() {
return new ClassFilter() {
public boolean matches(Class clazz) {
return Waiter.class.isAssignableFrom(clazz);
}
};
}
}
beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="waiter" class="Waiter"/>
<!-- Advisor Configuration -->
<bean id="greetingAdvice" class="GreetingBeforeAdvice"/>
<bean id="greetingAdvisor" class="GreetingAdvisor">
<property name="advice" ref="greetingAdvice"/>
</bean>
<bean id="waiterProxyFactory" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="interceptorNames">
<list>
<value>greetingAdvisor</value>
</list>
</property>
<property name="target" ref="waiter"/>
</bean>
</beans>
and finally App.java
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class App {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
final Waiter waiter = (Waiter) ctx.getBean("waiterProxyFactory");
waiter.greetTo("Koray Tugay");
waiter.serveTo("Koray Tugay");
}
}
The output for me is as follows using Spring 4.3.12:
How are you! Mr.Koray Tugay.
waiter greet to Koray Tugay...
waiter serving Koray Tugay...
I would say you have not configured 'target' object in you ProxyFactoryBean definition. A proxy cannot be created if it does not have a target.
Related
I'm currently experimenting on bean callback methods and noticed that if I define callback methods for one bean, these methods are also called on other beans.
I have a classes named x.HelloWorld (no callbacks) and y.HelloWorld (has init and destroy callback methods).
HelloWorld.java:
public class HelloWorld {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("Your Message : " + message);
}
}
HelloWorld2.java:
public class HelloWorld {
private String message;
public void setMessage(String message) {
this.message = message;
}
public void getMessage() {
System.out.println("Your Message : " + message);
}
public void init() {
System.out.println("Bean is going through init.");
}
public void destroy() {
System.out.println("Bean will destroy now.");
}
}
x.MainApp.java:
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.getMessage();
}
}
y.MainApp.java:
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
AbstractApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld2");
obj.getMessage();
context.registerShutdownHook();
}
}
beans.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-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<bean id="helloWorld" class="x.HelloWorld">
<property name="message" value="Hello World (1)" />
</bean>
<bean id="helloWorld2" class="y.HelloWorld"
init-method="init" destroy-method="destroy">
<property name="message" value="Hello World (2)" />
</bean>
</beans>
Results:
Running y.MainApp (works as expected):
Bean is going through init.
Your Message : Hello World (2)
Bean will destroy now.
Running x.MainApp (the init callback of x.HelloWorld is triggered by y.HelloWorld. Confused of this one.. any help is very much appreciated...)
Bean is going through init.
Your Message : Hello World (1)
Please modify your init method like this:
public void init() {
System.out.println("Bean with message '"+ message +"' is going through init.");
}
You will see that all beans are initialized at startup and that the message you see actually comes from the class with the init method.
If you don't want this behavior you can use lazy init:
<bean id="helloWorld2" class="y.HelloWorld"
init-method="init" destroy-method="destroy" lazy-init="true">
I want to inject bean in a Tapestry service (not in a page).
For the moment, I use this :
public class EntityRealm extends AuthorizingRealm {
ApplicationContext ctx = new ClassPathXmlApplicationContext("/application-context-security.xml");
SecurityServices securityServices = (SecurityServices)ctx.getBean("securityServices");
It works, but I want use this :
public class EntityRealm extends AuthorizingRealm {
#Inject
private SecurityServices securityServices;
And my applicationContext is in the web.xml.
In this second case, the injection doesn't work. Why ?
AppModule.java :
public class AppModule
{
//#Resource(name = "realm")
#Inject
private static EntityRealm realm;
#Contribute(WebSecurityManager.class)
public static void addRealms(Configuration<EntityRealm> configuration) {
//EntityRealm realm = new EntityRealm();
configuration.add(realm);
}
public static void contributeFactoryDefaults( MappedConfiguration<String, Object> configuration)
{
configuration.override(SecuritySymbols.LOGIN_URL, "/login");
configuration.override(SecuritySymbols.UNAUTHORIZED_URL, "/login");
configuration.override(SecuritySymbols.SUCCESS_URL, "/index");
configuration.override(SymbolConstants.APPLICATION_VERSION, "2.0-SNAPSHOT");
}
public static void contributeApplicationDefaults(MappedConfiguration<String, Object> configuration)
{
configuration.add(SymbolConstants.HMAC_PASSPHRASE, new BigInteger(130, new SecureRandom()).toString(32));
configuration.add(SymbolConstants.SUPPORTED_LOCALES, "en,fr");
configuration.add( "tapestry.default-cookie-max-age", "31536000" );
}
public RequestFilter buildTimingFilter(final Logger log)
{
return new RequestFilter()
{
public boolean service(Request request, Response response, RequestHandler handler)
throws IOException
{
long startTime = System.currentTimeMillis();
try
{
return handler.service(request, response);
} finally
{
long elapsed = System.currentTimeMillis() - startTime;
log.info(String.format("Request time: %d ms", elapsed));
}
}
};
}
public void contributeRequestHandler(OrderedConfiguration<RequestFilter> configuration,
#Local
RequestFilter filter)
{
configuration.add("Timing", filter);
}
}
And the EntityRealm.java :
public class EntityRealm extends AuthorizingRealm {
//***************************************
//************* Attributes *************
//***************************************
//ApplicationContext ctx = new ClassPathXmlApplicationContext("/application-context-security.xml");
//SecurityServices securityServices = (SecurityServices)ctx.getBean("securityServices");
//#Resource(name = "securityServices")
#Inject
private SecurityServices securityServices;
//***************************************
//************ Constructors *************
//***************************************
public EntityRealm() {
super(new MemoryConstrainedCacheManager());
setName("myapprealm");
setAuthenticationTokenClass(UsernamePasswordToken.class);
}
//***************************************
//********** Public Methods *************
//***************************************
#Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
if (principals == null) throw new AuthorizationException("PrincipalCollection was null, which should not happen");
application-context.xml :
<bean id="realm" class="net.atos.m2m.telecom.ihm.services.EntityRealm">
<property name="securityServices" ref="securityServices"></property>
</bean>
<bean id="securityServices" class="net.atos.m2m.telecom.ihm.applicatif.services.security.impl.SecurityServicesImpl">
<property name="servicesTelSecu" ref="servicesTelSecu"></property>
<property name="converterSecDSPtoDTO" ref="converterSecDSPtoDTO"></property>
<property name="converterSecDTOtoDSP" ref="converterSecDTOtoDSP"></property>
</bean>
Can you help me ?
Thank you.
How i say in previous comment, if you create EntityRealm in this way .. new EntityRealm() the inject\autowire does not work.
You must define EntityRealm as bean .. XML or Annotation.
<bean id="entityRealm" class="package.EntityRealm"/>
<bean id="securityServices" class="package.SecurityServices"/>
You can use #Resource instead,
#Resource(name = "securityServices")
private SecurityServices securityServices;
And make sure that application-context-security.xml file is loaded by Spring.
Why my beans is null?
[b]servlet-context.xml [/b]
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns="http://www.springframework.org/schema/beans"
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"
>
<!-- <context:annotation-config/>-->
<context:component-scan base-package="by"/>
<context:property-placeholder location="classpath:database.properties"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/javakava"/>
<property name="username" value="root"/>
<property name="password" value="admin"/>
</bean>
</beans>
[b]controller[/b]
public class Controller extends HttpServlet {
private static final long serialVersionUID = 1L;
#Autowired
private CommandFactory commandFactory;
#Override
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
}
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
performAction(request, response);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
performAction(request, response);
}
private void performAction(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
String page = null;
String paramPage = request.getParameter(Constants.PARAM_PAGE);
try {
if (paramPage != null && !paramPage.isEmpty()) {
Command command = commandFactory.getCommand(paramPage);
page = command.execute(request);
// Commands c = Commands.valueOf(paramPage);
// Command command = c.getCommandClass().newInstance();
page = command.execute(request);
RequestDispatcher requestDispatcher = request
.getRequestDispatcher(page);
requestDispatcher.forward(request, response);
} else {
throw new IllegalAccessError(
"Error with access to class from Controller.java");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
LoginCommand - Here is autowared TestService bean. In IDEA it's look's good. But in debug mode my testService is null..
#Component
public class LoginCommand implements Command {
#Autowired
TestService testService;
public String execute(HttpServletRequest request) {
DaoCheckUserImpl id = new DaoCheckUserImpl();
String pass = request.getParameter(Constants.PASS);
String login = request.getParameter(Constants.LOGIN);
id.checkUser();
String userN = id.getUserN();
String userP = id.getUserP();
String userRole = id.getUserRole();
int userId = id.getUserId();
if (userN.equals(login) & userP.equals(pass) & userRole.equals("admin")) {
/*
*
* Here testService is null[/b]
*
*/
List<Test> tests = testService.getAllTests();
request.setAttribute(Constants.TESTS, tests);
User user = new User();
user.setLogin(login);
request.getSession().setAttribute(Constants.USER, user);
return Constants.MAIN_ADMIN_PAGE;
} else {
}
return Constants.ERROR_LOGIN_PAGE;
}
}
}
TestService
#Service
public class TestService {
#Autowired
public DaoTestImpl daoTestImpl;
public List<Test> getAllTests() {
return daoTestImpl.getAllTests();
}
public Test selectTest(String idTest) {
return daoTestImpl.selectTest(idTest);
}
public void deleteTest(Test test) {
daoTestImpl.deleteTest(test);
}
[b]DaoTestImpl [/b]
Here I using JdbcDaoSupport , datasource injected with constructor.
#Component
public class DaoTestImpl extends JdbcDaoSupport implements DaoTest {
#Autowired
public DaoTestImpl(DataSource dataSource) {
setDataSource(dataSource);
}
...
public List<Test> getAllTests() throws DAOException {
return getJdbcTemplate().query(("SELECT *FROM tests"), rowMapper);
}
CommandFactory
#Component
public class CommandFactory {
#Autowired
public LoginCommand loginCommand;
public Command getCommand(String paramPage) {
Commands command = Commands.valueOf(paramPage.toUpperCase());
switch (command) {
case LOGIN_COMMAND:
return loginCommand;
commands
public enum Commands { LOGIN_COMMAND
/*login_Command(LoginCommand.class),
How do you create LoginCommand object?
Autowired is used by Spring to inject the correct bean. So it works only if LoginCommand is created by Spring. If you performed a NEW, or if you use another framework without a proper integration with Spring, this can explain your issue (for example Jersey 2 without the proper configuration).
EDIT:
By the way, you can had "#Required" annotation. This will not fix your problem, but the new error message can help you to understqnd what happen (in particular it will help to see if LoginCommand object is really created by Spring and if the autowired failed [as I think] because the instance of TestService was NOT found [package naming issue, classloader issue, etc.])
Did you check if all your components are in the "by" package (that is specified in component-scan)?
I am new to Spring and the project which i am in currently is using Spring IOC .
This is the current Setup of my Application , which i tried to represent as Standalone
This below class is started as a Process during server startup
Client.java
public final class Client {
public static void main(String[] args) {
try {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
String[] beans = ctx.getBeanDefinitionNames();
for (String string : beans) {
System.out.println(beans);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
}
context.xml
<!-- <context:component-scan base-package="com.tradeking" /> -->
<context:annotation-config />
<bean id="stream-core" class="com.StreamHandler" scope="singleton" init-method="init">
<constructor-arg>
<ref bean="streamingthread"/>
</constructor-arg>
</bean>
<bean id="streamingthread" class="com.StreamingThread" scope="singleton" >
</bean>
</beans>
StreamHandler.java
package com;
public class StreamHandler {
private StreamingThread streamThread;
public StreamHandler(StreamingThread streamThread) {
this.streamThread = streamThread;
}
public void init() {
this.streamThread.start();
}
}
StreamingThread.java
package com;
import java.util.HashSet;
import java.util.Set;
public class StreamingThread extends Thread {
private Set<String> streamSet = new HashSet();
public void run() {
while (true) {
for (int i = 0; i < 12; i++) {
streamSet.add("Test" + 1);
System.out.println("Run Called");
}
}
}
}
Now my question is that , i have got another class by name UbscHandler in which i need access to the streamSet which is present inside StreamingThread class
So i tried this way
added one more bean id inside the context.xml file as shown
<context:annotation-config />
<bean id="streamingthreadnew" class="com.StreamingThread" scope="singleton" >
#Autowired
#Qualifier("streamingthreadnew")
private StreamingThread sThread;
I am facing 2 issues here
i am not able to access the streamSet as it is private .
2.Instead of creating one more bean id can i get access to that particular class using Spring style .
Edited Part
Right now i am getting
Hi Streamed Thread Called0
java.lang.NullPointerException
at com.Client.anotherMethod(Client.java:33)
at com.Client.main(Client.java:26)
this is my context.xml
<bean id="streamingthread" class="com.StreamingThread" scope="singleton" >
</bean>
</beans>
**This is StreamingThread**
package com;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class Client{
#Autowired
#Qualifier("streamingthread")
private StreamingThread streamingthread;
public void run()
{
while(true)
{
}
}
public static void main(String[] args) {
try {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
String[] beans = ctx.getBeanDefinitionNames();
for (String string : beans) {
}
Client c = new Client();
c.anotherMethod();
} catch (Throwable t) {
t.printStackTrace();
}
}
public void anotherMethod()
{
Set set = streamingthread.getData();
System.out.println(set.size());
}
}
This is my StreaminThread class
package com;
import java.util.HashSet;
import java.util.Set;
public class StreamingThread extends Thread {
int i=0;
StreamingThread()
{
System.out.println("Hi Streamed Thread Called"+i);
}
private Set<String> streamSet = new HashSet();
public Set getData()
{
return streamSet;
}
public void run() {
/*while (true) {
for (int i = 0; i < 12; i++) {
streamSet.add("Test" + 1);
//s System.out.println("Run Called"+i);
}
}*/
}
}
If you want the same object to be injected, then you must not define another bean, but use the same definition:
<beans>
<bean id="stream-core" class="com.StreamHandler" scope="singleton" init-method="init">
<constructor-arg><ref bean="streamingthread"/></constructor-arg>
</bean>
<bean id="streamingthread" class="com.StreamingThread" scope="singleton" />
</beans>
And if you want a field to be accessible, then add a method to your class that lets you access the field.
EDIT:
You can only inject (autowire) Spring beans into other Spring beans, obtained from the application context. Client is not a Spring bean, and you get an instance of it using new Client(). Spring is totally unaware of this class and of its instanciation, so it can't inject any object into this Client instance.
You must get the StreamingThread from the application context:
StreamingThread streamingThread = applicationContext.getBean(StreamingThread.class);
How to configure PropertyPlaceholderConfigurer to use properties files relative (some directories up) to the war?
We have running a war multiple times and each war should read its configuration for example from ../../etc/db.properties.
Update:
Yes, the properties files are outside the war. The directory structure is:
/htdocs/shop/live/apache-tomat/webapps/shop.war
should read
/htdocs/shop/live/etc/db.properties
and
/htdocs/shop/test/apache-tomat/webapps/shop.war
should read
/htdocs/shop/test/etc/db.properties
Finally, we have introduced a new resource type "relative:":
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true" />
<property name="locations">
<list>
<value>classpath:db.properties</value>
<value>relative:../../../etc/db.properties</value>
</list>
</property>
</bean>
We have extended XmlWebApplicationContext to inject custom resource handling:
public class Context extends XmlWebApplicationContext {
#Override
public Resource getResource(String location) {
if (location.startsWith(RelativeResource.RELATIVE_URL_PREFIX)) {
String relativePath = location.substring(RelativeResource.RELATIVE_URL_PREFIX.length());
return new RelativeResource(getServletContext(), relativePath);
}
return super.getResource(location);
}
}
Here is the relative resource class:
public class RelativeResource extends AbstractResource {
public static final String RELATIVE_URL_PREFIX = "relative:";
private final ServletContext servletContext;
private final String relativePath;
public RelativeResource(ServletContext servletContext, String relativePath) {
this.servletContext = servletContext;
this.relativePath = relativePath;
}
#Override
public String getDescription() {
return "RelativeResource [" + relativePath + "]";
}
#Override
public boolean isReadable() {
return true;
}
#Override
public boolean isOpen() {
return true;
}
#Override
public InputStream getInputStream() throws IOException {
String rootPath = WebUtils.getRealPath(servletContext, "/");
if (!rootPath.endsWith(File.separator)) rootPath += File.separator;
String path = rootPath + relativePath;
return new FileInputStream(path);
}
}
My code, based on mazatwork solution:
public class RelativeResource extends AbstractResource {
private final String relativePath;
public RelativeResource(String relativePath) {
this.relativePath = relativePath;
}
#Override
public String getDescription() {
return "RelativeResource [" + relativePath + "]";
}
#Override
public boolean isReadable() {
File resourceFile = new File(getAbsoluteFileLocation());
return resourceFile.exists();
}
#Override
public boolean isOpen() {
return true;
}
#Override
public InputStream getInputStream() throws IOException {
return new FileInputStream(getAbsoluteFileLocation());
}
private String getAbsoluteFileLocation() {
return Paths.get("").toAbsolutePath().resolve(relativePath).toString();
}
}
After that we can write in xml for example:
<bean id="configurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>classpath:application.properties</value>
<value type="com.blabla.RelativeResource">overrideProperties.properties</value>
</list>
</property>
<property name="ignoreResourceNotFound" value="true"/>
</bean>
Pros of this method - you don't hack Spring Context and don't stick to this hacked context implementation, you can use any (for example, not XmlWebApplicationContext, but ClassPathXmlApplicationContext) from Spring distribution.
In your configuration you can specify the properties from the classpath instead of relative to the configuration file.
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>
For this to work you must make sure that the properties file makes it to the classpath.
Somehow I wasn't able to get the desired path following others' methods, so here is my working version, based primarily on Dmitry's answer (usage in xml is identical), while isReadable() and getInputStream() looks more like mazatwork's version:
public class RelativeResource extends AbstractResource {
private final String relativePath;
public RelativeResource(String relativePath) {
this.relativePath = relativePath;
}
#Override
public String getDescription() {
return "RelativeResource [" + relativePath + "]";
}
#Override
public boolean isReadable() {
return true;
}
#Override
public boolean isOpen() {
return true;
}
#Override
public InputStream getInputStream() throws IOException {
String rootPath = this.getClass().getResource("/").getPath();
rootPath = URLDecoder.decode(rootPath, "UTF-8");
if (!rootPath.endsWith(File.separator)) rootPath += File.separator;
String path = rootPath + relativePath;
return new FileInputStream(path);
}
}