Java 企业级应用开发 part 3 - J2EE part 3

Bean 相关的编程

  • Spring Bean
  • 依赖注入
  • 生命周期

Dependency Injection(依赖注入)

构造方法注入

首先我们建立一个 Java Bean

User1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.itheima;

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

public class User1 {
private int id;
private String name;
private String password;

//有参构造方法
@Autowired(required = false)
public User1(int id, String name, String password) {
this.id = id;
this.name = name;
this.password = password;
}

public String toString() {
return "id=" + id + ",name=" + name + ",password=" + password;
}
}

Java Bean 实际上是一个普通的 Java 类,但其按照一定的约定去写,例如包含 setter/getter 方法,提供无参构造方法等。Java Bean 的主要作用是封装数据,通常用于在不同层之间传递数据
这里的 @Autowired(required = false) 是为了消除 IDEA 的警告提示,表示这个构造方法的参数不需要自动装配,因为我们采用 xml 手动配置

接下来我们在 applicationContext.xml 中配置这个 Bean:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="com.itheima.User1">
<constructor-arg name="id" value="1"/>
<constructor-arg name="name" value="张三"/>
<constructor-arg name="password" value="123"/>
</bean>
</beans>

在这里我们在 <beans> 标签中定义了一个 Bean,id 是 user1,class 是 com.itheima.User1。我们通过 <constructor-arg> 标签来指定构造方法的参数值。其中的 namevalue 属性分别对应构造方法的参数名称和参数值。

随后我们测试依赖注入的结果

TestUser1.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.itheima;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser1 {
public static void main(String[] args) throws Exception {
//加载applicationContext.xml配置
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext-User.xml");
//获取配置中的User1实例
User1 user1 = applicationContext.getBean("user1", User1.class);
System.out.println(user1);
}
}

首先我们通过 ApplicationContext 加载 applicationContext-User.xml 配置文件,然后通过 getBean 方法获取配置中的 User1 实例,并打印出来(调用 toString 方法)

setter 方法注入

我们来创建另一个 Bean

User2.java
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.itheima;

public class User2 {
private int id;
private String name;
private String password;

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

public void setName(String name) {
this.name = name;
}

public void setPassword(String password) {
this.password = password;
}

public String toString() {
return "id=" + id + ",name=" + name + ",password=" + password;
}
}

getter/setter 可由 IDEA 自动生成,只需要编写成员变量

接下里我们在 applicationContext-User2.xml 中配置这个 Bean:

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user2" class="com.itheima.User2">
<property name="id" value="2"/>
<property name="name" value="李四"/>
<property name="password" value="456"/>
</bean>
</beans>

在这里我们通过 <property> 标签来指定 setter 方法的参数值。其中的 name 属性对应 setter 方法的名称(去掉 set 前缀,并将首字母小写),value 属性对应参数值

同样的,我们测试依赖注入的结果:

TestUser2.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.itheima;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser2 {
public static void main(String[] args) throws Exception {
//加载applicationContext.xml配置
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext-User2.xml");
//获取配置中的User2实例
User2 user2 = applicationContext.getBean("user2", User2.class);
System.out.println(user2);
}
}

ApplicationContext Interface

ApplicationContext 建立在 BeanFactory 的基础上,下面是几种常见 Implementation

  • ClassPathXmlApplicationContext:从类路径下加载配置文件
  • FileSystemXmlApplicationContext:从文件系统加载配置文件
  • AnnotationConfigApplicationContext:从 Java 注解配置类加载配置

Bean 的配置与实例化方法

常用 XML 配置 Bean,具体来说通过 <beans><bean> 标签来配置 Bean。<beans> 标签是 Bean 定义的根元素,<bean> 标签用于定义一个 Bean

<bean> 标签的常用属性:

  • id:Bean 的唯一标识符
  • name:Bean 的别名,可以有多个别名,使用逗号分隔
  • class:Bean 的全限定类名
  • scope:Bean 的作用域,常用的有 singleton(单例)和 prototype

<bean> 标签的常用子标签:

  • <constructor-arg>:用于指定构造方法的参数值
  • <property>:用于指定 setter 方法的参数值

直接由 Bean 类实例化

参考前文中的实现,不做单独介绍

由静态 Bean 工厂方法实例化

Bean2.java
1
2
3
4
5
6
7
package com.itheima;

public class Bean2 {
public Bean2() {
System.out.println("这是Bean2");
}
}
MyBean2Factory.java
1
2
3
4
5
6
7
8
package com.itheima;

public class MyBean2Factory {
//使用MyBean2Factory类的工厂创建Bean2实例
public static Bean2 createBean() {
return new Bean2();
}
}
applicationContextBean2.xml
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<bean id="bean2"
class="com.itheima.MyBean2Factory"
factory-method="createBean"/>
</beans>

