Spring

Inversion of control

The approach of outsourcing(外包) the construction and management of objects(outsource to a object factory).

下面这段程序是一个普通的java程序

1
2
3
4
5
6
7
8
public class MyApp {

public static void main(String[] args) {
// TODO Auto-generated method stub
Coach theCoach = new TrackCoach();
System.out.println(theCoach.getDailyWorkout());
}
}

Java编程中我们通常直接使用new创建一个新的对象,这样类和类之间存在高耦合,也就是说如果我们想要此时不想要TrackCoach给我们建议而是让其他Coach给我们建议,我们只能更改源代码。而IOC就是为了解决这个问题,对象的创建由Spring Container(Object Factory)帮我们创建。

Spring Container的主要功能:1.Create and Manage objects(IOC) 2.Inject object’s dependencies(Dependency Injection)

我们使用Spring Container之前需要进行配置,主要有三种方式:1.XML file(legacy, but most legacy apps use it) 2.Java annotation(modern) 3.Java source code(modern)

XML file configuration

IOC Spring程序的开发流程:
1.Configure your Spring Beans
什么是Spring Beans?
A “Spring Bean” is simply a Java object.
When Java objects are created by the Spring Container, then Spring refers to them as “Spring Beans”.

1
2
<bean id="myCoach" class="com.luv2code.springdemo.TrackCoach">
</bean>

2.Create a Spring Container
Spring Container is generally known as ApplicationContect. 它有一些具体的实现: ClassPathXmlApplicationContext, AnnotationApplicationContext, GenericWebApplicationContext…
这里我们先用xml配置的这种。
3.Retrieve Beans from Spring Container

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.luv2code.springdemo;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class HelloSpringApp {
public static void main(String[] args) {
//load spring configuration file, Create a Spring Container
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//retrieve bean from spring
Coach theCoach = context.getBean("myCoach", Coach.class);//这里我们在getBean方法后面加上Coach.class,spring会自动帮我们进行类型转换
//call methods on the bean
System.out.println(theCoach.getDailyWorkout());
//close the context
context.close();
}
}

Coach theCoach = context.getBean("myCoach", Coach.class);, 这里我们在getBean方法后面加上Coach.class,spring会自动帮我们进行类型转换, 这样比直接自己手动转换Coach theCoach = (Coach) context.getBean("myCoach");的好处是provides a measure of type safety by throwing a BeanNotOfRequiredTypeException if the bean is not of the required type.

Java Annotation

Java Annotations are special labels/marks added to Java classes whcih provide meta-data about the class and proceed at compile time or run-time for special processing.
Thus spring will scan java classes for special annotations and automatically register the beans in the Spring container.
所以使用Java Annotations开发IOC Spring程序的流程是:

  1. Enable component scanning in Spring config file.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <?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.xsd
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- add entry to enable component running -->
    <context:component-scan base-package="com.luv2code.springdemo"></context:component-scan>

    </beans>
  2. Add @Component Annotation to your java classes.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.luv2code.springdemo;
    import org.springframework.stereotype.Component;
    @Component("thatSillyCoach")
    public class TennisCoach implements Coach {
    @Override
    public String getDailyWorkout() {
    return "Practise your backhand volley";
    }
    }
  3. Retrieve Beans from Spring Container.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.luv2code.springdemo;

    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class AnnotationDemoApp {
    public static void main(String[] args) {
    //read spring config file
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    //get the bean from spring container
    Coach theCoach = context.getBean("thatSillyCoach", Coach.class);//这里的名字就是我们在annotation定义的ID
    //call a method on the bean
    System.out.println(theCoach.getDailyWorkout());
    //close the context
    context.close();
    }
    }

Spring也支持不写Bean ID,那么它默认的Bean ID就是class name的首字母小写:比如TennisCoach类,那么我们用的时候就是”tennisCoach”

Dependency Injection

Dependency Injection指的是如果我们跟object factory要一个car object,car object由许多包括tire object, seat object, engine object组成,那么object factory会自动帮我们组装好,这就是依赖注入。
“dependency” is the same thing as “helper objects”.

Injection Types: 1. Construction Injection 2. Setter Injection 3.auto-wiring(in annotation section)

XML configurarion - Construction injection

Construction injection的开发流程:

  1. Define the dependency interface and class
    1
    2
    3
    4
    5
    6
    7
    package com.luv2code.springdemo;
    public class HappyFortuneService implements FortuneService {
    @Override
    public String getFortune() {
    return "Today is your lucky day";
    }
    }
  2. Create a constructor in your class for injections
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.luv2code.springdemo;
    public class BaseballCoach implements Coach{
    //define a private field for the dependency
    private FortuneService fortuneService;
    //define a constructor for dependency injection
    public BaseballCoach(FortuneService theFortuneService) {
    this.fortuneService = theFortuneService;
    }

    @Override
    public String getDailyWorkout() {
    return "Spend 30 minutes on batting practise";
    }

    @Override
    public String getDailyFortune() {
    //use my fortuneService to get a fortune
    return fortuneService.getFortune();
    }
    }
  3. Configure the dependency injdection in spring config file
    1
    2
    3
    4
    5
    6
    7
    8
       <!-- define the dependency(helper object) -->
    <bean id="myFortune" class="com.luv2code.springdemo.HappyFortuneService">
    </bean>

    <bean id="myCoach" class="com.luv2code.springdemo.BaseballCoach">
    <!-- set up constructor injection -->
    <constructor-arg ref="myFortune" />
    </bean>
    当我们配置好后,这个时候再在主程序中执行Coach theCoach = (Coach) context.getBean("myCoach");,这个时候spring container(object factory)就会按照IOC和DI把组装好的对象直接给我们用了。

所以Dependency Injection帮我们做了什么呢?

1
2
3
4
5
6
7
8
   <!-- define the dependency(helper object) -->
<bean id="myFortune" class="com.luv2code.springdemo.HappyFortuneService">
</bean>

<bean id="myCoach" class="com.luv2code.springdemo.BaseballCoach">
<!-- set up constructor injection -->
<constructor-arg ref="myFortune" />
</bean>

等价于

1
2
HappyFortuneService myFortuneService = new HappyFortuneService();//Dependency Injection帮助我们创建helper object,并帮我们注入到我们需要的Bean里面
BaseballCoach myCoach = new BaseballCoach(myFortuneService);//这里Spring调用的是BaseballCoach的constructor是IOC的作用

IOC+DI使得我们的程序耦合度非常低,所有的关键信息都在配置文件中体现,我们的程序的高度可复用的!

XML configurarion - Setter injection

Setter Injection的开发流程:

  1. Define the dependency interface and class
  2. Create setter method in your class for injection
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.luv2code.springdemo;
    public class CricketCoach implements Coach {
    private FortuneService fortuneService;

    public CricketCoach() {
    System.out.println("Call Cricket Coach Constructor");
    }
    // our setter method
    public void setFortuneService(FortuneService fortuneService) {
    System.out.println("Call Cricket Coach setter method");
    this.fortuneService = fortuneService;
    }

    @Override
    public String getDailyWorkout() {
    return "Practise fast bowling for 15 minutes";
    }

    @Override
    public String getDailyFortune() {
    return fortuneService.getFortune();
    }
    }
  3. Configure the dependency injdection in spring config file
    1
    2
    3
    <bean id="myCricketCoach" class="com.luv2code.springdemo.CricketCoach">
    <property name="fortuneService" ref="myFortune"></property>
    </bean>
    spring会去寻找name属性对应的setter函数进行依赖注入,寻找的原则是set+name(首字母大写),所以这里会去找setFortuneService()这个函数。

