java Spring

核心概念

Spring 是一个非常活跃的开源框架,开源帮助分离项目组件之间的依赖关系,它的主要目的是简化企业开发

核心概念:

  • IOC(Inversion Of Control)控制反转;创建对象,对象之间依赖关系的维护全部再 Spring 容器中完成的,不需要我们程序猿在代码中具体实现。即对象创建和对象之间关系维护的控制权由程序猿管理变为由 Srping 管理
  • DI(Dependency Injection)依赖注入;处理对象和对象之间依赖关系的创建
  • AOP(Aspect Oriented Programming)面向切面编程

入门

使用注解

类上加入注解@Component,执行类上加入注解@ComponentScan,会扫描自动执行普通类的构造方法

原版:

1
2
3
4
5
6
7
// 创建打印机对象
MessagePrint printer = new MessagePrint();
// 创建消息服务对象
MessageService service = new MessageService();
// 设置打印机对象的 service 对象
printer.setService(service);
printer.printMessage();

使用 spring 版:

1
2
3
4
5
6
7
8
9
// 初始化 spring 容器
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationSpring.class);
// 从容器中获取对象
MessagePrint printer = context.getBean(MessagePrint.class);
MessageService service = context.getBean(MessageService.class);
// 设置属性
printer.setService(service);
// 打印消息
printer.printMessage();

在 setter 方法上加入注解@Autowired会自动调用(自动装配)printer.setService(service),从而上面的代码可以省略为:

1
2
3
4
5
6
// 初始化 spring 容器
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationSpring.class);
// 从容器中获取对象
MessagePrint printer = context.getBean(MessagePrint.class);
// 打印消息
printer.printMessage();

注意,使用 spring,需要=在 pox.xml 文件中加入 spring 依赖:

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>
</dependencies>

使用 XML 配置

xml 配置内容:

1
2
3
4
5
6
7
8
9
<!--
bean元素:描述当前的对象需要spring容器管理
id属性:标识对象,未来在应用程序中开源根据id获取对象
class:被管理对象的类全名
-->
<bean id="service" class="MessageService"></bean>
<bean id="printer" class="MessagePrint">
<property name="service" ref="service"></property>
</bean>

应用程序执行代码:

1
2
3
4
5
// 初始化 spring 容器,装配XML
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 从容器中获取对象
MessagePrint printer = context.getBean(MessagePrint.class);
printer.printMessage();

添加日志系统

在 pox.xml 中加入 log4j 依赖:

1
2
3
4
5
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

在 resources 资源文件夹下创建新文件:log4j.properties,加入测试日志配置:

1
2
3
4
5
6
7
log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n

log4j.category.org.springframework.beans.factory=DEBUG

然后再次运行程序就可以看倒控制台上打印的日志

一个详细的介绍:Log4j.properties配置详解

自动装配

使用配置类

在前面我们是在 main 方法中利用@ComponentScan进行自动组件扫描,然而在实际开发中,我们的应用程序可能不是通过 main 方法来启用,有可能通过微信端,安卓端,浏览器等,所以不是通过 main 方法启用,因此我们可以把组件和 main 方法解耦,单独创建一个扫描配置类。

单独创建一个 AppConfig 类

1
2
3
@Configuration // 备注为配置类
@ComponentScan
public class AppConfig {}

去除 main 方法中的ComponentScan,然后 main 方法中的 ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationSpring.class); 修改为:ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

到此为止使用了四个注解:

  • 组件扫描:
    • @Component:表示这个类需要在应用程序中被创建
    • @ComponentScan:自动发现应用程序中创建的类
  • 自动装配
    • @Autowired:自动满足 bean 之间的依赖
  • 定义配置类
    • @Configuration:表示当前类是一个配置类

使用 junit4 单元测试

引入 Spring 单元测试模块,即在 pox.xml 中加入 junit 依赖:

1
2
3
4
5
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>

