依赖注入
什么是依赖注入
就是在Spring容器内容将各个对象的依赖关系建立好的操作
最终的效果是从Spring容器中获得的对象是包含了被依赖对象的
(最终的效果是从Spring容器中获得的关羽是手拿青龙偃月刀的!)
为什么需要依赖注入
如果不使用依赖注入,我们需要从Spring容器中获得相应对象,再通过编写代码建立依赖关系
这样就会有代码的冗余,多个依赖关系的确定会造成代码臃肿
怎么使用依赖注入
@Bean
public Hero guanYu(){
Hero h=new Hero();
h.setName("关羽");
h.setAge(25);
return h;
}
上面的注入关羽是没有青龙偃月刀的
测试输出结果:关羽使用null战斗
要想在这个方法中为关羽赋值青龙偃月刀需要编写的代码如下
注意!!!删除DragonBlade类上本来有的组件扫描注解,否则可能导致异常!!!
@ComponentScan("cn.tedu.hero")
public class Config {
@Bean
public DragonBlade blade(){
return new DragonBlade();
}
@Bean
//方法的参数为DragonBlade类型
//这个参数编写后,spring容器会自动从当前容器中所有内容中搜索
//只要有DragonBlade类型的对象就会自动赋值到这个参数中
public Hero guanYu(DragonBlade blade){
Hero h=new Hero();
h.setName("关羽");
h.setAge(25);
h.setDragonBlade(blade);
return h;
}
}
依赖注入注意事项
如果现在程序中有两把青龙偃月刀
@Bean
public DragonBlade blade1(){
return new DragonBlade();
}
@Bean
public DragonBlade blade2(){
return new DragonBlade();
}
@Bean
//方法的参数为DragonBlade类型
//这个参数编写后,spring容器会自动从当前容器中所有内容中搜索
//只要有DragonBlade类型的对象就会自动赋值到这个参数中
public Hero guanYu(DragonBlade blade){
Hero h=new Hero();
h.setName("关羽");
h.setAge(25);
h.setDragonBlade(blade);
return h;
}
在运行之前的测试代码后发生异常:
expected single matching bean but found 2: blade1,blade2
原因是Spring容器中有2把青龙偃月刀
Spring并不能决定将哪一把赋给关羽的方法中
所以发生了异常
解决方法时,注入关羽的方法的参数名,需要匹配其中一个青龙偃月刀的id
代码如下
@ComponentScan("cn.tedu.hero")
public class Config {
//现在Spring容器中注入两把青龙偃月刀
@Bean
public DragonBlade blade1(){//id为blade1
return new DragonBlade();
}
@Bean
public DragonBlade blade2(){//id为blade2
return new DragonBlade();
}
@Bean
//方法的参数为DragonBlade类型
//这个参数编写后,spring容器会自动从当前容器中所有内容中搜索
//只要有DragonBlade类型的对象就会自动赋值到这个参数中
//但是如果当前Spring容器中有两个或以上的DragonBlade对象
//就需要按照DragonBlade对象的id来声明这个方法参数的属性名
//如果方法参数的名称没有匹配任何Spring容器中的id,则会发生异常
public Hero guanYu(DragonBlade blade2){
Hero h=new Hero();
h.setName("关羽");
h.setAge(25);
h.setDragonBlade(blade2);
return h;
}
}
没有类型匹配报异常
唯一类型匹配会成功
多个类型匹配看ID
组件扫描情况下依赖注入的实现
编写青龙偃月刀类
@Component
public class DragonBlade {
private String name="青龙偃月刀";
@Override
public String toString() {
return name;
}
}
编写关羽类
注意使用@Autowired来自动装配需要的属性
@Component
public class GuanYu implements Serializable {
private String name="关羽";
//@Autowired英文翻译为自动装配
//表示这个注解下面声明的属性Spring会自动将合适的类型的对象赋给它使用
//当前Spring容器中包含唯一匹配DragonBlade类型的对象,那么它会自动赋值
@Autowired
private DragonBlade dragonBlade;
public void fight(){
System.out.println(name+"使用"+dragonBlade+"战斗");
}
// getset略
}
配置类
@ComponentScan("cn.tedu.hero2")
public class Config {
}
最后进行测试
使用Set方法注入
在上面的组件扫描实现的依赖注入的代码中
实际上Spring还允许使用Set方法上添加@Autowired的方式来实现依赖注入
代码如下
@Component
public class GuanYu implements Serializable {
private String name="关羽";
//@Autowired英文翻译为自动装配
//表示这个注解下面声明的属性Spring会自动将合适的类型的对象赋给它使用
//当前Spring容器中包含唯一匹配DragonBlade类型的对象,那么它会自动赋值
private DragonBlade dragonBlade;
public void fight(){
System.out.println(name+"使用"+dragonBlade+"战斗");
}
...//省略其它get\set
public DragonBlade getDragonBlade() {
return dragonBlade;
}
//Set方法上添加@Autowired注解也能实现依赖注入功能
@Autowired
public void setDragonBlade(DragonBlade dragonBlade) {
this.dragonBlade = dragonBlade;
}
}
Ioc\DI的解耦
什么是耦合?
所谓耦合就是两个有依赖关系的类
他们的依赖关系是定死的,比如关羽必须使用青龙偃月刀
这样的耦合会造成关羽无法使用其他武器战斗,这明显是不合理的
会使程序造成可维护性和可扩展性差的情况
什么是解耦
解耦就是解除两个类之间定死的依赖关系
我们的程序应该追求较低的耦合性,而形成低耦合程序非常重要的原则就是
形成依赖的属性声明为接口而不是具体类
集体解耦操作如下
步骤1:
声明一个接口
// 这是一个武器接口
public interface Weapon {
//实现武器接口必须重写toString方法
public String toString();
//我们通过接口能够降低程序的耦合性
//在依赖武器的对象中声明时,声明接口类型对象,而不声明具体类
}
步骤2:
青龙偃月刀实现这个接口
@Component
public class DragonBlade implements Weapon, Serializable {
private String name="青龙偃月刀";
@Override
public String toString() {
return name;
}
}
步骤3:
在GuanYu类中原本依赖青龙偃月刀的代码
替换为依赖Weapon接口
@Component
public class GuanYu implements Serializable {
private String name="关羽";
//@Autowired英文翻译为自动装配
//表示这个注解下面声明的属性Spring会自动将合适的类型的对象赋给它使用
//当前Spring容器中包含唯一匹配DragonBlade类型的对象,那么它会自动赋值
// 为了实现解耦,我们声明为了Weapon接口
// 这时自动装配任何实现了Weapon接口的类对象
@Autowired
private Weapon weapon;
//为了降低程序的耦合性,将原声明的具体类DragonBlade
//修改为声明接口Weapon
//private DragonBlade dragonBlade;
public void fight(){
System.out.println(name+"使用"+weapon+"战斗");
}
//省略get\set
}
测试运行
习题:
将我们之前编写的Person和Pen类解耦
建议创建一个接口WriteAble包含toString方法的声明
Pen类实现WriteAble接口
Person类中原文声明Pen的位置修改为声明WriteAble
并实现依赖注入测试运行
实现依赖注入对象的更换
之前关羽都是使用青龙偃月刀
现在我们新编写一个武器:方天画戟SkyLancer也实现Weapon接口
研究观察怎么样让关羽更换方天画戟战斗
定义方天画戟类
@Component
public class SkyLancer implements Weapon, Serializable {
private String name="方天画戟";
@Override
public String toString() {
return name;
}
}
注释青龙偃月刀的@Component注解
//@Component 一旦注释这个类对象就不会Spring容器中
public class DragonBlade implements Weapon, Serializable {
private String name="青龙偃月刀";
....
}
现在Spring容器中的唯一的Weapon类型的对象就是方天画戟
关羽类没有修改任何代码,只能获得方天画戟作为武器
测试运行,输出:关羽使用方天画戟战斗
当@Autowired匹配多个类型对象的注意事项
上面的代码如果青龙偃月刀和方天画戟同时在Spring容器中会发生下列异常
expected single matching bean but found 2: dragonBlade,skyLancer
异常和之前使用@Bean注入时遇到的一样
原因是Spring容器中有青龙偃月刀和方天画戟两个武器
Spring无法抉择将哪一个对象赋值给关羽的weapon属性中,所以发生异常
解决方法有两个:
-
声明武器类型的属性名为其中一把武器的id
代码如下
@Component public class GuanYu implements Serializable { private String name="关羽"; //@Autowired英文翻译为自动装配 //表示这个注解下面声明的属性Spring会自动将合适的类型的对象赋给它使用 //当前Spring容器中包含唯一匹配DragonBlade类型的对象,那么它会自动赋值 // 为了实现解耦,我们声明为了Weapon接口 // 这时自动装配任何实现了Weapon接口的类对象 @Autowired // @Qualifier注解来决定Spring容器中具体id的对象注入到weapon属性中 //@Qualifier("dragonBlade")//指定了id为dragonBlade的对象赋值到weapon属性中 private Weapon skyLancer; //为了降低程序的耦合性,将原声明的具体类DragonBlade //修改为声明接口Weapon //private DragonBlade dragonBlade; public void fight(){ System.out.println(name+"使用"+skyLancer+"战斗"); } ....getset略 public Weapon getSkyLancer() { return skyLancer; } public void setSkyLancer(Weapon skyLancer) { this.skyLancer = skyLancer; } }
这种方法的缺点很明显,在修改属性名后,有一系列使用该属性名的位置都需要修改
可维护性不好
-
使用@Qualifier注解来指定要使用的对象的ID
代码如下
@Component public class GuanYu implements Serializable { private String name="关羽"; //@Autowired英文翻译为自动装配 //表示这个注解下面声明的属性Spring会自动将合适的类型的对象赋给它使用 //当前Spring容器中包含唯一匹配DragonBlade类型的对象,那么它会自动赋值 // 为了实现解耦,我们声明为了Weapon接口 // 这时自动装配任何实现了Weapon接口的类对象 @Autowired // @Qualifier注解来决定Spring容器中具体id的对象注入到weapon属性中 @Qualifier("dragonBlade")//指定了id为dragonBlade的对象赋值到weapon属性中 private Weapon weapon; //为了降低程序的耦合性,将原声明的具体类DragonBlade //修改为声明接口Weapon //private DragonBlade dragonBlade; public void fight(){ System.out.println(name+"使用"+weapon+"战斗"); } ..省略get\set public Weapon getWeapon() { return weapon; } public void setWeapon(Weapon weapon) { this.weapon = weapon; } }
同时使用@Component和@Bean实现依赖注入
我们将现有的武器青龙偃月刀和方天画戟类上的@Component删除
在Config类中使用@Bean注入这两个对象
代码如下
@ComponentScan("cn.tedu.hero2")
public class Config {
@Bean
public DragonBlade db() {
return new DragonBlade();
}
@Bean
public SkyLancer lan(){
return new SkyLancer();
}
}
这样的话在GuanYu类中使用的weapon属性在赋值时实际上获得就是@Bean注入的对象了
这种操作在Spring中是完全允许的
需要注意的就是使用@Qualifier注解执行正确的对象id即可
习题
将我们上面习题中编写的Pen和Pencil使用@Bean的方式注入
并在Person类中获得依赖注入并输出结果
使用Spring获得properties文件信息
步骤1:
首先将我们的jdbc.properties文件复制到我们的resources文件夹
步骤2:
pom.xml文件中添加mysql和druid连接池的依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.19</version>
</dependency>
步骤3:
使用Spring提供的功能获得properties文件中的信息
注意如果jdbc.properties是复制到idea中的,可能需要ReBuild项目
//指定配置文件的位置
//classpath实际上就是指resources文件夹
@PropertySource("classpath:jdbc.properties")
public class Config {
//这个自动装配是Spring获取Spring中自带的Environment类型对象
//这个对象会对@PropertySource声明的配置文件进行解析
@Autowired
Environment env;
@Bean
public DataSource dataEnv(){
System.out.println(env);
DruidDataSource ds=new DruidDataSource();
//使用env对象为ds设置参数
ds.setUrl(env.getProperty("db.url"));
ds.setDriverClassName(env.getProperty("db.driver"));
ds.setUsername(env.getProperty("db.username"));
ds.setPassword(env.getProperty("db.password"));
ds.setMaxActive(
env.getProperty("db.maxActive",Integer.class));
ds.setInitialSize(
env.getProperty("db.initialSize",Integer.class));
return ds;
}
}
然后就可以测试了
测试代码如下
@Test
public void test() throws SQLException {
DataSource ds=ctx.getBean("dataEnv",DataSource.class);
try(Connection conn=ds.getConnection()){
String sql="select username from vrduser where id=4";
PreparedStatement ps=conn.prepareStatement(sql);
ResultSet rs=ps.executeQuery();
while(rs.next()){
System.out.println(rs.getString(1));
}
}
}
实际上还有另外一种方式获得properties文件中的信息
代码如下
@PropertySource("classpath:jdbc.properties")
public class ConfigValue {
//这个方法无需自动装配Environment对象
//@Value实际上会自动从@PropertySource指定的配置文件中获得信息
//@Value("${}") ${}中的内容是properties文件中的name(键)
// 这个键对应的值会自动赋值到@Value注解之后的参数中!
@Bean
public DataSource dataValue(
@Value("${db.driver}") String driver,
@Value("${db.url}") String url,
@Value("${db.username}") String username,
@Value("${db.password}") String password,
@Value("${db.maxActive}") int maxActive,
@Value("${db.initialSize}") int initialSize
){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
ds.setMaxActive(maxActive);
ds.setInitialSize(initialSize);
return ds;
}
}