Spring注解方式装配Bean
V哥官网:http://www.vgxit.com
本文对应视频教程:http://www.vgxit.com/course/23
1,概述
通过之前的学习,我们己经知道如何使用XML的方式去装配Bean但是更多的时候我们已经不再推荐使用XML的方式去装配Bean(注意,XML装配Bean的知识你还是要会),更多时候会考虑使用注解的方式去装配Bean。使用注解的方式可以减少XML的配置,并且注解功能更为强大,它既能实现XML的功能,也提供了自动装配的功能,采用了自动装配后,程序员所需要做的决断就少了,更加有利于对程序的开发,这就是“约定由于配置” 的开发原则。
在Spring中,提供了两种方式来发现Bean:
- 组件扫描:通过定义资源的方式,让Spring Ioc容器扫描对应的包,从而把Bean装配进来。
- 自动装配:通过定义注解,让一些依赖关系可以通过注解完成。
因为使用注解的方式来开发Spring应用已经是主流了,所以,我们后续的课程主要还是基于注解来讲解的。
2,使用@Component装配Bean:
1,我们定义一个类,到时候使用这个类来装配bean:
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class Dog {
@Value("1")
private Integer id;
@Value("丑丑")
private String name;
@Value("贵宾犬")
private String breed;
}
2,定义类之类:
package com.vgxit.learn.spring.ktdm.vgannioc;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
public class VgAnnIocConfig {
}
3,测试代码:
package com.vgxit.learn.spring.ktdm.vgannioc.test;
import com.vgxit.learn.spring.ktdm.vgannioc.VgAnnIocConfig;
import com.vgxit.learn.spring.ktdm.vgannioc.beans.Dog;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class VganniocTest01 {
public static void main(String[] args) {
//获取Spring的上下文
ApplicationContext ctx = new AnnotationConfigApplicationContext(VgAnnIocConfig.class);
Dog dog = (Dog) ctx.getBean("dog");
System.out.println(dog);
}
}
代码分析:
1,@Component注解代表Spring会把这个注解修饰的类扫描成bean。这个注解的value属性就相当于我们在XML中配置的bean的id。我们在开发的时候也可以不用指定value属性,在不指定的时候,spring会自动给我们生成一个id,这个id就是我们的类名首字母小写。
2,注解@Value代表的是值的注入,这里我们只是简单的注入了一些值。这个地方id是int类型。这个时候我们不用操心,Spring会自己知道帮我们转换的
3,我们定义了一个类VgAnnIocConfig。这个类就相当于我们之前用过得xml的配置文件,我们就可以理解成它就是Spring的一个配置类。这个类里面使用了一个注解@ComponentScan,这个注解代表的就是扫描当前包和子包。
4,使用了注解的方式注入bean之后,我们开发的时候的上下文用AnnotationConfigApplicationContext
3,自定义扫描包
按照之前讲的方式,我们定义的扫描配置只能扫描配置类对应的包和子包。但是在真实的开发中,我们更多的情况是需要自定义要扫描的包的路径,这个时候如何解决呢?
@ComponentScan有一个属性叫做basePackages,这个属性是一个数组,这个数组里面存放的就是我们要扫描的包的路劲。只要你配置了这个属性了,Spring会自动的扫描你配置的包和下面的子包。4
4,自定义扫描类
就是我们还可以通过指定对应的类的方式来进行扫描,如果我们指定了对应的类,那么Spring会扫描对应的类所在包下面的所有类。
@ComponentScan(basePackageClasses = Cat.class)
public class VgAnnIocConfig {
}
5,按类型获取Bean
按照之前我们的学习,我们获取对应的bean是通过id来获取的。其实Spring还给我们提供了通过类型获取Bean的方式。
public class VganniocTest01 {
public static void main(String[] args) {
//获取Spring的上下文
ApplicationContext ctx = new AnnotationConfigApplicationContext(VgAnnIocConfig.class);
//在ioc容器中找到对应的类型的bean,然后返回
Dog dog = ctx.getBean(Dog.class);
Cat cat = ctx.getBean(Cat.class);
System.out.println(dog);
System.out.println(cat);
}
}
6,自动装配
我们上面给大家介绍的Dog,Cat这些类的属性,要不是int,要不就是String。反正都是一些简单数据类型,但是如果有一个类是复杂数据类型的怎么办?
1,首先,我们给Cat(猫)定义一个主人类:
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class Master {
@Value("1")
private Integer id;
@Value("V哥")
private String name;
}
2,然后改造我们的Cat类:
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
@Component
@Data
public class Cat {
@Value("1")
private Integer id;
@Value("汤圆")
private String name;
@Value("美短")
private String breed;
@Autowired//这个注解回去Spring的ioc容器中寻找对应类型的bean,然后引用过来
private Master master;
}
这里我们使用了AutoWried注解。这个注解的作用是,Spring把所有的Bean都生成好了之后,如果发现了有这个注解,Spring就会按照这个属性的类型去Spring容器里面找到对应的已经创建好的Bean,然后注入到这个属性中。
上面我们讲了自动装配,但是是有一些问题的。比如我们的Master没有注入到IOC容器中的时候,上面程序报错。但是有的时候,我们就希望如果容器中有对应类型的Bean就引用过来。如果没有对应类型的Bean那么就不要应用。这个时候,我们可以使用AutoWired的required属性。
@Autowired(required = false)
private Master master;
这里我们说一下,我们除了配置属性之外,还可以配置属性方法。比如我们可以在属性对应的set方法上来配置这个注解完成。但是没有必要,因为对于PO,我们一般都是使用lombok来开发。然后对于Service,Controller我们根本就没有为属性提供get和set方法的必要。
7,自动装配的歧义
我们上面讲解了Autowried注解,使用它是非常简单的,并且可以完成自动装配的功能。它是按照类型来匹配。这个时候会产生一些问题。
比如我们定义一个接口叫做Cat:
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
public interface Cat {
void say();
}
他分别有两个实现类,一个是MeiduanCat,一个是LihuaCat:
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import org.springframework.stereotype.Component;
@Component
public class MeiduanCat implements Cat {
@Override
public void say() {
System.out.println("喵喵");
}
}
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import org.springframework.stereotype.Component;
@Component
public class LihuaCat implements Cat {
@Override
public void say() {
System.out.println("哈哈");
}
}
然后,我们把Cat类型引用到另外一个bean中。
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
@Data
public class Master {
@Autowired
private Cat cat;
public void catSay() {
cat.say();
}
}
这个时候我们运行,直接报错,这个是因为Cat是借口,对应有两个实现类,一个是MeiduanCat一个是LihuaCat。我们使用Autowried注解来注入一个Cat的时候,Spring发现有两个不同类型的对象都是可以注入的。这个时候,Spring就不知道该注入哪一个了。这个时候程序直接报错。
8,注解Primary
注解Primary代表受邀的,当Spring IOC通过一个接口或者抽象类注入对象的时候,如果存在了多个实现类。spring就不知道该注入哪一个好了。这个我们可以使用Primary注解。这个注解的作用就是告诉Spring容器,优先使用我来注入。
@Component
@Primary
public class LihuaCat implements Cat {
@Override
public void say() {
System.out.println("哈哈");
}
}
9,注解Qualifier
上面我们可以是用Primary注解来设置一个有限注入的对象。但是有的时候还是不能满足我们的需求,因为使用了这个注解之后,我们只要使用的AutoWired注解来注入对象的时候,都会注入加入了Primary注解的对象。而其他对象就无法注入进来了。这个时候,Spring给我们提供了一个按照名称来注入的方式。这个名称就是Bean的id。
1,在注入的Component的时候,我们指定Bean的id
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import org.springframework.stereotype.Component;
@Component("lihuaCat")
public class LihuaCat implements Cat {
@Override
public void say() {
System.out.println("哈哈");
}
}
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import org.springframework.stereotype.Component;
@Component("meiduanCat")
public class MeiduanCat implements Cat {
@Override
public void say() {
System.out.println("喵喵");
}
}
2,在引用的时候,我们通过Qualifier注解来引用对应的Bean
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Data
public class Master {
@Autowired
@Qualifier("meiduanCat")
private Cat cat;
public void catSay() {
cat.say();
}
}
10,注解+构造方法注入
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Data
public class Master {
private Cat cat;
public Master(@Autowired @Qualifier("lihuaCat") Cat cat) {
this.cat = cat;
}
public void catSay() {
cat.say();
}
}
11,使用Bean注解:
上面我们都是通过Component来装配Bean。但是Component注解只能用在类上面,不能用在方法上面。但是,如果我们遇到我们要注入一个第三方包提供的类,我们应该怎么办呢?
比如,我们使用Druid来创建一个数据库连接池,但是我们希望这个连接池要让spring来管理。但是Druid阿里巴巴给我们提供的是第三方的开源包,我们不可能去修改源代码让其带上Component注解。这个时候我们又希望要注入,怎么办?
1,导入druid和mysql驱动:
com.alibaba
druid
1.2.5
mysql
mysql-connector-java
8.0.21
runtime
2,我们修改BeanConfig类,让其成为一个配置类。加上Configuration注解。
package com.vgxit.learn.spring.ktdm.vgannioc.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ComponentScan(basePackages = "com.vgxit.learn.spring.ktdm.vgannioc.beans")//定义扫描包的路劲
@Configuration//表明这个类是一个配置类
public class VgAnnIocConfig {
}
3,定义druid数据库连接的属性,druid.properties:
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis.ktdm?serverTimezone=Asia/Shanghai
username=root
password=Abc@123456
# 初始化连接数量
initialSize=5
# 最大连接数
maxActive=10
# 最大等待时间
maxWait=3000
4,提供获取数据库连接池的方法:
package com.vgxit.learn.spring.ktdm.vgannioc.config;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.Properties;
@ComponentScan(basePackages = "com.vgxit.learn.spring.ktdm.vgannioc.beans")//定义扫描包的路劲
@Configuration//表明这个类是一个配置类
public class VgAnnIocConfig {
/**
* 定义一个获取数据源的方法
* @return
*/
@Bean(name = "dataSource")//我们把方法返回的数据源作为一个Bean注入到Spring容器中
public DataSource getDataSource() throws Exception {
Properties properties = new Properties();
InputStream druidInputStram = VgAnnIocConfig.class.getClassLoader().getResourceAsStream("druid.properties");
properties.load(druidInputStram);
DataSource ds = DruidDataSourceFactory.createDataSource(properties);
return ds;
}
}
5,编写数据库连接池的使用方法:
package com.vgxit.learn.spring.ktdm.vgannioc.test;
import com.vgxit.learn.spring.ktdm.vgannioc.config.VgAnnIocConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class VganniocTest002 {
public static void main(String[] args) throws SQLException {
ApplicationContext ctx = new AnnotationConfigApplicationContext(VgAnnIocConfig.class);
DataSource ds = ctx.getBean(DataSource.class);
//新的语法来给大家写try resources
Connection conn = ds.getConnection();
PreparedStatement ps = conn.prepareStatement("select * from user");
ResultSet rs = ps.executeQuery();
try (conn; ps; rs){
while (rs.next()) {
System.out.println(rs.getInt("id") + "-------->" + rs.getString("name"));
}
}
}
}
12,单例注入和多例注入
我们上面的所有的注入方式,在Spring中其实使用单例的方式的。也就是我们创建好了对应的Bean之后,放到Spring容器中,我们要用得时候,Spring自己去IOC容器里面拿出来给我们用就好了。但是,我们还可以进过配置让Spring用多例的方式给我们创建。多例注入的意思,就是我们要使用的时候,spring会给我们立马创建一个(new一个)出来,然后给我们使用。
比如,我们来看看原来的使用方式:
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Data
public class Master {
@Autowired @Qualifier("lihuaCat")
private Cat cat;
public Master() {
System.out.println("创建了Master对象");
}
public void catSay() {
cat.say();
}
}
然后,我们通过spring容器两次获取了对应的Master对象:
public static void main(String[] args) {
//获取Spring的上下文
ApplicationContext ctx = new AnnotationConfigApplicationContext(VgAnnIocConfig.class);
//在ioc容器中找到对应的类型的bean,然后返回
Master master1 = ctx.getBean(Master.class);
master1.catSay();
Master master2 = ctx.getBean(Master.class);
master2.catSay();
}
然后我们发现Master对应的构造方法被调用了两次。
但是如果我们使用多例的注入方式,那么对应的代码如下:
package com.vgxit.learn.spring.ktdm.vgannioc.beans;
import lombok.Data;
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
@Data
@Scope("prototype")//这种方式就是多例的注入方式
public class Master {
@Autowired @Qualifier("lihuaCat")
private Cat cat;
public Master() {
System.out.println("创建了Master对象");
}
public void catSay() {
cat.say();
}
}
这个时候我们重新运行测试代码,可以发现Family的构造方法被调用了两次,这个可以证明,Spring每次都给我们创建了一个新对象。在默认情况下@Scope默认的方式就是单例的。如果我们要配置多列可以用@Scope("prototype"),当然我们要手动实现单例可以使用@Scope("singleton")
如果我们使用xml的方式来配置单例和多列,可以使用bean标签的scope属性。这里老师就不给大家演示了,大家自己下来手动实验一下。