Java with Spring
2013-12-05Overview
Post bellow refers to the old version of Spring. In modern Spring application you don’t need to write xml files. You can do almost everything from java code.
Example
interface Fruit {
String eat();
String getName();
}
interface Peelable {
String peel();
}
interface PeelableFruit extends Fruit, Peelable {
}
class Apple implements PeelableFruit {
@Override
public String peel() {
return "peeling " + getName();
}
@Override
public String eat() {
return "omnonnom " + getName();
}
@Override
public String getName() {
return "apple";
}
}
class Peeler {
String peel(Peelable peelable) {
return peelable.peel() + " with peeler";
}
}
class Juicer {
private final Peeler peeler;
private final PeelableFruit fruit;
public Juicer(Peeler peeler, PeelableFruit fruit) {
this.peeler = peeler;
this.fruit = fruit;
}
public String makeJuice() {
return peeler.peel(fruit) + "\n" +
"squeezing " + fruit.getName() + "\n" +
"Ready!";
}
}
Boot
for examples with xml files:
class Boot {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Juicer j1 = context.getBean(Juicer.class);
Juicer j2 = context.getBean(Juicer.class);
System.out.println(j1 == j2);
}
}
And Boot
for new way:
@SpringBootApplication
class Boot {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Boot.class, args);
Juicer j1 = context.getBean(Juicer.class);
Juicer j2 = context.getBean(Juicer.class);
System.out.println(j1 == j2);
}
}
Singletion scope
Default scope is singleton
. If you want to use other scopes you need to use annotation @Scope
.
The old, xml-ish way
<?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">
<bean id="apple" class="Apple" />
<bean id="juicer" class="Juicer">
<constructor-arg ref="apple" />
<constructor-arg ref="peeler" />
</bean>
<bean id="peeler" class="Peeler"/>
</beans>
The new way
@Configuration
class AppConfig {
@Bean
PeelableFruit fruit() {
return new Apple();
}
@Bean
Peeler peeler() {
return new Peeler();
}
@Bean
Juicer juicer(Peeler peeler, PeelableFruit fruit) {
return new Juicer(peeler, fruit);
}
}
Result
Result of running Boot
is true
. This is because we always get the same instance (the same address in the memory) of Juicer
.
Prototype scope
The old, xml-ish way
<?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">
<bean id="apple" class="Apple" />
<bean id="juicer" class="Juicer" scope="prototype">
<constructor-arg ref="apple" />
<constructor-arg ref="peeler" />
</bean>
<bean id="peeler" class="Peeler"/>
</beans>
The new way
@Configuration
class AppConfig {
@Bean
PeelableFruit fruit() {
return new Apple();
}
@Bean
Peeler peeler() {
return new Peeler();
}
@Bean
@Scope("prototype")
Juicer juicer(Peeler peeler, PeelableFruit fruit) {
return new Juicer(peeler, fruit);
}
}
Result
Result of running Boot
is false
. This is because we alwyas get the new instances of Juicer
.
Resolving conflicts in constructor’s arguments.
class PasswordWithType {
private String type;
private String password;
PasswordWithType(String type, String password) {
this.type = type;
this.password = password;
}
String getPassword() {
return password;
}
String getType() {
return type;
}
}
The old, xml-ish way
<bean id="test" class="Test">
<constructor-arg name="password" value="xhajs" />
<constructor-arg value="md5" />
</bean>
class Boot {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
PasswordWithType passwordWithType = context.getBean(PasswordWithType.class);
System.out.println("type=" + passwordWithType.getType());
System.out.println("password=" + passwordWithType.getPassword());
}
}
And result:
type = md5
password= xhajs
Note that we used name
attribute in the xml. If didn’t use, we would end type = xhajs
.
The new way
@Configuration
class AppConfig {
@Bean
String type() {
return "md5";
}
@Bean
String password() {
return "xhajs";
}
@Bean
PasswordWithType PasswordWithType(String type, String password) {
return new PasswordWithType(type, password);
}
}
@SpringBootApplication
class Boot {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(Boot.class, args);
PasswordWithType passwordWithType = context.getBean(PasswordWithType.class);
System.out.println("type=" + passwordWithType.getType());
System.out.println("password=" + passwordWithType.getPassword());
}
}
And result:
type = md5
password= xhajs
If we use java to configure our app we do not have to use names. Spring is smart enough to figure it out base on name of the methods in @Configurotion
classes.
If we want to specify specific name we could use javax.inject.Named
annotation - example @Named("type")
. We need to add that dependency:
compile 'javax.inject:javax.inject:1'
Factories
<?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">
<bean id="fruit" class="Fruits" factory-method="newBanana" />
</beans>
public interface Fruit {
void eat();
String getName();
}
public final class Fruits {
private Fruits() {
}
public static Fruit createFruit(String fruitName) {
if (fruitName.equals("banana")) {
return newBanana();
} else {
return newPlum("defaultName");
}
}
public static Fruit newBanana() {
return new Banana();
}
public static Fruit newPlum(String name) {
return new Plum(name);
}
private static class Banana implements Fruit {
public void eat() {
System.out.println("Eating a banana: omomom");
}
public String getName() {
return "banana";
}
}
private static class Plum implements Fruit {
private String name;
public Plum(String name) {
this.name = name;
}
public void eat() {
System.out.println(String.format("Eating a plum %s: omomom", name) );
}
public String getName() {
return "plum";
}
}
}
public class MainApp {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Fruit fruit = context.getBean(Fruit.class);
fruit.eat();
}
}
Result:
Eating a banana: omomom
Plum with name:
<bean id="fruit" class="Fruits" factory-method="newPlum">
<constructor-arg value="sliwka"></constructor-arg>
</bean>
Result:
Eating a plum sliwka: omomom
Plum by name using createFruit method:
<bean id="fruit" class="Fruits" factory-method="createFruit">
<constructor-arg value="sliwka"></constructor-arg>
</bean>
Result:
Eating a plum defaultName: omomom