ch02-依赖注入

服务端开发-依赖注入(Dependency Injection)

课程录屏

23 秋 服务端开发-02_哔哩哔哩_bilibili

Spring的两个核心技术

DI(Dependency Injection)

  • 保留抽象接口,让组件(Component)依赖于抽象接口,当组件要与其他实际的对象发生依赖关 系时,由抽象接口来注入依赖的实际对象

AOP(Aspect Oriented Programming)

  • 通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术
  • 利用AOP可以对业务逻辑的各个部分进行隔离, 从而使得业务逻辑各部分之间的耦合度降低,提 高程序的可重用性,同时提高了开发效率

Spring的核心是提供了一个容器(container)

image-20230915115523105

应用上下文

  • AnnotationConfigApplicationContext:基于注解的
  • AnnotationConfigWebApplicationContext
  • ClassPathXmlApplicationContext:基于xml配置文件生成上下文
  • FileSystemXmlApplicationContext
  • XmlWebApplicationContext

image-20230915215007006

classPathXmlApplicationContext获取上下文

getBean获得一个bean

bean的生命周期

image-20230915115605334

例子代码类图

image-20230915215235723

  • 虚线箭头:实现,具体的类实现接口
  • 实线:依赖,player有接口的引用

自动化配置

  • 组件扫描(component scanning)
  • 自动装配(autowiring)

组件扫描

  • @Configuration
  • @ComponentScan
    • 等价
    • 基础包(basePackages={“…”,”…”})
    • 类型不安全(not type-safe)
    • basePackageClasses={.class,.class}
    • Marker interface

自动装配

  • @Autowired
    • 用在构造器
    • 用在属性Setter方法
    • 用在(私有)属性
    • required=fals

JavaConfig

  • 自动化配置有时会行不通,如:第三方库
  • @Configuration
  • @Bean(name=“…”)
  • 注入
    • 调用方法()
    • 通过方法参数自动装配(其它配置类、其它方式创建的Bean)
    • 注意与业务逻辑和领域代码分开

代码演示

自动化配置

MediaPlayer.java 接口

1
2
3
4
5
6
7
package soundsystem;

public interface MediaPlayer {

void play();

}

SgtPeppers.java 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package soundsystem;
import org.springframework.stereotype.Component;

@Component
public class SgtPeppers implements CompactDisc {

private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";

public void play() {
System.out.println("Playing " + title + " by " + artist);
}

}

CDPlayer.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
package soundsystem;

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

@Component("soundsystem") //实例化cdplayer这个类的一个对象,soundsystem即为bean的id,bean的名字默认是当前类的名字
public class CDPlayer implements MediaPlayer {
@Autowired //第三种构建方式
private CompactDisc cd; //对接口的引用

@Autowired //自动注入:依赖关系的建立有赖于把一个对象注入另一个对象
//告诉spring,在构造时如果需要这样一个参数,就要求spring从它的上下文容器中找实现了这样一个接口的对象
//如果找到唯一一个,就把这个的引用作为构造方法的参数传入,建立cdplayer对cd的依赖;如果找到多个对象,会报错:不唯一。
public CDPlayer(CompactDisc cd) {
//通过构造方法建立cdplayer与cd的引用
this.cd = cd;
}

@Autowired //如果不存在带参数的构造方法,现在在set打这个标签,就相当于一次自动调用
//@Qualifier("cd") //如果有多个实例,可以通过这个注释指定注入id的bean

public void setCd(CompactDisc cd) {
this.cd = cd;
}

public void play() {
cd.play();
}

}

Object=Bean=Component

CDPlayerConfig.java

指定配置类:构建上下文的入口类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package soundsystem;

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

@Configuration
@ComponentScan(basePackages = {"soundsystem", "abc"})
//指示spring从当前类所属于的包和子包下找Component注解的类,并将其实例化
//basepackages即包路径,请到这个soundsystem及其子包中找component注解的类并将其实例化,类型不安全
@ComponentScan(basePackageClasses = {CDPlayer.class, abc.class})
//到CDPlayer.class类所在的包和子包下搜索,也是个数组(多个相关类),类型安全
//但是优化代码时可能会出现cdplayer被删除,这时候优化方法为在cdplayer的包下新建一个接口类MyTag,将classes改为MyTag
//MyTag即为标记类,作用即为提供搜索路径
public class CDPlayerConfig {

}

SgtPeppers.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package soundsystem;
import org.springframework.stereotype.Component;

@Component //请spring实例化这样的一个类 为具体的对象
@Primary //优先使用这个类的实例来注入
public class SgtPeppers implements CompactDisc {

private String title = "Sgt. Pepper's Lonely Hearts Club Band";
private String artist = "The Beatles";

public void play() {
System.out.println("Playing " + title + " by " + artist);
}

}

java程序的入口

MyAnnotationApp.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package app;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import soundsystem.CDPlayerConfig;
import soundsystem.MediaPlayer;

