第三章 全注解下的Spring Ioc

若有错,请指出
第二章 搭建Springboot环境,配置视图解析器jsp页面
第三章 全注解下的Spring Ioc
第四章 约定编程-Spring AOP
第五章 Spring Boot的数据库编程
第九章 初识Spring MVC

3.1 IOC容器简介

在这里插入图片描述
在这里插入图片描述

Spring IOC容器是一个管理Bean的容器,在Spring的定义中,所有的IOC容器都需要实现顶级容器接口BeanFactory
部分源码:

public interface BeanFactory {
    String FACTORY_BEAN_PREFIX = "&";
	//多个getBean方法
	//根据名称获取Bean
    Object getBean(String var1) throws BeansException;
	//根据名称+类型获取Bean
    <T> T getBean(String var1, Class<T> var2) throws BeansException;

    Object getBean(String var1, Object... var2) throws BeansException;

    <T> T getBean(Class<T> var1) throws BeansException;

    <T> T getBean(Class<T> var1, Object... var2) throws BeansException;

    <T> ObjectProvider<T> getBeanProvider(Class<T> var1);

    <T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
	//是否包含Bean
    boolean containsBean(String var1);
	//Bean是否单例,即每次getBean返回同一个对象(默认值)
    boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
	//Bean是否单例,即每次getBean返回不同对象
    boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
}

继承关系图
在这里插入图片描述
举例子
目录结构
在这里插入图片描述

在pom.xml引入依赖配置

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--引入dbcp包和数据库MySQL的依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
<!--        添加属性文件依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

创建简单的springboot项目后,定义简单对象

public class User {
    private Long id;
    private String userName;
    private String note;
    //右键快捷生成get和set方法

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

定义Java配置文件
@Configuration注解表明这是配置文件,Spring的容器会根据它来生成IOC容器去装配Bean
@Bean表示将initUser方法返回的POJO对象装配到IOC容器中,属性name定义这个bean的名称,如果没添加name,则将方法名initUser作为bean名称装配到容器中

import com.springboot.chapter3.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {
    @Bean(name = "user")//定义Bean名称
    public User initUser(){
        User user=new User();
        user.setId(1L);
        user.setUserName("hh");
        user.setNote("note");
        return user;
    }
}

构建IOC容器

import com.springboot.chapter3.pojo.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class IocTest {
    public static void main(String[] args){
        //读取配置文件AppConfig,装配Bean到IOC容器中
        ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
        //根据Bean的类名获取Bean对象
        User user=context.getBean(User.class);
        System.out.println(user.getId()+" "+user.getUserName()+" "+user.getNote());
    }
}

运行截图
在这里插入图片描述

3.2 装配Bean

3.2.1 通过扫描装配Bean

用@Bean一个个注入容器中很麻烦,可以通过进行扫描的方式装配Bean
@Component:表明哪个类注入容器中
@ComponentScan:表明采用何种策略扫描Bean
像@Controller、@Service、@Repository常用注解里面都标注了@Component

在pojo中添加注解@Component

@Component("user")//user为bean名称,若不写默认以类名第一个字母小写其余不变作为bean名
public class User {
    @Value("111")//属性赋值
    private Long id;
    @Value("userName111")
    private String userName;
    @Value("note111")
    private String note;
    //右键快捷生成get和set方法
}

改造AppConfig
在这里插入图片描述
启动IoCTest类
在这里插入图片描述
@ComponentScan做的事情就是告诉Spring从哪里找到Bean
@ComponentScan用法
basePackageClasses:定义扫描的类
下面两个需要通过@Filter去定义
includeFilters:定义满足过滤器条件的Bean才去扫描
excludeFilters:排除过滤条件的Bean

把User类放回到pojo包中再运行会报错,找不到bean对象,可能扫描不到包下的类或者缺失注解@Component
在这里插入图片描述

所以要修改AppConfig的注解@ComponentScan的扫描策略,能够扫描User类,有三种方式

//@ComponentScan("com.springboot.chapter3.*")//扫描该包及其子包被@Component或其他能被扫描的注解标注的类
//@ComponentScan(basePackages = {"com.springboot.chapter3.pojo"})//扫描pojo包下的类
@ComponentScan(basePackageClasses = {User.class})//扫描User类

再次运行就能成功了
在这里插入图片描述
在上面例子中是通过@Configuration标注的配置类AppConfig的注解@ComponentScan来采用扫描策略
我们也可以在启动类中添加如下注解,@ComponentScan详解&@SpringBootApplication的scanBasePackages属性

(scanBasePackages ={"com.springboot.chapter3"} )

3.2.2 自定义第三方Bean

许多Java的应用要引入第三方包,也有可能希望把第三方包对象注入到IOC容器中,可用@Bean注解注入
定义DBCP数据源