在 test\java 文件夹下创建一个 AppTest 类,将 main\java 下的 main 方法主类内容复制过来,然后主类文件可以选择删除。

然后在 pox.xml 中加入 spring-test 依赖:

1
2
3
4
5
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.13.RELEASE</version>
</dependency>

在测试类上添加注解@RunWith(SpringJUnit4ClassRunner.class),可以帮我们自动初始化 spring 的上下文环境。

继续添加注解@ContextConfiguration(classes=Appconfig.class),该注解可以帮们读取类对象的配置文件,即加载配置类,所以可以删除ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class); 这句代码了

删除CDPlayer player = context.getBean(CDPlayer.class),采用自动注入对象方式:

1
2
@Autowired
private CDPlayer player;

最终代码简化为:

1
2
3
4
5
6
7
8
9
10
11
12
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes=Appconfig.class)
public class AppTest {

@Autowired
private CDPlayer player;

@Test
public void testPlay () {
player.play();
}
}

原版:

1
2
3
4
5
6
7
8
9
public class AppTest {

@Test
public void testPlay () {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
CDPlayer player = context.getBean(CDPlayer.class)
player.play();
}
}

@Autowired使用场景

  1. 用在构造函数上(效率最高的一种方式)
  2. 用在成员变量上(最便捷的一种方式)
  3. 用在 setter 方法上
  4. 用在任意方法上

可以在 @Autowired 上添加上 required 属性,来设置是否必须要装配:@Autowired(required=false),这样将不会检测对象类是否被装配,但是调用对象属性和方法时候,需要判断是否存在。

使用接口装配

使用接口装配时,注意亮点:

  1. @Component需要写在实现类上而不是接口上
  2. 调用接口的时候通常会声明成员变量

z歧义性:当有两个实现类时,spring 不知道应该自动装配哪个实现类

解决歧义性方案:

  1. 直接在声明成员变量的时候,使用具体实现类:private UserServiceFestival userService而不使用接口类 UserService 声明:private UserService userServic
  2. 在实现类上利用@Primary注解指定首先 bean,但不能设置多个首选 bean,存在局限性
  3. 使用限定符@Qualifier("xxx限定符"),不明确指定用哪个,而在调用的时候匹配,使用同样是在成员变量上加入注解@Qualifier("xxx限定符")
  4. 使用类id,@Component("xxxid"),在测试用例上使用时,还是用限定符匹配@Qualifier("xxxid")
  5. 使用@Resource(name="类的默认ID,即类名"),利用@Resource 的同时可以省略@Autowired,也不用在实现类上单独添加类id(当然也可以用类id而不用默认id) 或限定符。需要注意的是@Resource不是 spring 的标准,而是 jdk 自带的标准,使用时会引入javax.annotation.Resource

处理分层架构

这里写图片描述

在实际开发中,我们一般使用分层架构来开发项目,这时候,配置在特定包下面的配置类文件只能处理当前类所在包和子包的自动装配,而不能装配兄弟(同级)包的装配,这时候需要怎么处理呢?

  1. 最简单的方式就是把配置类提到更外层的包里面去。
  2. 项目经常会把配置类单独放到一个特定的包里面,这时候就可以利用扫描指定包名的方式:@ComponentScan("包名"),或者@ComponentScan(basePackages = {"包名1","包名2","包名xxx"})指定多个包的注解方式;这种方式存在的缺陷是:因为是用字符串指定包名,代码重构的时候包名不会自动改变,需要手动操作。
  3. 设置类对象的方式;@ComponentScan(basePackageClasses={UserController.class,xxx类名的class对象})

为了在分层架构中更能表现出项目的结构层次,可以利用@Controller@Service@Repository等注解分别替代 Controller 层,Service 层和 Dao 层的@Component注解,本质没有区别,可以让语义更明确。

通过 XML 启用组件扫描

上面都是通过配置配置文件的方式来启用各个包之间组件的扫描,既然 XML 可以配置扫描 bean ,当然也可以代替配置文件配置扫描所有组件了,方法就是在 resources 资源文件夹下创建应用的 XML 文件,然后加入配置,例如:

1
<context:component-scan base-package="包名xxx" />

然后在 main 主类或者测试类上将注解@ContextConfiguration(classes=配置类.class)改为@ContextConfiguration("classpath:xxx.xml")

显示装配:java 装配和 XML 装配

正常情况下我们都推荐使用自动装配,但是如果想将第三方库中的组件装配倒应用程序中,则没有办法再类上添加@Compenent@Autowired等注解,这时候只能使用“显示装配(java装配)”和“XML装配”了。

在 javaConfig 中配置 bean 对象

显示装配步骤:

  1. 去除实现类上的@Compenent注解和配置类 AppConfig 上的@ComponentScan,留下@Configuration注解
  2. 在需要装配的方法上添加@Bean注解,在 spring 应用程序启动的时候这个方法就会被自动调用,例如:
    1
    2
    3
    4
    5
    6
    7
    @Configuration
    public class AppConfig {
    @Bean
    public UserDao userDaoNormal(){
    return new UserDaoNormal();
    }
    }

通过构造函数依赖注入

例如存在两个接口: UserDao 和 UserService 以及两个对应的实现类:

UserDaoNormal

1
2
3
4
5
6
public class UserDaoNormal implements UserDAO{
@Override
public void add() {
System.out.println("添加用户倒数据库中....")
}
}

UserServiceNormal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UserServiceNormal implements UserService{

private UserDao userDao;

// 通过构造函数依赖注入实现与 UserDAO 的关联,从而可以使用 UserDao 的实列
public UserServiceNormal(){
super();
}

public UserServiceNormal(UserDao userDao){
this.userDao = userDao
}

@Override
public void add() {
userDao.add();
}
}

对应的配置类使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configurationu
public class AppConfig {
@Bean
public UserDao userDaoNormal(){
return new UserDaoNormal();
}

@Bean
public UserDao UserServiceNormal(UserDao userDao){
// spring 会拦截调用,检查是否有重复创建,只存在一个,只调用一次(单例)
// UserDao userDao = UserDaoNormal(); // 在@bean方法中使用参数,可在参数中传递后,不需要再调用
return new UserDaoNormal(userDao)
}
}

通过 seeter 方法和任意方法 依赖注入

修改上面通过构造函数注入的 UserServiceNormal 类,添加 UserDao 类的 setter 方法:

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
public class UserServiceNormal implements UserService{

private UserDao userDao;

// 通过构造函数依赖注入实现与 UserDAO 的关联,从而可以使用 UserDao 的实列
public UserServiceNormal(){
super();
}

public UserServiceNormal(UserDao userDao){
this.userDao = userDao
}

// UserDao 类的 setter 方法
public void setUserDao(UserDao userDao) {
this.userDao = userDao
}

// UserDao 类的 一般方法
public void prepare(UserDao userDao) {
this.userDao = userDao
}

@Override
public void add() {
userDao.add();
}
}

然后修改 AppConfig 配置类的 UserServiceNormal Bean 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Configurationu
public class AppConfig {
@Bean
public UserDao userDaoNormal(){
return new UserDaoNormal();
}

@Bean
public UserDao UserServiceNormal(UserDao userDao){
UserServiceNormal userService = new UserServiceNormal();
userService.setUserDao(userDao);
// userService.prepare(userDao); // 普通方法
return userService;
}
}

处理自动装配的歧义性

java 显示装配中处理歧义性问题与自动装配一致

XML 装配比较繁琐和复杂,新项目不推荐使用

推荐尽可能的使用自动装配,显示的配置越少越好,

其次是使用 javaConfig ,最后再考虑 XML 的形式

高级装配

Spring 中的 bean 单例:

  • 无论我们是否主动去获取 bean 对象,Spring 上下文一加载就会在创建 bean 对象
  • 无论获取多少次,拿到的都是同一个对象