public class MyAnnotationApp {
public static void main(String[] args) {
//上下文:基于注解的配置类的应用上下文的建立
ApplicationContext ctx = new AnnotationConfigApplicationContext(CDPlayerConfig.class);
//向上下文询问并获取bean:即实现MediaPlayer接口的bean对象,cdplayer的实例
MediaPlayer player = ctx.getBean(MediaPlayer.class);
player.play();
}
}

测试

cdplayertest.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 soundsystem;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = CDPlayerConfig.class)
public class CDPlayerTest {

@Rule
//jar包,log对象抓取出控制台的输出
public final StandardOutputStreamLog log = new StandardOutputStreamLog();

// public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();

@Autowired
private MediaPlayer player;

@Autowired
private CompactDisc cd;

@Test
public void cdShouldNotBeNull() {
//断言一定有这样一个对象
assertNotNull(cd);
}

@Test
public void play() {
player.play();
assertEquals(
"Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles" + System.getProperty("line.separator"),
log.getLog());
}

}

componentscan的替代方法:javaConfig

实例化第三方库,无法拿到原代码

CDPlayerConfig.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
package soundsystem;

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

@Configuration
public class CDPlayerConfig {

@Bean
public CompactDisc compactDisc() {
return new SgtPeppers();
}

@Bean(name = "CDPlayer")
//spring从上下文中找参数所需要的实例,并将其注入
//可以通过name指定个性化的id,默认方法名
public CDPlayer cdPlayer(CompactDisc cd) {
return new CDPlayer(cd);
}

@Bean //spring从上下文中找参数所需要的实例,并将其注入
public CDPlayer cdPlayer2(CompactDisc cd) {
return new CDPlayer(cd);
}

// @Bean
// public CDPlayer cdPlayer() {
// return new CDPlayer(compactDisc());
// }
// @Bean
// public CDPlayer cdPlayer2() {
// return new CDPlayer(compactDisc());
// }
//第一个bean创建在上下文中之后,随后的对compactDisc()的调用都会产生一个拦截,如果上下文中有这个对象就会取这个对象
//无论compactDisc()调用多少次,都只会存在一个CompactDisc接口的对象
}

cdplayer.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 soundsystem;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CDPlayer implements MediaPlayer, BeanNameAware, ApplicationContextAware {
private CompactDisc cd;

public CDPlayer(CompactDisc cd) {
this.cd = cd;
}

public void play() {
cd.play();
}

@Override
public void setBeanName(String name) {
//bean实例化时能获得上下文传递来的信息,BeanNameAware实例化时会override
System.out.println("====" + name);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
//获得上下文的引用
System.out.println(applicationContext.getApplicationName());
}
}

xml配置

ConstructorArgReferenceTest-context.xml

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="compactDisc" class="soundsystem.SgtPeppers" />

<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
</beans>

测试

ConstructorArgReferenceTest.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
package soundsystem;

import static org.junit.Assert.*;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="ConstructorArgReferenceTest-context.xml")
public class ConstructorArgReferenceTest {

@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog();

@Autowired
private MediaPlayer player;

@Test
public void play() {
player.play();
assertEquals(
"Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n",
log.getLog());
}
}

可以通过xml传递参数(了解即可)

ConstructorArgValueTest-context.xml

value指定字面量

ref指定引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="compactDisc"
class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>

<bean id="cdPlayer"
class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
</beans>

ConstructorArgValueTest.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
package soundsystem;

import static org.junit.Assert.*;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ConstructorArgValueTest {

@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog();

@Autowired
private MediaPlayer player;

@Test
public void play() {
player.play();
assertEquals(
"Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n",
log.getLog());
}
}

如果参数是列表:list子元素

ConstructorArgCollectionTest-context.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
30
31
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="compactDisc" class="soundsystem.collections.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
<constructor-arg>
<list>
<value>Sgt. Pepper's Lonely Hearts Club Band</value>
<value>With a Little Help from My Friends</value>
<value>Lucy in the Sky with Diamonds</value>
<value>Getting Better</value>
<value>Fixing a Hole</value>
<value>She's Leaving Home</value>
<value>Being for the Benefit of Mr. Kite!</value>
<value>Within You Without You</value>
<value>When I'm Sixty-Four</value>
<value>Lovely Rita</value>
<value>Good Morning Good Morning</value>
<value>Sgt. Pepper's Lonely Hearts Club Band (Reprise)</value>
<value>A Day in the Life</value>
</list>
</constructor-arg>
</bean>

<bean id="cdPlayer" class="soundsystem.CDPlayer">
<constructor-arg ref="compactDisc" />
</bean>
</beans>

test

ConstructorArgCollectionTest.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
package soundsystem;

import static org.junit.Assert.*;