从输出可以看出spring在setter依赖注入时先调用没有参数的constructor,然后调用setter进行依赖注入。


上面依赖注入的是类,如果注入的是String的话则有不一样的用法:

1
2
3
4
5
6
7
8
<bean id="myCricketCoach" class="com.luv2code.springdemo.CricketCoach">
<!-- set up setter injection -->
<property name="fortuneService" ref="myFortune"></property>

<!-- inject literal values -->
<property name="emailAddress" value="shiyuliucu@gmail.com"></property>
<property name="team" value="Sachid"></property>
</bean>

更好的做法是把value写在properties file。

  1. create properties file
    创建一个sport.properties文件,在里面加入这两行
    foo.email = shiyuliucu@gmail.com
    foo.team = Sachid
  2. load properties file in spring config file

<context:property-placeholder location="classpath:sport.properties" />

  1. reference values from properties file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?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.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">

<context:property-placeholder location="classpath:sport.properties" />

<!-- define the dependency(helper object) -->
<bean id="myFortune" class="com.luv2code.springdemo.HappyFortuneService">
</bean>

<bean id="myCoach" class="com.luv2code.springdemo.BaseballCoach">
<!-- set up constructor injection -->
<constructor-arg ref="myFortune" />
</bean>

<bean id="myCricketCoach" class="com.luv2code.springdemo.CricketCoach">
<!-- set up setter injection -->
<property name="fortuneService" ref="myFortune"></property>

<!-- inject literal values -->
<property name="emailAddress" value="${foo.email}"></property>
<property name="team" value="${foo.team}"></property>
</bean>
</beans>

Java Annotations(Autowiring) - Contructor Injection

For dependency injection, Spring can use auto wiring. Spring will look for a class or interface that matches the property. And then, Spring will inject it automatically.
Autowiring的实现过程:Spring会扫描所有的@Component注解,如果发现里面有@autowired注解的成员变量,那么就会去找相对应的实现类并自动进行依赖注入。

开发流程是:

  1. Define the independency interface and class
  2. Create a constructor in your class for injection
  3. Configure the dependency injection with @Autowired Annotation
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package com.luv2code.springdemo;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;

    @Component("thatSillyCoach")
    public class TennisCoach implements Coach {

    private FortuneService fortuneService;

    @Autowired
    public TennisCoach(FortuneService fs) {
    this.fortuneService = fs;
    }

    @Override
    public String getDailyWorkout() {
    return "Practise your backhand volley";
    }

    @Override
    public String getDailyFortune() {
    return fortuneService.getFortune();
    }
    }

Java Annotations(Autowiring) - Setter Injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.luv2code.springdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("thatSillyCoach")
public class TennisCoach implements Coach {

private FortuneService fortuneService;

public TennisCoach() {
System.out.println("Tennis Coach: inside default constructor");
}


@Autowired
public void setFortuneService(FortuneService fortuneService) {
this.fortuneService = fortuneService;
}



@Override
public String getDailyWorkout() {
return "Practise your backhand volley";
}

@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}

Java Annotations(Autowiring) - Field Injection

其实最简单的方式是我们直接在class的fields上标注@Autowired,那么他们就会自动进行依赖注入,这是通过java的反射机制完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.luv2code.springdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component("thatSillyCoach")
public class TennisCoach implements Coach {
@Autowired
private FortuneService fortuneService;

public TennisCoach() {
System.out.println("Tennis Coach: inside default constructor");
}

@Override
public String getDailyWorkout() {
return "Practise your backhand volley";
}

@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}

Java Annotations(Autowiring) - Qualifiers

现在出现了一个很严重的问题:前面我们的接口只有一个实现类,所以spring直到要用这个类进行依赖注入,但是如果对于一个接口有多个实现类呢?这个时候就用到了Qualifiers(限定词)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.luv2code.springdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component("thatSillyCoach")
public class TennisCoach implements Coach {
@Autowired
@Qualifier("happyFortuneService")
private FortuneService fortuneService;

public TennisCoach() {
System.out.println("Tennis Coach: inside default constructor");
}

@Override
public String getDailyWorkout() {
return "Practise your backhand volley";
}

@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}

@Qualifier使用的必须是想用的类的类名(首字母小写)

Some tips

Annotations - Default Bean Names … and the Special Case
In general, when using Annotations, for the default bean name, Spring uses the following rule.
If the annotation’s value doesn’t indicate a bean name, an appropriate name will be built based on the short name of the class (with the first letter lower-cased).
For example:
HappyFortuneService –> happyFortuneService
However, for the special case of when BOTH the first and second characters of the class name are upper case, then the name is NOT converted.
For the case of RESTFortuneService
RESTFortuneService –> RESTFortuneService
No conversion since the first two characters are upper case.
Behind the scenes, Spring uses the Java Beans Introspector to generate the default bean name. Here’s a screenshot of the documentation for the key method.
https://docs.oracle.com/javase/8/docs/api/java/beans/Introspector.html#decapitalize(java.lang.String)


Using @Qualifier with Constructors
@Qualifier is a nice feature, but it is tricky when used with Constructors.

The syntax is much different from other examples and not exactly intuitive. Consider this the “deep end of the pool” when it comes to Spring configuration LOL :-)

You have to place the @Qualifier annotation inside of the constructor arguments.

Here’s an example from our classroom example. I updated it to make use of constructor injection, with @Autowired and @Qualifier. Make note of the code in bold below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.luv2code.springdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class TennisCoach implements Coach {

private FortuneService fortuneService;

// define a default constructor
public TennisCoach() {
System.out.println(">> TennisCoach: inside default constructor");
}

@Autowired
public TennisCoach(@Qualifier("randomFortuneService") FortuneService theFortuneService) {

System.out.println(">> TennisCoach: inside constructor using @autowired and @qualifier");

fortuneService = theFortuneService;
}


/*
@Autowired
public void doSomeCrazyStuff(FortuneService theFortuneService) {
System.out.println(">> TennisCoach: inside doSomeCrazyStuff() method");
fortuneService = theFortuneService;
}
*/

/*
@Autowired
public TennisCoach(FortuneService theFortuneService) {
fortuneService = theFortuneService;
}
*/

@Override
public String getDailyWorkout() {
return "Practice your backhand volley";
}

@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}

}

For detailed documentation on using @Qualified with Constructors, see this link in the Spring Reference Manual
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-autowired-annotation-qualifiers


How to inject properties file using Java annotations
FAQ: How to inject properties file using Java annotations

Answer:

This solution will show you how inject values from a properties file using annotatons. The values will no longer be hard coded in the Java code.

  1. Create a properties file to hold your properties. It will be a name value pair.
    New text file: src/sport.properties

foo.email=myeasycoach@luv2code.com
foo.team=Silly Java Coders
Note the location of the properties file is very important. It must be stored in src/sport.properties

  1. Load the properties file in the XML config file.
    File: applicationContext.xml
    Add the following lines:

    1
    <context:property-placeholder location="classpath:sport.properties"/>  

    This should appear just after the <context:component-scan …/> line

  2. Inject the properties values into your Swim Coach: SwimCoach.java

    1
    2
    3
    4
    5
    @Value("${foo.email}")
    private String email;

    @Value("${foo.team}")
    private String team;

Spring Beans Scopes and Lifecycle

Beans Scopes

Scope指的是Bean对象的作用范围

  • How long does a bean live
  • How many instances are created
  • How is the bean shared

