Autowire on new-ing up an object - spring

I've a scenario where a bean I'm using has some fields populated from properties file while others need to be populated dynamically (from a api call).
Here is my bean:
#Configuration
#ConfigurationProperties(prefix="studio")
public class Studio {
private String areaCode; // loads from application.properties
private String hours; // loads from application.properties
private String groupCode; // loads from application.properties
private Address address; // loads from a api
private String id; // loads from a api
public Studio(String id, String city, String subdivision,
String addressLine1, String postalCode) {
Address address = Address.builder()
.street(addressLine1)
.city(city)
.postalCode(postalCode)
.state(subdivision)
.build();
this.id = id;
this.address = address;
}
}
Now the method that populates the dynamic fields is like this:
private List<Studio> getStudioDataFromApi(ResponseEntity<String> exchange)
throws Exception {
List<Studio> infoList = $(exchange.getBody())
.xpath("Area[TypeCode=\"CA\"]")
.map(
Area -> new Studio(
$(Area).child("Id").text(String.class),
$(Area).child("Address").child("City").text(String.class),
$(Area).child("Address").child("Subdivision").text(String.class),
$(Area).child("Address").child("AddressLine1").text(String.class),
$(Area).child("Address").child("PostalCode").text(String.class))
);
return infoList;
}
I Autowire Studio in that class. Now whenever I run this, I get the fields that are populated from the properties file as null. I can see the reason, which is, new doesn't know anything about the autowired bean. My question is how can I use both? i.e. use a bean that has always some fields populated from a config when its new-ed up.
Context 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:beans="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean class="org.springframework.batch.core.scope.StepScope" />
<bean id="ItemReader" class="com.sdm.studio.reader.StudioReader" scope="step">
<property name="studio" ref="Studio" />
</bean>
<bean id="Studio" class="com.sdm.studio.domain.Studio" />
</bean>

[edit: full code example shown here is also on github ]
Try this:
//This class contains read-only properties, loaded from Spring Externalized Configuration
#Component
#ConfigurationProperties(prefix="studio")
public class Studio {
private String areacode; // loads from application.properties
//... also add other read-only properties and getters/setters...
public String getAreacode() {
return areacode;
}
public Studio setAreacode(String areacode) {
this.areacode = areacode;
return this;
}
}
//Just a POJO
class FullStudio {
private String id;
private Address address;
FullStudio(String id, String city, String areaCode) {
this.id = id;
this.address = new Address(id, city, areaCode);
}
#Override
public String toString() {
return "FullStudio{" +
"id='" + id + '\'' +
", address=" + address +
'}';
}
}
class Address{
String id;
String city;
String areaCode;
public Address(String id, String city, String areaCode) {
this.id = id;
this.city = city;
this.areaCode = areaCode;
}
#Override
public String toString() {
return "Address{" +
"id='" + id + '\'' +
", city='" + city + '\'' +
", areaCode='" + areaCode + '\'' +
'}';
}
}
What we are doing here is allowing Spring to control the lifecycle of the Studio class. You don't need to create a new Studio yourself. Spring does that when it starts up. Since it is also a #ConfigurationProperties class it will also populate values from Spring Externalized Configuration Note: you also need public getters and setters so that Spring can populate the values for you.
FullStudio is not a Spring managed class. You create your own FullStudio with values from Studio and any other api.
And here is a class that is not configured with Java Config #Configuration but instead is managed by an xml configuration:
public class StudioReader {
private Studio wiredstudio;
public String getMessage(){
return wiredstudio.getAreacode();
}
public StudioReader setWiredstudio(Studio studio) {
this.wiredstudio = studio;
return this;
}
}
And we use this mycontext.xml file to create this bean with the reference to wiredstudio. The Studio that Spring wires in here comes from our Studio instance configured with JavaConfig. The ref attribute of studio is the name that Spring automatically chose for us based on the name of the Studio class when it instantiated it into our spring application context:
<?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:beans="http://www.springframework.org/schema/p"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<bean id="studioReaderReader" class="com.example.StudioReader" >
<property name="wiredstudio" ref="studio" />
</bean>
</beans>
Personally, I think it is more trouble than its worth for new projects to combine xml and Java Configuration for Spring beans, but it can be done.
Here is a test class that shows how our Studio class can be used from classes created with Spring Java Config and XML config:
#RunWith(SpringRunner.class)
#SpringBootTest
public class StartAppTest {
#Autowired private Studio studio;
#Autowired private StudioReader studioReader;
#Test
public void contextok() throws Exception {
}
#Test
public void fullStudio() throws Exception {
FullStudio fs = new FullStudio("1", "Denver", studio.getAreacode());
System.out.println("stdio is: " + fs);
assertEquals("303", studio.getAreacode());
}
#Test
public void loadstudioreader() throws Exception {
assertEquals("303",studioReader.getMessage());
}
}
In order to run this test, you will need 2 more files:
#SpringBootApplication
#ImportResource("classpath:mycontext.xml")
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
and our application.properties file:
studio.areacode=303

