This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 8 years ago.
I've got a NullPointerException that is bugging the hell out of me. In my cursory research on SO I've found that this is usually happening when people are not autowiring their jdbctemplate, but as far as I can tell that should be wiring correctly. As a heads up I'm still learning the basics of Spring, and the code I'm working with is part of a legacy project.
ReportDaoImpl
#Service
public class ReportDaoImpl implements ReportDao {
#Autowired JdbcTemplate jdbcTemplate;
private static final Logger log = Logger.getLogger(ReportDaoImpl.class);
private static final String SELECT_ALL_ACCOUNT_INFO = "SELECT acct_name, login_name, pswd FROM PG_PAYPAL_ACCOUNTS";
#Autowired
public ReportDaoImpl(DataSource dataSource)
{
log.debug("attempt building");
jdbcTemplate = new JdbcTemplate(dataSource);
log.debug("building complete");
}
#Override
public ArrayList<String[]> getReportAccounts() {
log.debug("looking for accounts");
List<Map<String, Object>> resultList;
String[] accountDetails;
ArrayList<String[]> accounts = new ArrayList<String[]>();
try{
log.debug("Excecuting Query");
resultList = jdbcTemplate.queryForList(SELECT_ALL_ACCOUNT_INFO);
log.debug("Query Results");
log.debug(resultList.toString());
if(resultList != null && resultList.size() > 0){
for(Map<String, Object> temprow: resultList){
log.debug("Mapping Query Results to Account POJO");
accountDetails = new String[3];
accountDetails[0] = (String) temprow.get("acct_name");
accountDetails[1] = (String) temprow.get("login_name");
accountDetails[2] = (String) temprow.get("pswd");
log.debug("Single account details");
log.debug(accountDetails.toString());
log.debug("Adding single account to accounts array");
accounts.add(accountDetails);
}
}
return accounts;
} catch (Exception e){
log.debug("NO RESULTS: " + e);
System.out.println("NO RESULTS: " + e);
return null;
}
}
}
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:util="http://www.springframework.org/schema/util"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-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.test.testpackage.report" annotation-config="false" />
<context:property-placeholder location="classpath:project-be.properties" />
<import resource="classpath:db-config.xml"/>
<bean id="pgReportService" name="pgReportService" class="org.test.testpackage.report.service.AccountLookup" scope="singleton" />
<bean id="jdbcTemplate" name="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
Thanks so much!
The question is hard to read. What are you executing that is actually generating a NullPointerException?
For what I understand of your question you might be having a common mistake in Spring: If your variable jdbcTemplate is autowired in the ReportDaoImpl class then this class must be created also by autowiring and not by manual instantiation. Same thing happens for DataSource.
This means:
ReportDaoImpl reportDaoImp = new ReportDaoImpl(dataSource);
will not have an instatiated dataSource (non instantiated jdbcTemplate), because it is you who is actually making the instantiation and not letting Spring to do it.
So you need to specify a bean for ReportDaoImpl in your Application class, such as:
#Autowired
public ReportDaoImpl(DataSource dataSource){
ReportDaoImpl reportDaoImp = new ReportDaoImp();
reportDaoImp.setDataSource(dataSource);
return reportDaoImp;
}
And in the class where you use ReportDaoImp define an attribute:
#Autowired
ReportDaoImpl reportDaoImp;
This will instantiate DataSource (if the a bean for DataSource is also defined) and then instantiate a ReportDaoImpl passing this instance of DataSource.
Edit:
Actually this question answers might answer yours: Why is my Spring #Autowired field null?
Related
I I am very new to spring boot and only have been working with it for a couple of days, so I am also very confused about this project (which is not my own). I am supposed to write tests with MockMvc for the rest controller, however each test with MockMvc only returns the status code 200 although it should be a 404.
Here is one of the tests:
#WebMvcTest(ObjectController.class)
#SpringJUnitConfig(TestConfig.class)
public class MvcTest {
#Autowired
MockMvc mockMvc;
#Test
public void shouldReturn404() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/obj/123"))
.andExpect(MockMvcResultMatchers.status().is(HttpStatus.NOT_FOUND.value()));
}
}
This is my rest controller I would like to test.
#RestController
#RequestMapping("obj")
public class ObjectController {
#Autowired
MyClass myClass;
#GetMapping()
public List<MyObject> findAll() {
List<MyObject> objList;
objList = myClass.getObjects();
return objList;
}
#GetMapping("/{id}")
public MyObject findById(#PathVariable String id) {
for (MyObject obj : myClass.getObjects()) {
if (obj.getId().equals(id))
return obj;
}
return null;
}
}
Then there is this class:
public class MyClass {
private List<MyObject> objList = new ArrayList<MyObject>();
public MyObject addObject(MyObject obj) {
objList.add(obj);
return obj;
}
public void setObjects(List<MyObject> objList) {
this.objList = objList;
}
public synchronized List<MyObject> getObjects() {
return objList;
}
}
There is an xml file that belongs to the class looking like this which can be found in the resource folder:
<?xml version='1.0' encoding='ISO-8859-1'?>
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd'>
<bean id='MyClass' class='it.is.some.path.MyClass'>
<property name='objList'>
<list>
<ref bean='[R01] Object1' />
</list>
</property>
</bean>
</beans>
The referenced beans can be found in separate files under resources too, in a sub folder called 'objList' and look like this:
<?xml version='1.0' encoding='ISO-8859-1'?>
<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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd'>
<bean id='[R01] Object1' class='this.is.some.path.MyObject'>
<property name='id' value='123' />
</bean>
</beans>
The myclass.xml as well as the folder with all xmls of the objects are imported via #ImportResource in the Application class.
And then there is
public class MyObject {
public String id = RandomStringUtils.randomAlphanumeric(5);
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
am sorry for the lengthy explanation but I have been spending two days now trying to figure out how to get MockMvc running and I feel like I am completely stuck. I would be very, very grateful if anybody could help out.
I assume 200 is the correct response as you have a mapping for /obj/{id} where {id} is a placeholder for any path variable like 123 in /obj/123.
Even though your controller returns null from a code perspective, Spring won't map this automatically to a 404 not found if this is what you would expect.
So you can literally try it with every path variable, you'll always get HTTP 200 because there wasn't any exception and the DisptacherServlet could properly route the HTTP request to a controller mapping. HTTP 200 is the default response code if you don't specify anything.
If you want your controller endpoint to return 404 when your MyObject is null, you have to be more explicit about this, e.g.:
#GetMapping("/{id}")
public ResponseEntity<MyObject> findById(#PathVariable String id) {
for (MyObject obj : myClass.getObjects()) {
if (obj.getId().equals(id))
return ResponseEntity.ok(obj);
}
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}
I've seen a hundred examples of this:
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ImportResource;
#SpringBootApplication
#ImportResource("classpath:applicationContext.xml")
public class MySpringBootApplication {
public static void main(String[] args) {
SpringApplication.run(MySpringBootApplication.class, args);
}
}
And I have been on a rabbit trail for many hours now.
I am building a framework....and I need load (a handful of dependencies, not all of them...) from the xml dependency injection file (aka, "beans") :
applicationContext.xml
and I need to name to be dynamic, not hard coded.
String myValue = "DefaultEnvVarValue";
String envValue = System.getenv("MYENVVARIABLENAME");
if (null != envValue )
{
myValue=envValue;
}
String topLevelAppContextFileName = "applicationContext." + myValue + ".xml";
Without springboot, I would do this:
ApplicationContext context = new ClassPathXmlApplicationContext(topLevelAppContextFileName);
Is there a way to pull this off with SpringBoot?
I found PropertySourcesPlaceholderConfigurer for property files, but cannot find anything for the dependency injection.
Sidenote:
Before I get a "xml bad" comment, most of my dependencies are annotation based. But I'm making a framework for others to use, and therefore I need a handful of them to be xml-driven.....aka, I have a legit reason to have some of the DI be xml driven.
This could work -
Config
public class DemoApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext ac) {
ac = new ClassPathXmlApplicationContext(topLevelAppContextFileName);
}
}
Main
public static void main(String args[]) {
new SpringApplicationBuilder(Application.class)
.initializers(new DemoApplicationContextInitializer())
.run(
}
For future readers, I ended up doing this:
#SpringBootApplication
#ImportResource({"classpath*:applicationContext.xml"})
public class MySpringBootApplication {
public static void main(String[] args) {
try {
URL resource = MySpringBootApplication.class.getResource("/applicationContext.xml");
if (null == resource || StringUtils.isBlank(resource.getPath())) {
throw new FileNotFoundException("applicationContext.xml not found. The entry dependency injection file must be applicationContext.xml");
}
org.springframework.context.ConfigurableApplicationContext applicationContext = SpringApplication.run(MySpringBootApplication.class, args);
And then I put the "dynamic" part in the inside applicationContext.xml file.
Note the ":" delimiter that will allow a default value if the environment variable does not exist.
<?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:util="http://www.springframework.org/schema/util"
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/util
http://www.springframework.org/schema/util/spring-util.xsd">
<import resource="projectContext.${MYENVVARIABLENAME:DefaultEnvVarValue}.xml"/>
That was simpler to implement, even though I technically have 2 files, instead of one.
So if the environment variable does not exist, it will default to importing the second file called:
projectContext.DefaultEnvVarValue.xml
I am trying to dinamically update the users credentials in next way: When de admin pushes a button a new role is added to every standard user in database. The disconnected users have not problem, because when they login the custom authentication provider loads all his roles from db, but the logued users cant access to the new available sections, because the authentication object has not the new role. In order to solve this i tried many mechanisms, but the rightfull and less intrusive i saw was using listeners. Here is the idea: when the admin pushes the button, the db is updated and a new custom event is triggered and processed. This event produces, in theory, a reauthentication for each active user AND IT DOES, just that only for the user who triggered the event(the admin).
Now, i want to know why does it, why the event doesnt apply to every SecurityContextHolder and just to the one who triggered it. I though the problem was in the scope of the bean, so i gave it a session scope, but throws an error. Please, if anyone can help me.
Here is the properly code
My dispatcher servlet
<?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:p="http://www.springframework.org/schema/p"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd">
<context:component-scan base-package="printer">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
<bean id="viewResolver"
class="org.springframework.web.servlet.view.InternalResourceViewResolver"
p:prefix="/WEB-INF/jsp/"
p:suffix=".jsp" />
//nothing that matters here
<bean id="reauthenticating" class="printer.Security.Events.ReauthenticatingEventsPostProcessor" scope="prototype"/>
The event
public class ReauthenticatingUseronRoleChangeEvent extends ApplicationEvent {
private static final long serialVersionUID = 1L;
private String roleType;
private String actionType;
public ReauthenticatingUseronRoleChangeEvent(Object source,String roleType, String actionType) {
super(source);
this.roleType = roleType;
this.actionType = actionType;
}
public String getRoleType() {
return roleType;
}
public String getActionType() {
return actionType;
}
The event trigger which is my UserService
public class UserService_Impl implements UserService,ApplicationEventPublisherAware{
#Override
public void publishAccessToDownloadEvent() {
.....
enter code here
#Override
public void publishAccessToDownloadEvent() {
publisher.publishEvent(new ReauthenticatingUseronRoleChangeEvent(this, "ROLE_DOWNLOAD", "add"));
}
Here is the event listener. This is where i get lost, doesnt suppose it is executed for every user????
public class ReauthenticatingEventsPostProcessor implements ApplicationListener<ReauthenticatingUseronRoleChangeEvent> {
#Autowired
#Qualifier("userDao")
UserDAO userDao;
#Override
public void onApplicationEvent(ReauthenticatingUseronRoleChangeEvent e) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority> (auth.getAuthorities());
Role r=new Role();
r.setRole(e.getRoleType());
authorities.add(r);
Authentication newAuth = new UsernamePasswordAuthenticationToken(auth.getPrincipal(),auth.getCredentials(),authorities);
SecurityContextHolder.getContext().setAuthentication(newAuth);
}
}
#Override
public void setApplicationEventPublisher(ApplicationEventPublisher aep) {
this.publisher=aep;}
As i said above, the listener works fine, just that not as suppossed. Instead execute him for each user, does it for the user who triggered the event only.
Does anyone know of a Maven plugin that can be used to validate Spring configuration files? By validation, I mean:
Verify all beans reference a class on the build path
Verify all bean references refer to a valid bean definition
Verify no orphaned beans exist
Other configuration mistakes I'm sure I'm missing.
I searched around and didn't come up with anything.
A Maven plugin would be ideal for my purposes, but any other tools (Eclipse plugin, etc.) would be appreciated.
What we do on our project is simply write a JUnit test which loads the Spring configuration. This does a few of the things you described like:
Validate the XML
Ensures beans can be loaded with classes on the classpath (at least beans which aren't lazy-loaded)
It does not check that there are no orphan beans. There is no reliable way of doing this anyway considering from anywhere in your code, you can lookup beans directly given their ID. Just because a bean is not referenced by any other beans does not mean it is not used. In fact all Spring configs will have at least one bean which is not referenced by other beans because there always has to be a root to the hierarchy.
If you have beans which rely on real services like databases or something and you don't want to connect to these services in a JUnit test, you simply need to abstract the configuration to allow for test values. This can be easily accomplished with something like the PropertyPlaceholderConfigurer which allows you to have different properties specified in separate config files for each environment and then referenced by one beans definition file.
EDIT (to include sample code):
The way we do this is have at least 3 different spring files...
src/main/resources/applicationContext.xml
src/main/resources/beanDefinitions.xml
src/test/resources/testContext.xml
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"
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">
<import resource="classpath:beanDefinitions.xml"/>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="file:path/environment.properties" />
</bean>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="${driver}" />
...
</bean>
... <!-- more beans which shouldn't be loaded in a test go here -->
</beans>
beanDefinitions.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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<bean id="myBean" class="com.example.MyClass">
...
</bean>
<bean id="myRepo" class="com.example.MyRepository">
<property name="dataSource" ref="dataSource"/>
...
</bean>
... <!-- more beans which should be loaded in a test -->
</beans>
testContext.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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<import resource="classpath:beanDefinitions.xml"/>
<bean id="dataSource" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="org.springframework.jdbc.datasource.DriverManagerDataSource"/>
</bean>
</beans>
There are many things going on here, let me explain...
The applicationContext.xml file is the main spring file for your whole application. It contains an PropertyPlaceHolder bean to allow certain property values to be configurable between different environments we deploy to (test vs. prod). It imports all of the main beans that the app needs to run. Any beans which should not be used in a test, like DB beans, or other classes which communicate with external services/resources should be definied in this file.
The beanDefinitions.xml file has all of your normal beans in it which don't rely on external things. These beans can and will reference beans defined in the appContext.xml file.
The testContext.xml file is the test version of the appContext. It needs versions of all beans defined in the appContext.xml file but we used a mocking library to instantiate these beans. This way the real classes aren't used and there is no risk of access external resources. This file also doesn't need the property placeholder bean.
Now that we have a test context which we aren't afraid to load from a test, here is the code to do it...
SpringContextTest.java
package com.example;
import org.junit.Test;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.core.io.ClassPathResource;
public class SpringContextTest {
#Test
public void springContextCanLoad() {
XmlBeanFactory factory = new XmlBeanFactory(new ClassPathResource("testContext.xml"));
for (String beanName : factory.getBeanDefinitionNames()) {
Object bean = factory.getBean(beanName);
// assert anything you want
}
}
}
This may not be the optimal way of doing it; the ApplicationContext class is the recommended way of loading spring contexts. The above might be able to be replaced by:
#Test
public void springContextCanLoad() {
ApplicationContext context = new FileSystemXmlApplicationContext("classpath:testContext.xml");
}
I believe that one line will accomplish everything you need to verify your spring context is wired correctly. From there, you can load beans and assert like before.
Hope this helps!
Here's the URL of Spring IDE update site (Eclipse plugin). It does what you described above. Their site seems to be unavailable.
I came across this question when googling - I had exactly the same question.
I've written a (very much untested) Maven plugin to do this this. It currently only supports WARs but could easily be extended. In addition, I don't bother actually loading the beans since I don't want the hassle of having to maintain a large set of properties just to satisfy this plugin.
Here it is if it's ever any use:
package myplugins;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.project.MavenProject;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.PropertyValue;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.ClassUtils;
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Validates Spring configuration resource and class references
* using a classloader that looks at the specified WAR's lib and classes
* directory.
* <p/>
* It doesn't attempt to load the application context as to avoid the
* need to supply property files
* <br/>
* TODO: maybe one day supplying properties will become an optional part of the validation.
*
* #goal validate
* #aggregator
* #phase install
*/
public class WarSpringValidationMojo extends AbstractMojo
{
private final static String FILE_SEPARATOR = System.getProperty("file.separator");
/**
* Project.
* #parameter expression="${project}"
* #readonly
*/
private MavenProject project;
/**
* The WAR's root Spring configuration file name.
*
* #parameter expression="${applicationContext}" default-value="webAppConfig.xml"
*/
private String applicationContext;
/**
* The WAR's directory.
*
* #parameter expression="${warSourceDirectory}" default-value="${basedir}/target/${project.build.finalName}"
*/
private File warSourceDirectory;
#SuppressWarnings("unchecked")
public void execute() throws MojoExecutionException
{
try
{
if ("war".equals(project.getArtifact().getType()))
{
File applicationContextFile = new File(warSourceDirectory, "WEB-INF" + FILE_SEPARATOR + applicationContext);
File classesDir = new File(warSourceDirectory, "WEB-INF" + FILE_SEPARATOR + "classes");
File libDir = new File(warSourceDirectory, "WEB-INF" + FILE_SEPARATOR + "lib");
Set<URL> classUrls = new HashSet<URL>();
if (classesDir.exists())
{
classUrls.addAll(getUrlsForExtension(classesDir, "class", "properties"));
}
if (libDir.exists())
{
classUrls.addAll(getUrlsForExtension(libDir, "jar", "zip"));
}
ClassLoader parentClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader classLoader = new URLClassLoader(classUrls.toArray(new URL[classUrls.size()]), parentClassLoader);
ClassUtils.overrideThreadContextClassLoader(classLoader);
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
factory.setBeanClassLoader(classLoader);
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
reader.setValidating(true);
reader.loadBeanDefinitions(new FileSystemResource(applicationContextFile));
for (String beanName : factory.getBeanDefinitionNames())
{
validateBeanDefinition(classLoader, factory.getBeanDefinition(beanName), beanName);
}
getLog().info("Successfully validated Spring configuration (NOTE: validation only checks classes, " +
"property setter methods and resource references)");
}
else
{
getLog().info("Skipping validation since project artifact is not a WAR");
}
}
catch (Exception e)
{
getLog().error("Loading Spring beans threw an exception", e);
throw new MojoExecutionException("Failed to validate Spring configuration");
}
}
private void validateBeanDefinition(ClassLoader beanClassloader, BeanDefinition beanDefinition, String beanName) throws Exception
{
Class<?> beanClass = validateBeanClass(beanClassloader, beanDefinition, beanName);
validateBeanConstructor(beanDefinition, beanName, beanClass);
validateBeanSetters(beanDefinition, beanName, beanClass);
}
private Class<?> validateBeanClass(ClassLoader beanClassloader, BeanDefinition beanDefinition, String beanName) throws Exception
{
Class<?> beanClass;
try
{
beanClass = beanClassloader.loadClass(beanDefinition.getBeanClassName());
}
catch (ClassNotFoundException e)
{
throw new ClassNotFoundException("Cannot find " + beanDefinition.getBeanClassName() +
" for bean '" + beanName + "' in " + beanDefinition.getResourceDescription(), e);
}
return beanClass;
}
private void validateBeanConstructor(BeanDefinition beanDefinition, String beanName,
Class<?> beanClass) throws Exception
{
boolean foundConstructor = false;
ConstructorArgumentValues constructorArgs = beanDefinition.getConstructorArgumentValues();
Class<?>[] argTypes = null;
if (constructorArgs != null)
{
Constructor<?>[] constructors = beanClass.getDeclaredConstructors();
int suppliedArgCount = constructorArgs.getArgumentCount();
boolean isGenericArgs = !constructorArgs.getGenericArgumentValues().isEmpty();
for (int k = 0; k < constructors.length && !foundConstructor; k++)
{
Constructor<?> c = constructors[k];
knownConstructorLoop:
{
Class<?>[] knownConstructorsArgTypes = c.getParameterTypes();
if (knownConstructorsArgTypes.length == suppliedArgCount)
{
if (isGenericArgs)
{
foundConstructor = true; // TODO - support generic arg checking
}
else
{
for (int i = 0; i < knownConstructorsArgTypes.length; i++)
{
Class<?> argType = knownConstructorsArgTypes[i];
ConstructorArgumentValues.ValueHolder valHolder = constructorArgs.getArgumentValue(i,
argType);
if (valHolder == null)
{
break knownConstructorLoop;
}
}
foundConstructor = true;
}
}
}
}
}
else
{
try
{
Constructor c = beanClass.getConstructor(argTypes);
foundConstructor = true;
}
catch (Exception ignored) { }
}
if (!foundConstructor)
{
throw new NoSuchMethodException("No matching constructor could be found for bean '" +
beanName + "' for " + beanClass.toString() + " in " + beanDefinition.getResourceDescription());
}
}
private void validateBeanSetters(BeanDefinition beanDefinition, String beanName, Class<?> beanClass) throws Exception
{
MutablePropertyValues properties = beanDefinition.getPropertyValues();
List<PropertyValue> propList = properties.getPropertyValueList();
try
{
Method[] methods = beanClass.getMethods();
for (PropertyValue p : propList)
{
boolean foundMethod = false;
String propName = p.getName();
String setterMethodName = "set" + propName.substring(0, 1).toUpperCase();
if (propName.length() > 1)
{
setterMethodName += propName.substring(1);
}
for (int i = 0; i < methods.length && !foundMethod; i++)
{
Method m = methods[i];
foundMethod = m.getName().equals(setterMethodName);
}
if (!foundMethod)
{
throw new NoSuchMethodException("No matching setter method " + setterMethodName
+ " could be found for bean '" + beanName + "' for " + beanClass.toString() +
" in " + beanDefinition.getResourceDescription());
}
}
}
catch (NoClassDefFoundError e)
{
getLog().warn("Could not validate setter methods for bean " + beanName +
" since getting the methods of " + beanClass + " threw a NoClassDefFoundError: "
+ e.getLocalizedMessage());
}
}
private Collection<? extends URL> getUrlsForExtension(File file, String... extensions) throws Exception
{
Set<URL> ret = new HashSet<URL>();
if (file.isDirectory())
{
for (File childFile : file.listFiles())
{
ret.addAll(getUrlsForExtension(childFile, extensions));
}
}
else
{
for (String ex : extensions)
{
if (file.getName().endsWith("." + ex))
{
ret.add(file.toURI().toURL());
break;
}
}
}
return ret;
}
}
And the plugin's pom.xml:
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
... <my project's parent> ...
</parent>
<groupId>myplugins</groupId>
<artifactId>maven-spring-validation-plugin</artifactId>
<version>1.0</version>
<packaging>maven-plugin</packaging>
<name>Maven Spring Validation Plugin</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-project</artifactId>
<version>2.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.0.7.RELEASE</version>
</dependency>
</dependencies>
</project>
Once installed, run like so at the root level of your WAR module:
mvn myplugins:maven-spring-validation-plugin:validate
For now I could inject values from property-files:
#Value("${aaa.prop}")
public String someProp;
But I want something more...
For example I have some property file:
aaa.props=p1,p2,p3
aaa.props.p1=qwe
aaa.props.p2=asd
aaa.props.p3=zxc
I know for sure, that it contains property aaa.props, and know nothing about other properties. And I want to get this properties in to map using code like this:
#Value ("${aaa.props}")
public Map<String, String> someProps;
Resulting someProps: {p1=qwe,p2=asd,p3=zxc}
Well I built a generic approach for you: a factory bean that creates a map by filtering another map (properties are a kind of map, after all).
Here's the factory bean:
public class FilteredMapFactoryBean<V> extends
AbstractFactoryBean<Map<String, V>>{
private Map<String, V> input;
/**
* Set the input map.
*/
public void setInput(final Map<String, V> input){
this.input = input;
}
/**
* Set the string by which key prefixes will be filtered.
*/
public void setKeyFilterPrefix(final String keyFilterPrefix){
this.entryFilter = new EntryFilter<String, V>(){
#Override
public boolean accept(final Entry<String, V> entry){
return entry.getKey().startsWith(keyFilterPrefix);
}
};
}
public static interface EntryFilter<EK, EV> {
boolean accept(Map.Entry<EK, EV> entry);
}
/**
* If a prefix is not enough, you can supply a custom filter.
*/
public void setEntryFilter(final EntryFilter<String, V> entryFilter){
this.entryFilter = entryFilter;
}
private EntryFilter<String, V> entryFilter;
/**
* {#inheritDoc}
*/
#Override
public Class<?> getObjectType(){
return Map.class;
}
/**
* {#inheritDoc}
*/
#Override
protected Map<String, V> createInstance() throws Exception{
final Map<String, V> map = new LinkedHashMap<String, V>();
for(final Entry<String, V> entry : this.input.entrySet()){
if(this.entryFilter == null || this.entryFilter.accept(entry)){
map.put(entry.getKey(), entry.getValue());
}
}
return map;
}
}
Here is a spring bean definition file with some sample usage:
<?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-3.0.xsd">
<!-- use System.getProperties() as input -->
<bean class="spring.test.FilteredMapFactoryBean" id="javaMap">
<property name="keyFilterPrefix" value="java." />
<property name="input" value="#{T(java.lang.System).getProperties()}" />
</bean>
<!-- use custom properties as input -->
<bean class="spring.test.FilteredMapFactoryBean" id="customMap">
<property name="keyFilterPrefix" value="hello" />
<property name="input">
<props>
<prop key="hello">Is it me you're looking for?</prop>
<prop key="hello.again">Just called to say: hello.</prop>
<prop key="hello.goodby">You say goodbye and I say hello</prop>
<prop key="goodbye.blue.sky">Did-did-did-did-you hear the falling bombs?</prop>
<prop key="goodbye.ruby.tuesday">Who could hang a name on you?</prop>
</props>
</property>
</bean>
</beans>
And here is a test class:
public class Tester{
#SuppressWarnings("unchecked")
public static void main(final String[] args){
final ApplicationContext context =
new ClassPathXmlApplicationContext("classpath:spring/test/mapFactorybean.xml");
final Map<String, String> javaMap =
(Map<String, String>) context.getBean("javaMap");
print("java.", javaMap);
final Map<String, String> customMap =
(Map<String, String>) context.getBean("customMap");
print("hello.", customMap);
}
private static void print(final String prefix, final Map<String, String> map){
System.out.println("Map of items starting with " + prefix);
for(final Entry<String, String> entry : map.entrySet()){
System.out.println("\t" + entry.getKey() + ":" + entry.getValue());
}
System.out.println("");
}
}
The output is as expected:
Map of items starting with java.
java.runtime.name:Java(TM) SE Runtime Environment
java.vm.version:14.2-b01
java.vm.vendor:Sun Microsystems Inc.
java.vendor.url:http://java.sun.com/
java.vm.name:Java HotSpot(TM) Client VM
java.vm.specification.name:Java Virtual Machine Specification
java.runtime.version:1.6.0_16-b01
java.awt.graphicsenv:sun.awt.Win32GraphicsEnvironment
[... etc]
Map of items starting with hello.
hello.goodby:You say goodbye and I say hello
hello:Is it me you're looking for?
hello.again:Just called to say: hello.
I'm afraid you can't, directly. But you can
implement ApplicationContextAware and set the ApplicationContext as a field in your bean.
in a #PostConstruct method call context.getBean("${aaa.props}")
parse the result manually and set it to the desired fields
you can use #Value.
Properties file:
aaa.props={p1:'qwe',p2:'asd',p3:'zxc'}
Java code:
#Value("#{${aaa.props}}")
private Map<String,String> someProps;
You could do something like this:
Maven dependency
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.2</version>
</dependency>
Add the import.
import javax.annotation.Resource;
...
#Resource (name="propertiesMapName")
public Properties someProps;
In your spring xml application context:
<util:properties id="propertiesMapName" location="classpath:yourFile.properties"/>
You will need this namespace
xmlns:util="http://www.springframework.org/schema/util"
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-3.1.xsd
The solution it's well defined on https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config
#ConfigurationProperties(prefix="my")
public class Config {
private List<String> servers = new ArrayList<String>();
public List<String> getServers() {
return this.servers;
}
}