1.默认的作用范围是singleton

  • Spring container(Object Factory) 只会默认为这个bean创建一个实例
  • It is cashed in memory
  • All requests for the bean will return a SHARED reference to the same BEAN

2.prototype: Creates a new bean instance for each container request

3.request: Scoped to a HTTP web request. Only used for web apps.
4.session: Scoped to a HTTP web session. Only used for web apps.
5.global-session: Scoped to a global HTTP web session. Only used for web apps.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.luv2code.springdemo;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class BeanScopeDemoApp {

public static void main(String[] args) {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("beanScope-applicationContext.xml");
Coach theCoach = context.getBean("myCoach", Coach.class);
Coach alphaCoach = context.getBean("myCoach", Coach.class);

//check if they are the same
System.out.println(theCoach==alphaCoach);

context.close();
}
}

singleton模式下结果是true,prototype模式下结果是false。


我们也可以使用Anotation @Scope:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.luv2code.springdemo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component("thatSillyCoach")
@Scope("prototype")//在这里进行scope的注解
public class TennisCoach implements Coach {
@Autowired
@Qualifier("happyFortuneService")
private FortuneService fortuneService;

public TennisCoach() {
System.out.println("Tennis Coach: inside default constructor");
}

@Override
public String getDailyWorkout() {
return "Practise your backhand volley";
}

@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}
}

Beans lifecycle

we can add custom code during bean intialization

  • calling custom business logic method
  • setting up handles to resources(db, sockets, file, etc)

we can add custom code during bean destruction

  • calling custom business logic method
  • cleaning up handles to resources(db, sockets, file, etc)

使用initialization和destruction的方法是先定义init或者destroy函数,然后再spring配置文件中进行配置。

1
2
3
4
5
<bean id="myCoach" class="com.luv2code.springdemo.TrackCoach" 
init-method="doMyStartUpStuff" destroy-method="doMyCleanUpStuff">
<!-- set up constructor injection -->
<constructor-arg ref="myFortune" />
</bean>

我们也可以使用Annotation:@PostConstruct and @PreDestroy

Special Note about @PostConstruct and @PreDestroy Method Signatures

I want to provide additional details regarding the method signatures of @PostContruct and @PreDestroy methods.

Access modifier

The method can have any access modifier (public, protected, private)

Return type
The method can have any return type. However, “void’ is most commonly used. If you give a return type just note that you will not be able to capture the return value. As a result, “void” is commonly used.

Method name
The method can have any method name.

Arguments
The method can not accept any arguments. The method should be no-arg.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.luv2code.springdemo;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component("thatSillyCoach")
public class TennisCoach implements Coach {
@Autowired
@Qualifier("happyFortuneService")
private FortuneService fortuneService;

public TennisCoach() {
System.out.println("Tennis Coach: inside default constructor");
}

@Override
public String getDailyWorkout() {
return "Practise your backhand volley";
}

@Override
public String getDailyFortune() {
return fortuneService.getFortune();
}

@PostConstruct
//define my init method
public void doStartUpStuff() {
System.out.println("Tennis Coach: inside default doStartUpStuff()");
}

@PreDestroy
//define my destroy method
public void doCleanUpStuff() {
System.out.println("Tennis Coach: inside default doCleanUpStuff()");
}
}

Spring Configuration with java code(no xml)

3 ways to configure Spring Container:

  1. Full xml config
  2. xml Component scan + Annotation
  3. Java configuration class

开发流程:

  1. Create a Java Class and annotate as @Configuration
  2. Add component scanning support: @ComponentScan(optional)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    package com.luv2code.springdemo;

    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;

    @Configuration
    @ComponentScan("com.luv2code.springdemo")
    public class SportConfig {

    }
  3. Read Spring Configuration class
  4. Retrieve bean from Spring container
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.luv2code.springdemo;

    import org.springframework.context.annotation.AnnotationConfigApplicationContext;

    public class JavaConfigDemoApp {

    public static void main(String[] args) {
    //read spring config file
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SportConfig.class);
    //get the bean from spring container
    Coach theCoach = context.getBean("thatSillyCoach", Coach.class);
    //call a method on the bean
    System.out.println(theCoach.getDailyWorkout());
    System.out.println(theCoach.getDailyFortune());
    //close the context
    context.close();
    }
    }

Java config的方式不仅可以定义Spring Container,也可以定义Bean Object
开发流程:

  1. Define method to expose bean using @Bean
  2. Inject bean dependencies
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.luv2code.springdemo;

    import org.springframework.context.annotation.Bean;
    //import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;

    @Configuration
    //@ComponentScan("com.luv2code.springdemo") 因为我们定义好Bean了,所以也不需要自动扫描了
    public class SportConfig {
    //define bean for our sad fortune service

    @Bean
    public FortuneService sadFortuneService() {//函数名是Bean id
    return new SadFortuneService();
    }

    //define bean for our swim coach AND inject dependency
    @Bean
    public Coach swimCoach() {
    return new SwimCoach(sadFortuneService());
    }
    }
  3. Read Spring Java configuration class
  4. Retrieve bean from Spring Container
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.luv2code.springdemo;

    import org.springframework.context.annotation.AnnotationConfigApplicationContext;

    public class SwimJavaConfigDemoApp {

    public static void main(String[] args) {
    //read spring config file
    AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SportConfig.class);
    //get the bean from spring container
    Coach theCoach = context.getBean("swimCoach", Coach.class);
    //call a method on the bean
    System.out.println(theCoach.getDailyWorkout());
    System.out.println(theCoach.getDailyFortune());
    //close the context
    context.close();
    }
    }

使用java code从properties file中读取数据也是可以的。
开发流程是:

  1. Create properties file
  2. Load properties file in Spring Config
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.luv2code.springdemo;

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;

    @Configuration
    @PropertySource("classpath:Sport.properties")
    public class SportConfig {
    //define bean for our sad fortune service
    @Bean
    public FortuneService sadFortuneService() {//函数名是Bean id
    return new SadFortuneService();
    }

    //define bean for our swim coach AND inject dependency
    @Bean
    public Coach swimCoach() {
    return new SwimCoach(sadFortuneService());
    }
    }
  3. Reference Value from Properties File
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    package com.luv2code.springdemo;

    import org.springframework.beans.factory.annotation.Value;

    public class SwimCoach implements Coach {

    private FortuneService fortuneService;

    @Value("${foo.email}")
    private String email;

    @Value("${foo.team}")
    private String team;

    public String getEmail() {
    return email;
    }

    public String getTeam() {
    return team;
    }

    public SwimCoach(FortuneService fs) {
    this.fortuneService = fs;
    }

    @Override
    public String getDailyWorkout() {
    return "Swim 1000 meters as a wram up";
    }

    @Override
    public String getDailyFortune() {
    return this.fortuneService.getFortune();
    }
    }

Spring MVC

  • Controller
    • Code created by developer
    • Contains your business logic
      • handle the request
      • store/retrieve data(db, web service…)
      • place data in a model
    • send to appropriate view template
  • Model
    • contains your data
    • store/retrieve data via backend systems
      • database, wev services …
      • using a spring bean if you like
    • place your data in the model
      • data can be any java object/collection
  • View Template
    • Spring MVC is flexible
      • support many view templates
    • Most common is JSP+JSTL
    • developer creates a page
      • display data

Configuration