        <!--引入dbcp包和数据库MySQL的依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-dbcp2</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

AppConfig类添加下面代码生成数据源

//    自定义第三方bean
	//若不写name,Spring会把方法名称作为Bean名称保存到IOC容器中
    @Bean(name = "dataSource")
    public DataSource getDataSource() {
        Properties props = new Properties();
        props.setProperty("driver", "com.mysql.jdbc.Driver");
        props.setProperty("url", "jdbc:mysql://localhost:3306/chapter3");
        props.setProperty("username", "root");
        props.setProperty("password", "123456");
        DataSource dataSource = null;
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }

编写IocTest类

public class IoCTest {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext ctx=null;
        try {
            ctx=new AnnotationConfigApplicationContext(AppConfig.class);
            //获取第三方Bean
            DataSource dataSource=ctx.getBean(DataSource.class);//通过类型获取
            System.out.println(dataSource.getConnection().getMetaData().getDatabaseProductName());
        }finally {
            if(ctx!=null)
                ctx.close();//关闭IOC容器
        }

运行截图
在这里插入图片描述

3.3 依赖注入

上一节中讲了如何将bean装配到IOC容器中,这里讲讲Bean之间的依赖,也就是依赖注入(Dependency Injection,DI)
例如,人类(Person)利用一些动物(Animal)去完成一些事,狗(Dog)用来看门,猫(Cat)用来抓老鼠;也就是说人类可以依赖动物提供一些特定的服务

3.3.1 样例

目录结构
在这里插入图片描述
创建Person接口和实现类

public interface Person {
    public void service();
}

@Component
public class BussinessPerson implements Person {
    //按类型(Animal)查找IOC容器的Bean,如果找的到就进行赋值
    @Autowired
    Animal animal = null;
    //使用动物服务
    @Override
    public void service() {
        this.animal.use();
    }
}

定义动物接口和实现类

public interface Animal {
    public void use();
}

@Component
public class Dog implements Animal {
    @Override
    public void use() {
        System.out.println("狗【" + Dog.class.getSimpleName() + "】看门用的");
    }
}

测试类

public class IocTest {
    public static void main(String[] args){
        ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);
        Person person=context.getBean(BussinessPerson.class);
        person.service();
    }
}

运行截图
在这里插入图片描述
通过这例子说明Perso需要使用特定动物服务只需要加入一些描述如@Autowired

3.3.2 注解@Autowire

它是使用最多的注解之一,注入机制最基本一条就是根据类型(type)。前面提到的IOC容器顶级接口BeanFactory里有一些getBean方法,就是通过类型(type)或者名称(name)获取bean。
使用规则:
1.先根据类型找到对应Bean
2.如果对应类型不唯一,则根据属性名和Bean名称进行更精确匹配
3.如果还无法匹配就会抛出异常
该注解可以写到属性,set方法和构造方法参数上,效果一样。另外,@Autowire是默认必须找到对应Bean的注解,如果标注了但又找不到会报错,所以可以配置@Atuowire(required=false)允许被标注的属性值为空(不推荐这样写)
在这里插入图片描述

3.3.3 消除歧义性-@Primary和@Qualifier

出现歧义原因可划分3点
1.多个Bean类实现同一个接口
2.Java中存在继承,通过父类型去查找存在多个子类的Bean
3.Java中一个类可以存在多个实例,多个Bean实例都可以存放在IOC容器中
这里模拟第一种情况的歧义
创建猫类

@Component
public class Cat implements Animal {
    @Override
    public void use() {
        System.out.println("猫【" + Cat.class.getSimpleName() + "】抓老鼠的");
    }
}

在这里插入图片描述
虽然可以通过修改属性名让@Autowire根据属性名和类型匹配,但是非常不方便需要改动多处代码