Related

Spring beans used and injected if bean class does not have any getter/setter methods?

Here it is stated that Spring beans can be instantiated, configured and injected even if bean class does not have any getter/setter methods.
Is that true?
Could you give an example (or a link to such)?
The reason Spring managed objects are referred to as beans is because
in the very early versions, Spring was intended only for use with
JavaBeans. That is no longer the case of course: Spring can manage
just about any object, even if it doesn’t have JavaBean type
characteristics such as default constructors or mutator methods
(getters and setters). None the less, the term ‘Spring beans’ has
stuck.
I get these exceptions:
org.springframework.beans.factory.BeanCreationException caused by org.springframework.beans.NotWritablePropertyException: Invalid property 'name' of bean class [com.my.pkg1.Student]: Bean property 'name' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter?
#WebListener
public class MyListener implements ServletContextListener {
#Override
public void contextInitialized(ServletContextEvent sce) {
String metadata = "mybean.xml";
ApplicationContext context = new ClassPathXmlApplicationContext(metadata );
Student student = context.getBean("student", Student.class);
System.out.println(student.age);
System.out.println(student.name);
}
}
and
public class Student {
String name;
int age;
// public String getName() {
// return name;
// }
//
// public void setName(String name) {
// this.name = name;
// }
//
// public int getAge() {
// return age;
// }
//
// public void setAge(int age) {
// this.age = age;
// }
}
and mybean.xml bean definition XML file for Student class:
<?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="student" class="com.my.pkg1.Student">
<property name="name" value="John"></property>
<property name="age" value="23"></property>
</bean>
</beans>
I feel <property name="name" value="John"> is not valid without getters and setters...
since your bean definition sets the name and age properties, a setter is needed to assign those values.
You could also customize the bean using a constructor (<constructor-arg>) or a builder function (factory-method).
Here's an intro to these different construction methods: https://www.baeldung.com/spring-xml-injection

Spring Constructor Injection Weird Error

I have encountered below BeanDefinitionDecorator error at below constructor-arg dependency line in Bean XML (root-context.xml) in Eclipse, but no issue when using setter injection with property element, appreciate if anyone here could provide advice, as not able to identify the root cause even after search through Internet. Thanks in advance.
Error encountered
"Multiple annotations found at this line:
Cannot locate BeanDefinitionDecorator for element [constructor-arg]
cvc-complex-type.2.4.c: The matching wildcard is strict, but no declaration can be found for element 'constructor-arg'.
Configuration problem: Cannot locate BeanDefinitionDecorator for element [constructor-arg] Offending resource: file [C:/Users/Administrator/
workspace/EsmProject/src/main/webapp/WEB-INF/spring/root-context.xml]"
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
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">
<beans:bean id="myObject" class="com.packapp.MyObject"></beans:bean>
<beans:bean id="homeDAO" class="com.packapp.HomeDAOImpl">
<constructor-arg><ref bean="myObject"/></constructor-arg>
</beans:bean>
<beans:bean id = "homeService" class="com.packapp.HomeServiceImpl">
<beans:property name="homeDAO" ref="homeDAO"></beans:property>
</beans:bean>
<context:component-scan base-package="com.packapp" />
</beans:beans>
public class MyObject {
private String homeName;
private String homeAddress;
public String getHomeName(){
return homeName;
}
public void setHomeName(String homeName){
this.homeName = homeName;
}
public String getHomeAddress(){
return homeAddress;
}
public void setHomeAddress(String homeAddress){
this.homeAddress = homeAddress;
}
}
#Repository
public class HomeDAOImpl implements HomeDAO {
private MyObject obj;
public HomeDAOImpl(MyObject obj){
this.obj = obj;
}
public String getAddress() {
return this.obj.getHomeAddress();
}
}
#Service
public class HomeServiceImpl implements HomeService {
private HomeDAO homeDAO;
public void setHomeDAO(HomeDAO homeDAO){
this.homeDAO = homeDAO;
}
public String getAddress() {
return this.homeDAO.getAddress();
}
}
#Controller
#RequestMapping("ctrl2")
public class HomeController2 {
private HomeService homeService;
#Autowired(required=true)
#Qualifier(value="homeService")
public void setHomeService (HomeService hs){
this.homeService = hs;
}
#RequestMapping(value = "/site2", method = RequestMethod.GET)
public String home(Locale locale, Model model) {
Date date = new Date();
DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, locale);
String formattedDate = dateFormat.format(date);
model.addAttribute("serverTime", formattedDate + this.homeService.getAddress());
return "home2";
}
}
I think <constructor-arg><ref bean="myObject"/></constructor-arg>
must be <beans:constructor-arg><beans:ref bean="myObject"/></beans:constructor-arg>
because you have a prefix for elements in spring-beans.xsd.