因为所有的java web项目本质上都是对servlets的封装并加入一些方便程序员编写的特性,所以配置SpringMVC的第一步就是在web.xml文件中配置Spring写好的DispatcherServlet,Servlet的相关知识包含在在Servlets and JSPs的课程里。

  • JAR configuration: 将所需的jar file放入WebContent/WEBINF/lib中,eclipse自动将他们加载到项目的classpath中。所需的jar有spring core的jar和JSTL的jar
  • web.xml(配置servlet)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    id="WebApp_ID" version="3.1">

    <display-name>spring-mvc-demo</display-name>

    <absolute-ordering />

    <!-- Spring MVC Configs -->

    <!-- Step 1: Configure Spring MVC Dispatcher Servlet -->
    <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
    <!-- servlet在启动前扫描spring-mvc-demo-servlet.xml的配置进行IOC和DI -->
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/spring-mvc-demo-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Step 2: Set up URL mapping for Spring MVC Dispatcher Servlet -->
    <!-- 这一句相当于所有访问的指定都会给到spring提供的这个servlet去 -->
    <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
    </servlet-mapping>

    </web-app>
  • applicationContext.xml(这个demo里面名字叫spring-mvc-demo-servlet.xml)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <?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:mvc="http://www.springframework.org/schema/mvc"
    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/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <!-- Step 3: Add support for component scanning -->
    <context:component-scan base-package="com.luv2code.springdemo" />

    <!-- Step 4: Add support for conversion, formatting and validation support -->
    <mvc:annotation-driven/>

    <!-- Step 5: Define Spring MVC view resolver -->
    <!-- 这里是告诉ViewResolver我们的jsp文件都放在什么地方了,这样SpringMVC才能找到它们。
    也就是ViewResolver会自动去/WEB-INF/view/文件夹下去找.jsp文件 -->
    <bean
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/" />
    <property name="suffix" value=".jsp" />
    </bean>

    </beans>

Create Controllers and Views

Basic Controller开发流程:

  1. Create Controller Class
    对class使用@Controller这个Annotation,@Controller继承自@Component,所以Spring也会自动扫描@Controller的类。
  2. Define Controller Method
  3. Add Request Mapping to Controller method
  4. Return View Name
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package com.luv2code.springdemo.mvc;

    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;

    @Controller
    public class HomeController {

    @RequestMapping("/")
    public String showPage() {//函数名,参数都是随意的
    return "main-menu"; //由于我们的配置,spring会去找/WEB-INF/view/main-menu.jsp
    }
    }
  5. Develop View Page
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>Spring MVC demo - Home Page</title>
    </head>
    <body>
    <h1>Spring MVC demo - Home Page</h1>
    </body>
    </html>

Question
How does component scan work in this example? You have different package names.
You listed the component scan package as: com.luv2code.springdemo
But the our MVC controllers are defined in com.luv2code.springdemo.mvc

Answer
For the Spring attribute: base-package=”com.luv2code.springdemo”
Spring will recursively scan for components starting at the base package: “com.luv2code.springdemo”
When I say “recursive”, it means that Spring will start at the base package and scan all sub packages.
The package com.luv2code.springdemo.mvc is a sub package because of naming structure, just like folders on a file system.
As a result, it will be included in the scan.


Controller进阶:读取html表格里的数据
HelloWorldController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.luv2code.springdemo.mvc;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloWorldController {

//need a controller method to show the initial HTML form
@RequestMapping("/showForm")
public String showForm() {
return "helloworld-form";
}

//need a controller method to process the HTML form
@RequestMapping("/processForm")
public String processForm() {
return "helloworld";
}
}

helloworld-form.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World - Input Form</title>
</head>
<body>
<form action="processForm" method="get">
<input type="text" name="studentName" placeholder="what's your name" />
<input type="submit" />
</form>

</body>
</html>

helloworld.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello world of spring!</title>
</head>
<body>
<!-- EL表达式,比我学的JSP进化了一些,不用写复杂的JAVA code了-->
<!-- Student Name: <%= request.getParameter("studentName") %> -->
Student Name: ${param.studentName}
</body>
</html>

Controller进阶:Adding data to the Spring Model
Spring Model:

  • The Model is a container for your application data
  • In your controller
    • You can put anything in a model
    • strings, object, info from database, etc…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.luv2code.springdemo.mvc;

import javax.servlet.http.HttpServletRequest;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class HelloWorldController {

//need a controller method to show the initial HTML form
@RequestMapping("/showForm")
public String showForm() {
return "helloworld-form";
}

//need a controller method to process the HTML form
@RequestMapping("/processForm")
public String processForm() {
return "helloworld";
}

//new a controller method to read form data and add data to the model
@RequestMapping("/processFormVersionTwo")
public String letsShoutDude(HttpServletRequest request, Model model) {
//read the request parameter from the HTML form
String theName = request.getParameter("studentName");
//convert data to all caps
theName = theName.toUpperCase();
//create the message
String result = "Yo! " + theName;
//add message to model
model.addAttribute("message", result);
return "helloworld";
}
}
  • Your view page(JSP) can access data from model
1
2
3
4
5
6
7
8
9
10
11
12
13
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello world of spring!</title>
</head>
<body>
Student Name: ${message}

</body>
</html>

Controller进阶: How do I use CSS, JavaScript and Images in a Spring MVC Web App?
Here are the steps on how to access static resources in a Spring MVC. For example, you can use this to access images, css, JavaScript files etc.

Any static resource is processed as a URL Mapping in Spring MVC. You can configure references to static resources in the spring-mvc-demo-servlet.xml.

In my example, I’m going to have the following directory structure:
I chose to put everything in the “resources” directory. But you can use any name for “resources”, such as “assets”, “foobar” etc. Also, you can give any name that you want for the subdirectories under “resources”.

Step 1: Add the following entry to your Spring MVC configuration file: spring-mvc-demo-servlet.xml

You can place this entry anywhere in your Spring MVC config file.

<mvc:resources mapping="/resources/**" location="/resources/"></mvc:resources>

Step 2: Now in your view pages, you can access the static files using this syntax:

<img src="${pageContext.request.contextPath}/resources/images/spring-logo.png">

You need to use the JSP expression ${pageContext.request.contextPath} to access the correct root directory for your web application.

Apply the same technique for reading CSS and JavaScript.

Here’s a full example that reads CSS, JavaScript and images.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

<!DOCTYPE html> <html>

<head>

<link rel="stylesheet" type="text/css"

href="${pageContext.request.contextPath}/resources/css/my-test.css">

<script src="${pageContext.request.contextPath}/resources/js/simple-test.js"></script>

</head>

<body>

<h2>Spring MVC Demo - Home Page</h2>

<a href="showForm">Plain Hello World</a>

<br><br>

<img src="${pageContext.request.contextPath}/resources/images/spring-logo.png" />

<br><br>

<input type="button" onclick="doSomeWork()" value="Click Me"/>

</body>

</html>

Request Params and Request Mapping

Bind data using @RequestParam Annotation
上面我们是使用String theName = request.getParameter("studentName");,Spring提供了@RequestParam注解来直接读取request的内容并bind到我们的参数中。

1
2
3
4
5
6
7
8
9
10
@RequestMapping("/processFormVersionThree")
public String processFormVersionThree(@RequestParam("studentName") String theName, Model model) {
//convert data to all caps
theName = theName.toUpperCase();
//create the message
String result = "Yo! " + theName;
//add message to model
model.addAttribute("message", result);
return "helloworld";
}

我们需要理解的是:这里我们是因为要对request params进行处理,所以先将request params使用@RequestParam绑定到theName变量上,再将处理后的结果放入model中让我们的前端接收到,但是其实在前端仍旧是可以使用{$param.studentName}去获取最初始的request params的,只不过拿到的是没处理过得!


