Michał Kowol Tech Blog

Java with Spring

Overview

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