wiring properties with spring's p namespace doesn't work

I'm trying wiring properties with spring's p namespace. The problem is I got "org.springframework.beans.factory.BeanDefinitionStoreException: (...) cvc-complex-type.3.2.2: Attribute 'p:instrument-ref' is not allowed to appear in element 'bean'."
Please look at my xml, classes and tell me what I'm doing wrong.
performers.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="guitar" class="com.competition.Guitar"></bean>
<bean id="instrumentalist" class="com.competition.Instrumentalist"
p:instrument-ref="guitar" p:song="some song">
</bean>
</beans>
Instrumentalist.java:
package com.competition;
public class Instrumentalist implements Performer {
private Instrument instrument;
private String song;
public Instrument getInstrument() {
return instrument;
}
public void setInstrument(Instrument instrument) {
this.instrument = instrument;
}
public String getSong() {
return song;
}
public void setSong(String song) {
this.song = song;
}
public void perform() {
System.out.println("Playing " + song);
instrument.play();
}
}
Guitar.java:
package com.competition;
public class Guitar implements Instrument {
public void play() {
System.out.println("play on guitar");
}
}

Load-time weaving with AspectJ in the Spring Framework Private methods

I have built an application to test injecting log information (Entering and Exiting) around classes. I have built it using spring and used the following example for building it.
http://static.springsource.org/spring/docs/2.5.5/reference/aop.html#aop-aj-ltw
It works great now but I have 2 issues:
Private methods are not included when the log is weaved around the method. Is there a setting in the xml settings for spring to allow private methods to be weaved or is there another way around this?
I have to include all packages that are to be weaved in the META-INF/aop.xml for this to be able to work. Is there a setting to be used so that the log information can be weaved for all classes created in the project without having to add the package name to the aop.xml.
Below I have included all code and xml that is used. As I said all is working fine except the 2 issues above. Please assist me.
app.java
package se.jpab.application;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
#Service
public class App
{
public static void main( String[] args )
{
ApplicationContext appContext = new ClassPathXmlApplicationContext( new String[] { "spring.xml" });
Client client = (Client) appContext.getBean("client");
Location clientLocation = (Location) appContext.getBean("location");
// Set all values
clientLocation.setAdress1("Adress 1");
clientLocation.setAdress2("Adress 2");
clientLocation.setBox("N/A");
clientLocation.setCity("City of Dallas");
client.setName("John Doe");
client.setUrl("http://www.url.com");
client.setLocation(clientLocation);
// Print out all values
System.out.println(client.getName());
System.out.println(client.getUrl());
System.out.println(client.getLocation().getAdress1());
System.out.println(client.getLocation().getAdress2() + " " + client.getLocation().getCity());
}
}
Client.java
package se.jpab.application;
import org.springframework.stereotype.Service;
#Service
public class Client {
String name;
String url;
Location location;
//Constructors
public Client(String custName, String custUrl, Location custLocation){
name = custName;
url = custUrl;
location = custLocation;
}
public Location getLocation() {
return location;
}
public void setLocation(Location location) {
this.location = location;
}
public Client(){
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
printThis(name);
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public void printThis(String inStr) {
System.out.println("PRIVAT METOD");
System.out.println("Inkommand sträng --> " + inStr);
}
}
Location.java
package se.jpab.application;
import org.springframework.stereotype.Service;
#Service
public class Location {
String city;
String adress1;
String adress2;
String box;
//Constructors
public Location (String city, String adress1, String adress2, String box){
this.city = city;
this.adress1 = adress1;
this.adress2 = adress2;
this.box = box;
}
public Location (){
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getAdress1() {
return adress1;
}
public void setAdress1(String adress1) {
this.adress1 = adress1;
}
public String getAdress2() {
return adress2;
}
public void setAdress2(String adress2) {
this.adress2 = adress2;
}
public String getBox() {
return box;
}
public void setBox(String box) {
this.box = box;
}
}
aop.xml
<!DOCTYPE aspectj PUBLIC
"-//AspectJ//DTD//EN" "http://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj>
<weaver options=" -showWeaveInfo">
<!-- only weave classes in our application-specific packages -->
<include within="se.jpab.application.*"/>
<include within="se.jpab.aspect.*"/>
</weaver>
<aspects>
<!-- weave in just this aspect -->
<aspect name="se.jpab.aspect.InjectLogg"/>
</aspects>
</aspectj>
Aspect
package se.jpab.aspect;
import java.util.Arrays;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
#Aspect
public class InjectLogg {
private static final Log fallbackLogger = LogFactory.getLog(InjectLogg.class);
#Around("execution(public * se.jpab.application..*.*(..))")
public Object profile(ProceedingJoinPoint pjp) throws Throwable {
Object invoker = pjp.getThis();
Log logger;
logger = LogFactory.getLog(getClassNameFrom(invoker.getClass()));
// Start injecting logg messages on entering a method.
logger.info("ENTERING: (" + pjp.getSignature().getName() + ")");
try {
logger.info("ARGUMENTS: " + Arrays.toString(pjp.getArgs()) + ")");
} catch (NullPointerException e) {
logger.info("ARGUMENTS: No arguments");
}
try {
// proceed to original method call
Object result = pjp.proceed();
// Injecting exiting messages after method is finished
logger.info("RESULT: " + result);
logger.info("EXITING: (" + pjp.getSignature().getName() + ")");
// Return the result of the method we are logging
return result;
} catch (IllegalArgumentException e) {
// same with ThrowsAdvice
logger.info("Exception. Throws IllegalArgumentException");
throw e;
}
}
private String getClassNameFrom(Class invokerClassName) {
// Function that ....... To be continued JP
// Add check for that string contains $$ and se.goteborg if not then
// return fallback logger class.
String[] classNameParts = invokerClassName.getName().split("$$");
int positionOfPackageName = classNameParts[0].indexOf("se.jpab");
String className = classNameParts[0].substring(positionOfPackageName);
return className;
}
}
Spring configuration (spring.xml)
<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:mvc="http://www.springframework.org/schema/mvc"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
<aop:aspectj-autoproxy proxy-target-class="true"/>
<context:load-time-weaver/>
<context:annotation-config />
<context:component-scan base-package="se.jpab"/>
</beans>
Your first question:
Private methods are not included when the log is weaved around the
method. Is there a setting in the xml settings for spring to allow
private methods to be weaved or is there another way around this?
If you want to weave private methods, use full AspectJ and therein a privileged aspect, not Spring AOP. The Spring manual says:
If your interception needs include protected/private methods or even
constructors, consider the use of Spring-driven native AspectJ weaving
instead of Spring's proxy-based AOP framework. This constitutes a
different mode of AOP usage with different characteristics, so be sure
to make yourself familiar with weaving first before making a decision.
Your second question:
I have to include all packages that are to be weaved in the
META-INF/aop.xml for this to be able to work. Is there a setting to be
used so that the log information can be weaved for all classes created
in the project without having to add the package name to the aop.xml.
You can catch a package an all its subpackages with the .. syntax, e.g. se.jpab.application..* or even se.jpab..*. You can also combine several conditions with boolean operators. See the AspectJ documentation for examples.

Creating a list of instantiated beans in Spring

I have a bean Employee as:
public class Employee {
private int empId;
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
}
An EmployeeList class which has looks like:
public class EmployeeList {
#Autowired
public Employee[] empList;
}
The spring config 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:annotation-config/>
<bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"/>
<bean id="empBean" class="Employee" scope="prototype">
</bean>
<bean id="empBeanList" class="EmployeeList">
</bean>
</beans>
The main method class:
public class App
{
public static void main( String[] args )
{
ApplicationContext empContext = new ClassPathXmlApplicationContext(
"employee-module.xml");
EmployeeList objList = (EmployeeList) empContext.getBean("empBeanList");
Employee obj = (Employee) empContext.getBean("empBean");
obj.setEmpId(1);
System.out.println(obj.getEmpId());
System.out.println("length " + objList.empList.length);
Employee obj1 = (Employee) empContext.getBean("empBean");
obj1.setEmpId(2);
System.out.println(obj1.getEmpId());
System.out.println("length " + objList.empList.length);
Employee obj2 = (Employee) empContext.getBean("empBean");
System.out.println("length " + objList.empList.length);
}
}
The count of Employee instances I get is always 1. Why it is not incremented when I get the bean instance multiple times. The Employee bean has scope as prototype.
Because getting a new prototype instance doesn't magically add it to a previously instantiated bean array.
When the context starts up, a single employee bean is instantiated and injected in the empBeanList bean, and then the empList bean is created and doesn't change anymore.

Resources