Controller level Request Mapping
我们可以在Controller层面添加一个Request Mapping,那么我们所有函数的Request Mapping都将相对于这个路径。

Spring MVC Form Tags and data binding

Spring MVC Form Tags are the building block for a web page. Form Tags are configurable and resuable for a web page.
Spring MVC Form Tags can make use of data binding. Automatically setting/retrieving data from a Java Bean/Object.

Text Fields
student-form.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!-- 用了这一句我们才能使用Form Tags -->
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Student Registration Form</title>
</head>
<body>
<!-- modelAttribute对应着我们加入model的对象的名字 -->
<form:form action="processForm" modelAttribute="student">
<!-- path对应着对象的变量名 -->
First Name: <form:input path="firstName"/>
<br>
Last Name: <form:input path="lastName"/>
<br>
<input type="submit"/>
</form:form>

</body>
</html>

When form is loaded, Spring MVC will call student.getFirstName(), student.getLastName(). 也就是Spring根据modelAttribute和path去自动调用get函数。
When form is submitted, Spring MVC will call student.setFirstName(), student.setLastName().

Student.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.luv2code.springdemo.mvc;

public class Student {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}

StudentController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.luv2code.springdemo.mvc;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/student")
public class StudentController {
@RequestMapping("/showForm")
public String showForm(Model model) {

//create a student object
Student theStudent = new Student();

//add student object to the model
//NAME VALUE
model.addAttribute("student", theStudent);

return "student-form";
}

@RequestMapping("/processForm")
public String processForm(@ModelAttribute("student") Student theStudent) {
return "student-confirmation";
}

}

student-confirmation.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Student Confirmation</title>
</head>
<body>
The student is confirmed: ${student.firstName} ${student.lastName}
<br>

</body>
</html>

student.firstName会call student.getFirstName()函数。

我们需要理解的是:这里我们是直接从student-form.jsp把Model传给student-confirmation.form,然后在前端我们直接使用绑定好的{$student.firstName}即可。但是request params仍旧是存在的,我们在前端仍旧可以使用{$param。firstName}来获取数据,只是为了符合MVC里的Model的概念,我们希望在Controller之间传递的是Model而不是一个又一个的参数!


Drop Down Lists
student-form.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!-- 用了这一句我们才能使用Form Tags -->
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Student Registration Form</title>
</head>
<body>
<!-- modelAttribute对应着我们加入model的对象的名字 -->
<form:form action="processForm" modelAttribute="student">

<!-- Text Fields -->
<!-- path对应着对象的变量名 -->
First Name: <form:input path="firstName"/>
<br>
Last Name: <form:input path="lastName"/>
<br>

<!-- Dropdown Lists -->
Country:
<form:select path="country">
<!-- value是java code的值,label是在网页上显示的值 -->
<form:option value="Brasil" label="BRA"></form:option>
<form:option value="France" label="Fra"></form:option>
<form:option value="Germany" label="DEU"></form:option>
<form:option value="India" label="INR"></form:option>
</form:select>
<br>
<input type="submit"/>
</form:form>

</body>
</html>

student-confirmation.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Student Confirmation</title>
</head>
<body>
The student is confirmed: ${student.firstName} ${student.lastName}
<br>
The student's country is: ${student.country}
</body>
</html>

这种写法是吧所有的选项都在jsp里面列出来,但是在我们真正的项目中,我们往往是从数据库中读取数据然后显示一个下拉菜单。
student.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.luv2code.springdemo.mvc;

import java.util.LinkedHashMap;

public class Student {
private String firstName;
private String lastName;
private String country;

private LinkedHashMap<String, String> countryOptions;

public Student() {
//模仿从数据库拿数据的过程
countryOptions = new LinkedHashMap<String, String>();
countryOptions.put("BR", "Brasil");
countryOptions.put("FR", "France");
countryOptions.put("DE", "Germany");
countryOptions.put("IN", "India");
countryOptions.put("BR", "Brasil");
}
public LinkedHashMap<String, String> getCountryOptions() {
return countryOptions;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}

student-form.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!-- 用了这一句我们才能使用Form Tags -->
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Student Registration Form</title>
</head>
<body>
<!-- modelAttribute对应着我们加入model的对象的名字 -->
<form:form action="processForm" modelAttribute="student">

<!-- Text Fields -->
<!-- path对应着对象的变量名 -->
First Name: <form:input path="firstName"/>
<br>
Last Name: <form:input path="lastName"/>
<br>

<!-- Dropdown Lists -->
Country:
<form:select path="country">
<!-- items指向的是java中的一个collection。Spring会call student.getCountryOptions() -->
<form:options items="${student.countryOptions}"/>
</form:select>
<br>
<input type="submit"/>
</form:form>

</body>
</html>

Radio buttons

1
2
3
4
5
6
7
<!-- Radio Buttons -->
Favorite Languages:
Java<form:radiobutton path="favoriteLanguage" value="Java"/>
C#<form:radiobutton path="favoriteLanguage" value="C#"/>
PHP<form:radiobutton path="favoriteLanguage" value="JaPHPva"/>
Ruby<form:radiobutton path="favoriteLanguage" value="Ruby"/>
<br>

当我们提交form的时候,会call setFavoriteLanguage()函数。
在student-confirmation.jsp页面中的${student.favoriteLanguage}会call student.getFavoriteLanguage()。

Ckeckboxes
student-form.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!-- 用了这一句我们才能使用Form Tags -->
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Student Registration Form</title>
</head>
<body>
<!-- modelAttribute对应着我们加入model的对象的名字 -->
<form:form action="processForm" modelAttribute="student" method="post">

<!-- Text Fields -->
<!-- path对应着对象的变量名 -->
First Name: <form:input path="firstName"/>
<br>
Last Name: <form:input path="lastName"/>
<br>

<!-- Dropdown Lists -->
Country:
<form:select path="country">
<!-- items指向的是java中的一个collection。Spring会call student.getCountryOptions() -->
<form:options items="${student.countryOptions}"/>
</form:select>
<br>

<!-- Radio Buttons -->
Favorite Languages:
Java<form:radiobutton path="favoriteLanguage" value="Java"/>
C#<form:radiobutton path="favoriteLanguage" value="C#"/>
PHP<form:radiobutton path="favoriteLanguage" value="JaPHPva"/>
Ruby<form:radiobutton path="favoriteLanguage" value="Ruby"/>
<br>

<!-- checkbox -->
Operating Systems:
linux<form:checkbox path="operatingSystems" value="linux" />
mac<form:checkbox path="operatingSystems" value="mac" />
windows<form:checkbox path="operatingSystems" value="windows" />
<br>

<input type="submit"/>
</form:form>

</body>
</html>

Student.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.luv2code.springdemo.mvc;

import java.util.LinkedHashMap;

public class Student {
private String firstName;
private String lastName;
private String country;
private String favoriteLanguage;
private String[] operatingSystems;


public String[] getOperatingSystems() {
return operatingSystems;
}
public void setOperatingSystems(String[] operatingSystems) {
this.operatingSystems = operatingSystems;
}
public String getFavoriteLanguage() {
return favoriteLanguage;
}
public void setFavoriteLanguage(String favoriteLanguage) {
this.favoriteLanguage = favoriteLanguage;
}
private LinkedHashMap<String, String> countryOptions;

public Student() {
//模仿从数据库拿数据的过程
countryOptions = new LinkedHashMap<String, String>();
countryOptions.put("BR", "Brasil");
countryOptions.put("FR", "France");
countryOptions.put("DE", "Germany");
countryOptions.put("IN", "India");
countryOptions.put("BR", "Brasil");
}
public LinkedHashMap<String, String> getCountryOptions() {
return countryOptions;
}
public String getCountry() {
return country;
}
public void setCountry(String country) {
this.country = country;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}

由于checkboxes可以选多个,所以这里是数组。

student-confirmation.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!-- 使用jstl,这样我们才能使用for loop -->
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Student Confirmation</title>
</head>
<body>
The student is confirmed: ${student.firstName} ${student.lastName}
<br>
The student's country is: ${student.country}
<br>
Favorite Language is: ${student.favoriteLanguage}
<br>
Operating Systems is:
<ul>
<!-- jstl -->
<c:forEach var="temp" items="${student.operatingSystems}">
<li>${temp}</li>
</c:forEach>
</ul>
</body>
</html>

使用jstl去做for loop。

Form Validation

Java’s Standard Bean Validation API:

  • Java has a standard Bean validation API
  • defines a metadata model and API for entity validation
  • not tied to either the web tier or the persistence tier
  • available for server-side apps and also client-side JavaFX/Swing apps

Spring and validation

  • Spring version 4 and higher supports Beans Validation API
  • Preferred method for validation when building Spring apps
  • Simply add Validation JARs to our project

Validation features include: required, validate length, validate numbers, validate with regular expressions, custom validation

我们使用的Validation JAR是Hibernate Team开发的。https://hibernate.org/validator/


Validate Required Fields
开发流程:

  • Add validation rule to Customer class

Customer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.luv2code.springdemo.mvc;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

public class Customer {
private String firstName;

@NotNull(message = "is required")
@Size(min=1)
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}

}
  • Display error messages on HTML form

customer-form.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Customer Register page</title>
<style type="text/css">
.error {color:red}
</style>
</head>
<body>
Fill out the form. * means required.
<form:form action="processForm" modelAttribute="customer">
First Name: <form:input path="firstName" />
<br>
Last Name (*): <form:input path="lastName" />
<form:errors path="lastName" cssClass="error" />

<br>
<input type="submit" value="Submit" />

</form:form>

</body>
</html>
  • Perform validation in the Controller class

ConstomerContoller.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.luv2code.springdemo.mvc;

import javax.validation.Valid;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;


@Controller
@RequestMapping("/customer")
public class CustomerController {

@RequestMapping("/showForm")
public String showForm(Model model) {
model.addAttribute("customer", new Customer());
return "customer-form";
}

@RequestMapping("/processForm")
public String processForm(@Valid @ModelAttribute Customer theCustomer, BindingResult bindingResult) {
if(bindingResult.hasErrors()) {
return "customer-form";
}
else {
return "customer-confirmation";
}
}
}

@Valid注解可以判断Validation是否符合并可以在后面跟一个BindingResult参数。

  • Update confirmation page

cutomer-confirmation.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Customer Confirmation Page</title>
</head>
<body>
The customer is confirmed: ${customer.firstName} ${customer.lastName}
</body>
</html>

现在还有一个问题是如果我们的lastName是whitespaces时,我们的validation不能检测到。
我们采用的方法是使用@InitBinder,它的作用是pre-process each web request to our controller.

1
2
3
4
5
6
7
// add an initbinder
// remove leading and trailing whitespace
@InitBinder
public void InitBinder(WebDataBinder dataBinder) {
StringTrimmerEditor stringTrimmerEditor = new StringTrimmerEditor(true);
dataBinder.registerCustomEditor(StringTrimmerEditor.class, stringTrimmerEditor);
}

StringTrimmerEditor类会帮我们去掉leading和trailing whitespace,去掉以后还会检查输入是不是长度为零,为零的话会将其变为null。


Validate Number Range

开发流程:

  • Add validation rule to Customer class

Customer.java

1
2
3
4
5
6
7
8
9
10
@Min(value=0, message = "must be greater or equal to 0")
@Max(value=10, message = "must be less or equal to 10")
private int freePasses;

public int getFreePasses() {
return freePasses;
}
public void setFreePasses(int freePasses) {
this.freePasses = freePasses;
}
  • Display error messages on HTML form

customer-form.jsp

1
2
3
Free Passes: <form:input path="freePasses" />
<form:errors path="freePasses" cssClass="error" ></form:errors>
<br>
  • Perform validation in the Controller class
    这一步不用改了,就是@Valid注解
  • Update confirmation page

如果我们还想要freePasses非空,那么我们可以加入@notNull注解,但是错误信息显示int是不能处理not null的,所以我们将其转换为Integer。但这个时候我们如果再输入一大堆String,错误信息会是”不能将String转为Integer”。
我们的解决办法是加入custom error message。(https://www.udemy.com/course/spring-hibernate-tutorial/learn/lecture/6747542#questions)


Validation with regular expression

开发流程:

  • Add validation rule to Customer class

Customer.java

1
2
3
4
5
6
7
8
9
@Pattern(regexp = "[a-zA-Z0-9]{5}", message = "only 5 chars/digits")
private String postalCode;

public String getPostalCode() {
return postalCode;
}
public void setPostalCode(String postalCode) {
this.postalCode = postalCode;
}
  • Display error messages on HTML form

customer-form.jsp

1
2
3
Postal Code: <form:input path="postalCode" />
<form:errors path="postalCode" cssClass="error" ></form:errors>
<br>
  • Perform validation in the Controller class
    这一步不用改了,就是@Valid注解和BindingReasult
  • Update confirmation page

Custom Validation(A Custom Annotation)
为我们的coursecode定制一个validation:前缀必须为luv
开发流程

  • Create custom validation rule
    • Create @CourceCode annotation
    CourseCode.java
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.luv2code.springdemo.mvc.validation;

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;

    import javax.validation.Constraint;
    import javax.validation.Payload;

    @Constraint(validatedBy = CourseCodeConstrainValidator.class) //helper class that contains business rules/validation logic
    @Target({ElementType.METHOD, ElementType.FIELD}) //can apply our annotation to a method or field
    @Retention(RetentionPolicy.RUNTIME) //Retain this annotation in the Java class file. Process it at runtime
    public @interface CourceCode {
    //define default course code
    public String value() default "LUV";
    //define default error message
    public String message() default "Must start with LUV";
    //define default groups
    public Class<?>[] groups() default {};
    //define default payload
    public Class<? extends Payload>[] payload() default {};
    }

    • Create CourseCodeConstraintValidator
    CourseCodeConstrainValidator.class
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    package com.luv2code.springdemo.mvc.validation;

    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;

    public class CourseCodeConstrainValidator implements ConstraintValidator<CourseCode, String>{

    private String coursePrefix;

    @Override
    public void initialize(CourseCode theCourseCode) {
    coursePrefix = theCourseCode.value();
    }

    @Override
    public boolean isValid(String theCode, ConstraintValidatorContext constraintValidatorContext) {
    boolean result;
    if(theCode!=null) {
    result = theCode.startsWith(coursePrefix);
    }
    else {
    result = true;
    }

    return result;
    }

    }
  • Add validation rule to Customer Class

Customer.java

1
2
3
4
5
6
7
8
9
@CourseCode(value = "luv", message="must start with luv" )
private String courseCode;

public String getCourseCode() {
return courseCode;
}
public void setCourseCode(String courseCode) {
this.courseCode = courseCode;
}
  • Display error messages on HTML form

customer-form.jsp

1
2
3
Course Code: <form:input path="courseCode" />
<form:errors path="courseCode" cssClass="error" ></form:errors>
<br>
  • Update confirmation page

Bonus: Deploying your App to Tomcat as a Web Application Archive (WAR) file**

When you deploy your Java web apps, you can make use of a Web Application Archive (WAR) file.

The Web Application Archive (WAR) file is a compressed version of your web application. It uses the zip file format but the file has the .war extension.

If you are using Eclipse, then the best way to visualize it is think of your “WebContent” directory being compressed as a zip file with the .war extension.

This includes all of your web pages, images, css etc. It also includes the WEB-INF directory which includes your classes in WEB-INF/classes and supporting JAR files in WEB-INF/lib.

The WAR file format is part of the Java EE / Servlet specification. As a result, all Java EE servers support this format (ie jboss, weblogic, websphere, glassfish and tomcat).

Below, I provide steps on how to create a WAR file in Eclipse. I also show how to deploy the WAR file on Tomcat.

  1. In Eclipse, stop Tomcat

  2. Right-click your project and select Export > WAR File

  3. In the Destination field, enter: /mycoolapp.war

  4. Outside of Eclipse, start Tomcat

  • If you are using MS Windows, then you should find it on the Start menu
  1. Make sure Tomcat is up and running by visiting: http://localhost:8080

  2. Deploy your new WAR file by copying it to \webapps

Give it about 10-15 seconds to make the deployment. You’ll know the deployment is over because you’ll see a new folder created in webapps … with your WAR file name.

  1. Visit your new app. If your war file was: mycoolapp.war then you can access it with: http://localhost:8080/mycoolapp/

Hibernate

Hibernate is a framework for persisting/saving Java Objects in a database.

Benefits of Hibernate: 1. Hibernate handles all of the low-level SQL 2. Minimizes the amount of JDBC code you have to develop 3.Hibernate provides the Object-to-Relational Mapping(ORM)

JDBC(Java Database Connection)是Java web程序连接数据库的时候使用的库文件,但是它的问题是需要手动写SQL语句。Hibernate是建立在JDBC上的ORM框架,本质上还是使用JDBC,只不过帮我们做好了封装。

开发Hibernate需要的其他库:1.JDK 2.Database Server(MySQL) 3.Hibernate JAR files and JDBC driver.

Configuration

  • Add Hibernate Configuration file

hibernate.cfg.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>

<!-- JDBC Database connection settings -->
<property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hb_student_tracker?useSSL=false&amp;serverTimezone=UTC</property>
<property name="connection.username">hbstudent</property>
<property name="connection.password">hbstudent</property>

<!-- JDBC connection pool settings ... using built-in test pool -->
<property name="connection.pool_size">1</property>

<!-- Select our SQL dialect -->
<property name="dialect">org.hibernate.dialect.MySQLDialect</property>

<!-- Echo the SQL to stdout -->
<property name="show_sql">true</property>

<!-- Set the current session context -->
<property name="current_session_context_class">thread</property>

</session-factory>

</hibernate-configuration>
  • Annotate Java Class
    • Map class to database
    • Map fields to database columns
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.luv2code.hibernate.demo.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name="student")
public class Student {

@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="id")
private int id;

@Column(name="first_name")
private String firstName;


@Column(name="last_name")
private String lastName;

@Column(name="email")
private String email;


public Student() {

}

public Student(String firstName, String lastName, String email) {
super();
this.firstName = firstName;
this.lastName = lastName;
this.email = email;
}

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getFirstName() {
return firstName;
}

public void setFirstName(String firstName) {
this.firstName = firstName;
}

public String getLastName() {
return lastName;
}

public void setLastName(String lastName) {
this.lastName = lastName;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

//for debug
@Override
public String toString() {
return "Student [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + "]";
}
}