在这里,通过制定 bean2 的 class 属性为 MyBean2Factory,并指定 factory-method 属性为 createBean,Spring 就会调用 MyBean2Factory 类的 createBean 方法来创建 Bean2 实例

随后通过 ApplicationContext 获取 Bean2 实例:

Bean2Test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.itheima;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Bean2Test {
public static void main(String[] args) {
// ApplicationContext在加载配置文件时,对Bean进行实例化
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationBean2.xml");
System.out.println(applicationContext.getBean("bean2"));
}
}

由实例 Bean 工厂方法实例化

Bean3.java
1
2
3
4
5
6
7
package com.itheima;

public class Bean3 {
public Bean3() {
System.out.println("这是Bean3");
}
}
MyBean3Factory.java
1
2
3
4
5
6
7
8
9
10
11
12
package com.itheima;

public class MyBean3Factory {
public MyBean3Factory() {
System.out.println("bean3工厂实例化中");
}

//创建Bean3实例的方法
public Bean3 createBean() {
return new Bean3();
}
}
applicationContextBean3.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
<!-- 配置工厂 -->
<bean id="myBean3Factory"
class="com.itheima.MyBean3Factory"/>
<!-- 使用factory-bean属性指向配置的实例工厂,
使用factory-method属性确定使用工厂中的哪个方法-->
<bean id="bean3" factory-bean="myBean3Factory"
factory-method="createBean"/>
</beans>

这里 myBean3Factory 本身即是一个 Bean,在 bean3 的配置中通过 factory-bean 属性指定其工厂 Bean 为 myBean3Factory,并通过 factory-method 属性指定使用 myBean3Factory 中的 createBean 方法来创建 Bean3 实例

Bean3Test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.itheima;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Bean3Test {
public static void main(String[] args) {
// ApplicationContext在加载配置文件时,对Bean进行实例化
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationBean3.xml");
System.out.println(applicationContext.getBean("bean3"));
}
}

这里从应用上下文获取 bean3 实例时,Spring 会先实例化 myBean3Factory,然后调用其 createBean 方法来创建 bean3 实例

Bean 的作用域

通过 <bean> 标签的 scope 属性来指定 Bean 的作用域,常用的有:

  • singleton(单例):默认值,表示在 Spring 容器中只创建一个 Bean 实例,所有对该 Bean 的请求都会返回同一个实例
  • prototype(原型):表示每次请求该 Bean 时都会创建一个新的实例。
  • request:表示在一个 HTTP 请求内共享一个 Bean 实例,不同请求之间的 Bean 实例不同(仅适用于 Web 应用)
  • session:表示在一个 HTTP 会话内共享一个 Bean 实例,不同会话之间的 Bean 实例不同(仅适用于 Web 应用)

Singleton(单例)作用域

applicationBean1.xml
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--<bean id="bean1" class="com.itheima.Bean1"></bean>-->
<bean id="bean1" class="com.itheima.Bean1" scope="singleton"/>
<!-- <bean id="bean1" class="com.itheima.Bean1" scope="prototype"></bean>-->
</beans>

由于其默认就是 singleton,所以其实可以省略 scope 属性。这里在 IDEA 中将会给出多余配置的 warning

Bean1.java
1
2
3
4
5
6
7
package com.itheima;

public class Bean1 {
public Bean1() {
System.out.println("这是Bean1");
}
}
ScopeTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.itheima;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class ScopeTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationBean1.xml");
Bean1 bean1_1 = (Bean1) applicationContext.getBean("bean1");
Bean1 bean1_2 = (Bean1) applicationContext.getBean("bean1");
System.out.print(bean1_1 == bean1_2);
}
}

运行结果将会显示 true,而若将 scope 属性改为 prototype,则运行结果将会显示 false

Bean 的装配

基于 setter/getter 方法的装配方式需要由默认构造函数,而基于构造方法的装配方式则不需要默认构造函数,具体参考前文中 依赖注入部分

基于注解(annotation)的装配

Spring 常用注解:

  • @Component:用于标注一个类为 Spring 管理的组件,表示该类是一个普通的 Bean
  • @Controller:用于标注一个类为 Spring 的控制器组件,表示该类是一个控制层的 Bean
  • @Service:用于标注一个类为 Spring 的服务组件,表示该类是一个业务逻辑层的 Bean
  • @Repository:用于标注一个类为 Spring 的数据访问组件,表示该类是一个 DAO 层的 Bean
  • @Scope:用于指定 Bean 的作用域
  • @Value:用于注入简单类型的属性值