    @Autowired
    Animal cat = null;

我们可以用@Primary标注(这注解只能出现一个)
在这里插入图片描述
也可以用@Qualifier,IOC容器就会以类型和名称去寻找对应的Bean进行注入
在这里插入图片描述
还有比较少用的JavaEE自带注解@Resource,@Autowire和@Resource区别

3.4 Bean的生命周期(重要,面试题)

Bean的生命周期可分为bean的定义、bean的初始化、bean的生存期、bean的销毁4个部分。
在这张图中除了BeanPostProcessor外,其他的接口都是针对当前Bean有效,BeanPostProcessor则是对全部Bean有效。ApplicationContextAware这个接口,只对实现类ApplicationContext的IOC容器生效,只有实现了ApplicationContext接口的容器才会在生命周期调用ApplicationContextAware定义的方法setApplicationContext。
Bean的生命周期图
在这里插入图片描述

Bean定义过程如下:
1.Spring通过我们的配置

ApplicationContext context=new AnnotationConfigApplicationContext(AppConfig.class);

根据AppConfig配置类的注解@ComponentScan定义的扫描路径去找带有@Component的类(@Controller、@Service里面包含@Component也可以被扫描),这里是资源定位的过程
2.找到资源后会开始解析,将定义的信息保存起来。(没有初始化Bean,没有Bean的实例,只是Bean的定义)
3.把Bean的定义发布到IOC(ApplicationContext context)容器中,只有Bean定义,还没bean的实例
有时候我们只需要在取处Bean的时候才做初始化和依赖注入,就需要做到延迟初始化@ComponentScan的配置项lazyInit
在BussinessPerson类添加无参构造方法

    //测试延迟初始化
    public BussinessPerson(){
        System.out.println("初始化");
    }

未延迟初始化时
在这里插入图片描述

配置延迟初始化后需要在取bean的时候就是执行getBean方法时才初始化
在这里插入图片描述
这里来模拟Bean完整的生命周期,实现生命周期图中的接口
改造类BussinessPerson

@Component
public class BussinessPerson implements Person, BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean, DisposableBean {
    //按类型(Animal)查找IOC容器的Bean,如果找的到就进行赋值
    @Qualifier("cat")
    @Autowired//1.注入方式:属性上
    Animal animal = null;
    //测试延迟初始化
    public BussinessPerson(){
        System.out.println("初始化");
    }
    //2.注入方式:构造方法参数上
//    public BussinessPerson(Animal animal){
//        this.animal=animal;
//    }
    //3.注入方式:set方法上
    //@Autowired
    public void setAnimal(Animal animal) {
        this.animal = animal;
    }

    //使用动物服务
    @Override
    public void service() {
        this.animal.use();
    }

    @Override
    public void setBeanName(String beanName) {
        System.out.println("【" + this.getClass().getSimpleName()
                + "】调用BeanNameAware的setBeanName");
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("【" + this.getClass().getSimpleName()
                + "】调用BeanFactoryAware的setBeanFactory");
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        System.out.println("【" + this.getClass().getSimpleName()
                + "】调用ApplicationContextAware的setApplicationContext");

    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("【" + this.getClass().getSimpleName()
                + "】调用InitializingBean的afterPropertiesSet方法");
    }

    @PostConstruct
    public void init() {
        System.out.println("【" + this.getClass().getSimpleName()
                + "】注解@PostConstruct定义的自定义初始化方法");
    }

    @PreDestroy
    public void destroy1() {
        System.out.println("【" + this.getClass().getSimpleName()
                + "】注解@PreDestroy定义的自定义销毁方法");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("【" + this.getClass().getSimpleName()
                + "】 DisposableBean方法");
    }
}

实现BeanPostProcessor接口的类,对所有bean有效

@Component
public class BeanPostProcessorBean implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("所有Bean有效的postProcessBeforeInitialization方法");
        return bean;
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("所有Bean有效的postProcessAfterInitialization方法");
        return bean;
    }
}

IocTest类