import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.StandardOutputStreamLog;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class ConstructorArgCollectionTest {

@Rule
public final StandardOutputStreamLog log = new StandardOutputStreamLog();

@Autowired
private MediaPlayer player;

@Test
public void play() {
player.play();
assertEquals(
"Playing Sgt. Pepper's Lonely Hearts Club Band by The Beatles\r\n" +
"-Track: Sgt. Pepper's Lonely Hearts Club Band\r\n" +
"-Track: With a Little Help from My Friends\r\n" +
"-Track: Lucy in the Sky with Diamonds\r\n" +
"-Track: Getting Better\r\n" +
"-Track: Fixing a Hole\r\n" +
"-Track: She's Leaving Home\r\n" +
"-Track: Being for the Benefit of Mr. Kite!\r\n" +
"-Track: Within You Without You\r\n" +
"-Track: When I'm Sixty-Four\r\n" +
"-Track: Lovely Rita\r\n" +
"-Track: Good Morning Good Morning\r\n" +
"-Track: Sgt. Pepper's Lonely Hearts Club Band (Reprise)\r\n" +
"-Track: A Day in the Life\r\n",
log.getLog());
}
}

命名空间

CNamespaceValueTest-context.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="compactDisc" class="soundsystem.BlankDisc"
c:_0="Sgt. Pepper's Lonely Hearts Club Band"
c:_1="The Beatles" />

<bean id="cdPlayer" class="soundsystem.CDPlayer"
c:_-ref="compactDisc" />

</beans>

p命名空间

p属性

PropertyRefTest-context.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

<bean id="compactDisc" class="soundsystem.BlankDisc">
<constructor-arg value="Sgt. Pepper's Lonely Hearts Club Band" />
<constructor-arg value="The Beatles" />
</bean>

<bean id="cdPlayer" class="soundsystem.properties.CDPlayer"
p:compactDisc-ref="compactDisc" />
</beans>

Spring Web开发框架的分层

image-20230916165335412

image-20230916170628871

Faker

1
2
3
4
5
6
7
8
9
10
11
private Contact generateContact() {
id++;
Faker faker = new Faker(Locale.ENGLISH);
Contact contact = new Contact();
contact.setId(id);
contact.setFirstName(faker.hame().firstName());
contact.setLastName(faker.name().lastName());
contact.setPhoneNumber(faker.phoneNumber().cellPhone());
contact.setEmailAddress(faker,internet(),emailAddress()):
return contact:
}

模拟工具

DiUS/java-faker: Brings the popular ruby faker gem to Java (github.com)

profile

配置类

profile在不同的环境下选择不同的bean创建

@Profile

​ @Configuration

​ @Profile(“dev”) 类

​ @Bean

​ @Profile(“prov”)

​ 方法

​ 激活

缺省属性,如果不指定,默认第一个;如果指定,则为第二个

​ spring.profiles.default

​ spring.profiles.active

​ @ActiveProfiles(“dev”

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.myapp;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
public class DataSourceConfig {

@Bean(destroyMethod = "shutdown")
@Profile("dev")
public DataSource embeddedDataSource() {
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql")
.build();
}

@Bean
@Profile("prod")
public DataSource jndiDataSource() {
JndiObjectFactoryBean jndiObjectFactoryBean = new JndiObjectFactoryBean();
jndiObjectFactoryBean.setJndiName("jdbc/myDS");
jndiObjectFactoryBean.setResourceRef(true);
jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
return (DataSource) jndiObjectFactoryBean.getObject();
}

}

根配置

@Configeration

@ComponetScan

@Import(其它配置类…)

@ImportResource(其它xml文件)

Class BootConfig(){ }

@Conditional

@Bean 或@Component

@Conditional(**.class)

接口

Condition{ boolean matches(…) }

自动装配的歧义性

@Component 或 @Bean

@Primary

定义时

@Componet或@Bean

@Qualifier(“…”)自定义限定符

使用时

@Autowired

@Qualifier(“…”) bean名称或自定义限定符,默认Bean名是限定符

也可以自定义注解,这些注解本身也加了@Qualifier注解

@Cold

@Creamy

Bean的作用域

@Scope可以与@Component和@Bean一起使用,指定作用域

◼ Singleton,单例,在整个应用中,只创建bean的一个实例

◼ Prototype,原型,每次注入或者通过Spring应用上下文获取的时候,都会创建一个新bean实例

◼ Session,会话,在Web应用中,为每个会话创建一个bean实例

◼ Request,请求,在Web应用中,为每个请求创建一个bean实例

使用会话和请求作用域

@Component

@Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)

public ShoppingCart cart(){…}

通过代理注入给单例对象

image-20230921190907266

运行时注入外部值

  • 使用Environment检索属性
  • 使用@PropertySource指定属性文件

Environment.getProperty的几种形式

  • String getProperty(String key)
  • String getProperty(String key, String defaultValue)
  • T getProperty(String key, Class type)
  • T getProperty(String key, Class type, T defaultValue )