装配相关注解:

  • @Autowired:用于自动装配 Bean,默认按照类型进行装配
  • @Resource:用于自动装配 Bean,默认按照名称进行装配
  • @Qualifier:用于指定装配 Bean 的名称,配合 `@Autowired
  • @PostConstruct:用于标注一个方法,在 Bean 初始化完成后执行
  • @PreDestroy:用于标注一个方法,在 Bean 销毁前执行

注解注入属于 AOP,需要导入 spring-aop 依赖,并在配置文件中开启注解扫描和自动装配功能:

1
2
3
4
5
6
7
8
9
10
11
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- 由于新版本的 Java annotation 被独立出去了,所以现在也要导入 -->
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
applicationContext.xml
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
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 命名空间 ,在配置文件中开启相应的注解处理器 -->
<context:component-scan base-package="com.itheima"/>
</beans>

定义一个 UserEntity (位于 Entity 包内)

User.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
package com.itheima.entity;

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

@Component("user")
@Scope("singleton")
public class User {
@Value("1")
private int id;
@Value("张三")
private String name;
@Value("123")
private String password;

public int getId() {
return id;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getPassword() {
return password;
}

public void setPassword(String password) {
this.password = password;
}

public String toString() {
return "id=" + id + ",name=" + name + ",password=" + password;
}
}

定义 UserDao 和 UserService,分别位于 daoservice 包内

UserDao.java
1
2
3
4
5
package com.itheima.dao;

public interface UserDao {
public void save();
}
UserDaoImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.itheima.dao;

import com.itheima.entity.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Repository;

//使用@Repository注解将UserDaoImpl类标识为Spring中的Bean
@Repository("userDao")
public class UserDaoImpl implements UserDao {
public void save() {
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) applicationContext.getBean("user");
System.out.println(user);
System.out.println("执行UserDaoImpl.save()");
}
}

Service 的接口与实现:

UserService.java
1
2
3
4
5
package com.itheima.service;

public interface UserService {
public void save();
}
UserServiceImpl.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.itheima.service;

import com.itheima.dao.UserDao;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

//使用@Service注解将UserServiceImpl类标识为Sring中的Bean
@Service("userService")
public class UserServiceImpl implements UserService {
//使用@Resource注解注入UserDao
@Resource(name = "userDao")
private UserDao userDao;

public void save() {
this.userDao.save();
System.out.println("执行UserServiceImpl.save()");
}
}

这里通过 @Resource(name = "userDao") 注解将 UserDaoImpl 注入到 UserServiceImpl 中,name 属性指定要注入的 Bean 的名称,这里是 userDao

Controller 层:

UserController.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.itheima.controller;

import com.itheima.service.UserService;
import org.springframework.stereotype.Controller;

import javax.annotation.Resource;

//使用Controller注解将UserController类标识为Spring中的Bean
@Controller
public class UserController {
//使用@Resource注解注入UserService
@Resource(name = "userService")
private UserService userService;

public void save() {
this.userService.save();
System.out.println("执行UserController.save()");
}
}

编写测试类:

AnnotationTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.itheima;

import com.itheima.controller.UserController;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AnnotationTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationContext.xml");
UserController userController = (UserController)
applicationContext.getBean("userController");
userController.save();
}
}

自动装配

通过 @Autowired 注解实现自动装配,默认按照类型进行装配,如果有多个同类型的 Bean,可以配合 @Qualifier 注解指定 Bean 的名称,这里不做详细介绍

Bean 的生命周期

对于 Singleton 作用域的 Bean,Spring 可以管理创建、初始化和销毁的整个生命周期;对于 Prototype 作用域的 Bean,Spring 只负责创建和初始化,销毁由用户自己负责

这里介绍基于注解的监控时间节点方法

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
package com.itheima;

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

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

@Component("student")
public class Student {
@Value("1")
private String id;
@Value("张三")
private String name;

public String getId() {
return id;
}

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

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

//使用@PostConstruct标注Bean对象初始化节点的监控方法
@PostConstruct
public void init() {
System.out.println("Bean的初始化完成,调用init()方法");
}

//使用@PreDestroy标注Bean对象销毁前节点的监控方法
@PreDestroy
public void destroy() {
System.out.println("Bean销毁前调用destroy()方法");
}

public String toString() {
return "id=" + id + ",name=" + name;
}
}

这里的 @PostConstruct 注解标注的方法会在 Bean 初始化完成后执行,而 @PreDestroy 注解标注的方法会在 Bean 销毁前执行

applicationContextStudent.xml
1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns="http://www.springframework.org/schema/beans"
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命名空间,在配置文件中开启相应的注解处理器 -->
<context:component-scan base-package="com.itheima"/>
</beans>
StudentTest.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package com.itheima;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class StudentTest {
public static void main(String[] args) {
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("applicationStudent.xml");
Student student = (Student) applicationContext.getBean("student");
System.out.println(student);
//销毁Spring容器中的所有Bean
AbstractApplicationContext ac = (AbstractApplicationContext) applicationContext;
ac.registerShutdownHook();
}
}