public class IocTest {
    public static void main(String[] args){
        //测试Bean的生命周期
        AnnotationConfigApplicationContext context=null;
        try {
            context=new AnnotationConfigApplicationContext(AppConfig.class);
            Person person=context.getBean(Person.class);
            person.service();
        }finally {
            if(context!=null)context.close();//关闭IOC容器
        }
}

运行截图
在这里插入图片描述
@PostConstruct和@PreDestroy注解自定义方法和接口ApplicationContextAware的setApplicationContext方法较为常用

3.5 使用属性文件

在Spring Boot中经常使用配置文件,我们可以用默认的配置文件application.properties也可以自己创建配置文件
1.使用默认配置文件
目录结构在这里插入图片描述
application.properties

database.driverName=com.mysql.jdbc.Driver
database.url=jdbc:mysql://localhost:3306/chapter3
database.username=root
database.password=123456

创建新类使用属性配置

@Component
public class DataBaseProperties {
    //引用application.properties属性名的值
     @Value("${database.driverName}")
    private String driverName;
     @Value("${database.url}")
    private String url;
    private String username;
    private String password;

    public String getDriverName() {
        return driverName;
    }

    public void setDriverName(String driverName) {
        this.driverName = driverName;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUsername() {
        return username;
    }
	//@Value也可以写在set方法上注入
     @Value("${database.username}")
    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

     @Value("${database.password}")
    public void setPassword(String password) {
        this.password = password;
    }
}

上面使用@Value赋值我们也可以用@ConfigurationProperties注解赋值
在这里插入图片描述
控制层

@Controller
public class PropsController {
    @Autowired
    private DataBaseProperties dataBaseProperties = null;
    @RequestMapping("/dbp")
    @ResponseBody
    public DataBaseProperties getDBP() {
        return dataBaseProperties;
    }
}

启动项目然后输入路径
在这里插入图片描述
2.使用新的属性文件
将application.properties的配置放在同目录下的database.properties
在这里插入图片描述
为了能让该配置文件能被扫描还需要在启动类添加如下注解

@PropertySource("classpath:database.properties")

启动后如果出现说database.properties is not exist建议可以试试重建项目(rebuild)
在这里插入图片描述

3.6 条件装配bean

AppConfig类

    @Profile(value = "test")
    @Bean(name = "dataSource", destroyMethod = "close")
    //@Conditional(DatabaseConditional.class)
    public DataSource getDataSource(
            @Value("${database.driverName}") String driver,
            @Value("${database.url}") String url,
            @Value("${database.username}") String username,
            @Value("${database.password}") String password
    ) {
        Properties props = new Properties();
        props.setProperty("driver", driver);
        props.setProperty("url", url);
        props.setProperty("username", username);
        props.setProperty("password", password);
        DataSource dataSource = null;
        try {
            dataSource = BasicDataSourceFactory.createDataSource(props);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }

注释掉某些配置然后运行就会抛出异常
在这里插入图片描述
有时候某些因素会导致Bean无法初始化。例如在数据库连接池中漏掉一些配置造成无法连接,在这种情况下如果IOC容器还想继续装配就会抛出异常。这时不希望装配bean,而是都提供配置后才装配bean。
为了处理这样搞场景我们可以使用过@Conditional注解,需要实现接口Condition的方法来完成对应功能

在getDataSource方法添加注解@Conditional(DatabaseConditional.class)
创建DatabaseConditional类,通过这样在装配bean的时候就match方法会读取上下文环境如果已经配置了对应的数据库信息,返回true执行getDataSource方法,否则不装配bean

package com.springboot.chapter3.condition;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotatedTypeMetadata;
//配置条件装配bean
public class DatabaseConditional implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        //取出环境配置
        Environment environment = context.getEnvironment();
        //判断属性文件是否存在对应的数据库配置
        return environment.containsProperty("database.driverName") &&
                environment.containsProperty("database.url") &&
                environment.containsProperty("database.username") &&
                environment.containsProperty("database.password");
    }
}

3.7 Bean的作用域

之前提到过的IOC容器最顶级接口BeanFactory里面有isSingleton(默认值,IOC容器只存在单例)和isPrototype(每从容器中取bean就创建一个新的bean)
未设置作用域的时候是单例模式,要想定义作用域类可以添加注解@Scope
在这里插入图片描述
测试作用域
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值