006,Spring注解方式装配Bean

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属性。这里老师就不给大家演示了,大家自己下来手动实验一下。

你可能感兴趣的:(006,Spring注解方式装配Bean)