Why we are using JPA Annotation instead of Hibernate ?

For example, why we are not using this org.hibernate.annotations.Entity?

ANSWER:
JPA is a standard specification. Hibernate is an implementation of the JPA specification.

Hibernate implements all of the JPA annotations.

The Hibernate team recommends the use of JPA annotations as a best practice.

Hibernate CRUD

Two Key Players:
- SessionFactory
- Reads the hibernate config file
- Creates Session objects
- Heavy-weight object
- Only create once in your app
- Session
- Wraps a JDBC connection
- Main object used to save/retrieve objects
- Short-lived object
- Retrieve from session-factory


Create Object

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.luv2code.hibernate.demo;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import com.luv2code.hibernate.demo.entity.Student;


public class CreateStudentDemo {

public static void main(String[] args) {
//create session factory
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Student.class)
.buildSessionFactory();

//create a session
Session session = factory.getCurrentSession();

//use the session object to save Java Object
try {
//Create a student object
System.out.println("Creating a new student object");
Student newStudent = new Student("shiyu", "liu", "shiyuliucu@gmail.com");
//Start a transaction
session.beginTransaction();
//Save the student object
System.out.println("Saving the student...");
session.save(newStudent);
//Commit transaction
session.getTransaction().commit();
} catch (Exception e) {
factory.close();
}
}
}

Read Objects
Student myStudent = session.get(Student.class, newStudent.getId());

Query Objects
Hibernate使用HQL进行query,其实就是SQL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package com.luv2code.hibernate.demo;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import com.luv2code.hibernate.demo.entity.Student;



public class QueryStudentDemo {

public static void main(String[] args) {
//create session factory
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Student.class)
.buildSessionFactory();

//create a session
Session session = factory.getCurrentSession();

//use the session object to save Java Object
try {

//Start a transaction
session.beginTransaction();

//query students
//这里from Student的Student是类的名字
List<Student> theStudents = session.createQuery("from Student").getResultList();
//display students
for(Student s:theStudents) {
System.out.println(s);
}

//query students: lastName="liu"
//use Java property name, not database column name
theStudents = session.createQuery("from Student s where s.lastName='liu' ").getResultList();
//display students
for(Student s:theStudents) {
System.out.println(s);
}

//query students: lastName="liu" or fistName="amy"
theStudents = session.createQuery("from Student s where s.lastName='liu' OR s.firstName='amy' ").getResultList();
//display students
for(Student s:theStudents) {
System.out.println(s);
}

//query students where email like %qq.com"
theStudents = session.createQuery("from Student s where s.email LIKE '%qq.com' ").getResultList();
//display students
for(Student s:theStudents) {
System.out.println(s);
}
//Commit transaction
session.getTransaction().commit();
} catch (Exception e) {
factory.close();
}
}
}

Update Objects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.luv2code.hibernate.demo;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import com.luv2code.hibernate.demo.entity.Student;


public class UpdateStudentDemo {

public static void main(String[] args) {
//create session factory
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Student.class)
.buildSessionFactory();

//create a session
Session session = factory.getCurrentSession();

//use the session object to save Java Object
try {
session.beginTransaction();
int studentId = 1;
Student theStudent = session.get(Student.class, studentId);
//update the firstName where id==1
theStudent.setFirstName("Cindy");
session.getTransaction().commit();

//update all student email with liushiyu@gmail
session = factory.getCurrentSession();
session.beginTransaction();
session.createQuery("UPDATE Student SET email='liushiyu@gmail' ").executeUpdate();
session.getTransaction().commit();

} catch (Exception e) {
factory.close();
}

}

}

Delete Objects

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.luv2code.hibernate.demo;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;

import com.luv2code.hibernate.demo.entity.Student;


public class DeleteStudentDemo {

public static void main(String[] args) {
//create session factory
SessionFactory factory = new Configuration()
.configure("hibernate.cfg.xml")
.addAnnotatedClass(Student.class)
.buildSessionFactory();

//create a session
Session session = factory.getCurrentSession();

//use the session object to save Java Object
try {
session.beginTransaction();
int studentId = 1;
Student theStudent = session.get(Student.class, studentId);
//delete the student where id==1
session.delete(theStudent);
session.getTransaction().commit();

//delete student where id==2
session = factory.getCurrentSession();
session.beginTransaction();
session.createQuery("DELETE from Student WHERE id=2 ").executeUpdate();
session.getTransaction().commit();

} catch (Exception e) {
factory.close();
}
}
}

SpringMVC + Hibernate

Configuration

  1. Define database datasource/connection pool
  2. Setup Hibernate session factory
  3. Setup Hibernate transaction manager
  4. Enable configuration of transactional annotations
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    <?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:tx="http://www.springframework.org/schema/tx"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    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/mvc
    http://www.springframework.org/schema/mvc/spring-mvc.xsd
    http://www.springframework.org/schema/tx
    http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!-- Add support for component scanning -->
    <context:component-scan base-package="com.luv2code.springdemo" />

    <!-- Add support for conversion, formatting and validation support -->
    <mvc:annotation-driven/>

    <!-- Define Spring MVC view resolver -->
    <bean
    class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/view/" />
    <property name="suffix" value=".jsp" />
    </bean>

    <!-- Step 1: Define Database DataSource / connection pool -->
    <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
    destroy-method="close">
    <property name="driverClass" value="com.mysql.cj.jdbc.Driver" />
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/web_customer_tracker?useSSL=false&amp;serverTimezone=UTC" />
    <property name="user" value="springstudent" />
    <property name="password" value="springstudent" />

    <!-- these are connection pool properties for C3P0 -->
    <property name="minPoolSize" value="5" />
    <property name="maxPoolSize" value="20" />
    <property name="maxIdleTime" value="30000" />
    </bean>

    <!-- Step 2: Setup Hibernate session factory -->
    <bean id="sessionFactory"
    class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="myDataSource" />
    <property name="packagesToScan" value="com.luv2code.springdemo.entity" />
    <property name="hibernateProperties">
    <props>
    <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
    <prop key="hibernate.show_sql">true</prop>
    </props>
    </property>
    </bean>

    <!-- Step 3: Setup Hibernate transaction manager -->
    <bean id="myTransactionManager"
    class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <!-- Step 4: Enable configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="myTransactionManager" />

    </beans>

DAO

Data Access Object:

  • Responsible for interfacing with the database
  • This is a common design pattern: DAO

由于我们已经xml中设置了<property name="packagesToScan" value="com.luv2code.springdemo.entity" />,所以我们不需要像在普通的Hibernate项目中那样手动说明Entity Class(Java中与数据库一一对应的类叫做Entity Class): SessionFactory factory = new Configuration().configure("hibernate.cfg.xml").addAnnotatedClass(Student.class).buildSessionFactory();。Spring会自动去扫描@Entity。


DAO层需要SessionFactory,SessionFactory需要DataSource,这俩都是dependency,我们可以使用依赖注入。DataSource已经被自动注入好了,我们xml配置中有<property name="dataSource" ref="myDataSource" />,这是setter injection,所以如果我们去看org.springframework.orm.hibernate5.LocalSessionFactoryBean的源码的话就会发现它实现了setter injection。https://github.com/spring-projects/spring-framework/blob/master/spring-orm/src/main/java/org/springframework/orm/hibernate5/LocalSessionFactoryBean.java

接下来需要做的就是:
1.Define DAO interface
CustomerDAO.java

1
2
3
4
5
6
7
8
9
package com.luv2code.springdemo.dao;

import java.util.List;

import com.luv2code.springdemo.entity.Customer;

public interface CustomerDAO {
public List<Customer> getCustomers();
}
  1. Define DAO implementation(inject the session factory)
    Spring @Transactional: Automatically begin and end a trasaction for your Hibernate code, no need for you to explicitly do this in your code.(The Spring magic happens behind the scenes)
    Spring @Repository: 我们之前已经见过@Controller,这里我们的DAO implementation需要使用@Repository。It will automatically register DAO implementation(Component auto-scanning) and also provide translation of any JDBC related exceptions.

CustomerDAOImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.luv2code.springdemo.dao;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.luv2code.springdemo.entity.Customer;

@Repository
public class CustomerDAOImpl implements CustomerDAO {

//need to inject the session factory
@Autowired
private SessionFactory sessionFactory;

@Override
@Transactional
public List<Customer> getCustomers() {
//get current hibernate session
Session currentSession = sessionFactory.getCurrentSession();

//create a query
Query<Customer> theQuery = currentSession.createQuery("from Customer", Customer.class);
//execute query and get result list
List<Customer> customers = theQuery.getResultList();
//return the result
return customers;
}

}
  1. Inject DAO into Controller

CustomerController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.luv2code.springdemo.controller;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;

import com.luv2code.springdemo.dao.CustomerDAO;
import com.luv2code.springdemo.entity.Customer;

@Controller
@RequestMapping("/customer")
public class CustomerController {

//need to inject the customer DAO
@Autowired
private CustomerDAO customerDAO;

@GetMapping("/list")
public String listCustomers(Model model) {
//get customers form dao
List<Customer> customers = customerDAO.getCustomers();
//add the customers to the model
model.addAttribute("customers", customers);
return "list-customers";
}

}

Adding CSS

开发流程:

  1. Place CSS in a “resourses” directory
  2. Configure Spring to serve up “resources” directory

spring-mvc-crud-demo-servlet.xml

1
2
<!-- Add support for reading web resources : css, images, js, etc -->
<mvc:resources location="/resources/" mapping="/resources/**"></mvc:resources>

这里mapping="/resources/**"中的**的意思是recursively的去subfolder里寻找资源文件。
3. Reference CSS in your JSP

1
<link type="text/css" rel="stylesheet" href="${pageContext.request.contextPath}/resources/css/style.css" />

Refactor: Add a service layer


Purpose of Service Layer

  1. Service Facade design pattern
  2. Intermediate layer for customer business logic
  3. Integrate data from mutiple sources(DAO/repositories)

我们已经使用过@Controller和@Repository,分别用于Controller层和DAO层。Service我们使用的是@Service。这三个Annotation都继承与@Component,所以也都会被自动扫描。

开发流程:

  1. Define Service interface
  2. Define Service implementation:这里我们要把DAO注入到Service层,再把Service层注入到Controller层。同时我们把@Transational注解改到Service层去。

CustomerServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.luv2code.springdemo.service;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.luv2code.springdemo.dao.CustomerDAO;
import com.luv2code.springdemo.entity.Customer;

@Service
public class CustomerServiceImpl implements CustomerService {
@Autowired
private CustomerDAO customerDAO;

@Override
@Transactional
//define transaction in service layer
public List<Customer> getCustomers() {
return customerDAO.getCustomers();
}

}

CustomerDAOImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.luv2code.springdemo.dao;

import java.util.List;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import com.luv2code.springdemo.entity.Customer;

@Repository
public class CustomerDAOImpl implements CustomerDAO {

//need to inject the session factory
@Autowired
private SessionFactory sessionFactory;

@Override
public List<Customer> getCustomers() {
//get current hibernate session
Session currentSession = sessionFactory.getCurrentSession();

//create a query
Query<Customer> theQuery = currentSession.createQuery("from Customer", Customer.class);
//execute query and get result list
List<Customer> customers = theQuery.getResultList();
//return the result
return customers;
}
}

所以总结一下,Controller层负责接收浏览器的GET/POST请求,DAO层负责与数据库交互获取数据,Service层负责处理从DAO层拿到的数据,处理结束后传回Controller层。