Spring-IOC:Bean的作用域、生命周期、XML的装配、注解注入、@Autowired

Bean的作用域

<!-- 3.bean的作用域:
    prototype:多实例
            1>容器启动 默认不会创建多实例bean
            2>获取的时候 创建这个bean
            3>每次获取都会创建一个新的实例
    singleton:单实例,默认的
           1>在容器启动完成之前 就已经创建好对象,保存在容器中了
           2>任何时候获取 都是获取之前创建好的对象
    request:在web环境下,同一次请求创建一个bean实例(没用)
    session:在web环境下,同一次会话创建一个bean实例(没用)
  -->
<bean ></bean>

scope的值:

  • prototype:多实例
    1>容器启动 默认不会创建多实例bean
    2>获取的时候 创建这个bean
    3>每次获取都会创建一个新的实例

  • singleton:单实例,默认的
    1>在容器启动完成之前 就已经创建好对象,保存在容器中了
    2>任何时候获取 都是获取之前创建好的对象

  • request:在web环境下,同一次请求创建一个bean实例(没用)

  • session:在web环境下,同一次会话创建一个bean实例(没用)

工厂模式创建bean

1.概述和准备

bean的创建默认就是框架利用反射new出来的bean实例

新建AirPlan类

public class AirPlan {

    private String fdj;//发动机
    private String yc;//机翼长度
    private Integer personNum;//载客人数
    private String jzName;//机长
    
    
    get/set方法....

我们在之前的时候 如果通过bean实例进行创建AirPlan会有个弊端

<bean >
	<property name="fdj" value="中国制造发动机"></property>
    <property name="yc" value="50"></property>
    <property name="personNum" value="300"></property>
    <property name="jzName" value="机长1号"></property>
</bean>
<bean >
	<property name="fdj" value="中国制造发动机"></property>
    <property name="yc" value="50"></property>
    <property name="personNum" value="300"></property>
    <property name="jzName" value="机长2号"></property>
</bean>

而我们看到 其实不同的飞机bean 他们的区别也就只有机长的名字不同罢了

所以为了解决这个问题,我们出现了工厂模式

2.工厂模式

工厂模式:工厂帮我们创建对象,有一个专门帮我们创建对象的类,这个类就是工厂

例如 :AirPlan ap = AirPlanFactory.getAirPlan(String jzName); 具体的细节我们不需要在做

静态工厂

静态工厂:工厂本身不用创建对象,都是通过静态方法调用 :对象 = 工厂类.工厂方法名()

package com.jiang.factory;/*
 *
 * @author JiangPeng
 * @My code no bug
 */

import com.jiang.bean.AirPlan;

//静态工厂
public class AirPlanStaticFactory {

    public static AirPlan getAirPlan(String jzName){
        System.out.println("AirPlanStaticFactory...静态工厂正在造飞机");
        AirPlan airPlan = new AirPlan();
        airPlan.setFdj("发动机");
        airPlan.setJzName(jzName);
        airPlan.setPersonNum(300);
        airPlan.setYc(50);
        airPlan.setFjsName("副驾驶01");
        return airPlan;
    }
}

在Xml中使用静态工厂创建bean

<!-- 静态工厂(不需要创建工厂本身)   -->
<bean >
    <!--factory-method:指定那个是工厂方法-->
    <!--通过constructor-arg传参 因为只有一个jzName需要传入 可以省略name直接写value-->
    <constructor-arg name="jzName" value="机长01"></constructor-arg>
</bean>
  • factory-method :指定工厂方法是那个
  • 通过constructor-arg传参 因为只有一个jzName需要传入 可以省略name直接写value

实例工厂

实例工厂:工厂本身用创建对象:

​ 工厂类 工厂对象 = new 工厂类()

​ 工厂对象.getAirPlan("张三");

package com.jiang.factory;/*
 *
 * @author JiangPeng
 * @My code no bug
 */

import com.jiang.bean.AirPlan;

//实例工厂
public class AirPlanInstanceFactory {

    //new AirPlanInstanceFactory.getAirPlan()
    public AirPlan getAirPlan(String jzName){
        System.out.println("AirPlanInstanceFactory...实例工厂正在造飞机");
        AirPlan airPlan = new AirPlan();
        airPlan.setFdj("发动机");
        airPlan.setJzName(jzName);
        airPlan.setPersonNum(300);
        airPlan.setYc(50);
        airPlan.setFjsName("副驾驶01");
        return airPlan;
    }

}

在Xml中使用实例工厂创建bean

<!-- 实例工厂   -->
<!-- a.首先创建我们的实例工厂(我们实例工厂是先new AirPlanInstanceFactory.getAirPlan() ) -->
<bean name="airPlanInstanceFactory" class="com.jiang.factory.AirPlanInstanceFactory"></bean>
<!-- b.然后配置我们的对象(AirPlan)使用那个工厂创建
    factory-bean="airPlanInstanceFactory":指定使用那个实例工厂
    factory-method="getAirPlan":使用那个工厂方法
 -->
<bean name="airPlan02" class="com.jiang.bean.AirPlan"
      factory-bean="airPlanInstanceFactory" factory-method="getAirPlan">
    <!--通过constructor-arg传参 因为只有一个jzName需要传入 也可以省略name直接写value-->
    <constructor-arg name="jzName" value="机长02"></constructor-arg>
</bean>
  • 创建实例工厂(与静态工厂不同,实例工厂是先new AirPlanInstanceFactory.getAirPlan()
  • 配置使用那个工厂 factory-bean:指定那个工厂 factory-method:指定工厂方法

3.FactoryBean

FactoryBean:(Spring规定的一个接口);
只要是这个接口的实现类,Spring都认为是一个工厂

1.编写一个FactoryBean的实现类 :即新建MyFactoryBeanImpl.java

package com.jiang.factory;/*
 *
 * @author JiangPeng
 * @My code no bug
 */

import com.jiang.bean.Book;
import org.springframework.beans.factory.FactoryBean;

import java.util.UUID;

/**
 * 实现了FactoryBean接口的类都是Spring可以识别的工厂类
 * Spring会自动调用工厂方法创建实例
 *
 * 使用:1.编写一个FactoryBean的实现类
 *      2.在Spring配置文件中进行注册
 */
public class MyFactoryBeanImpl implements FactoryBean {

    //getObject:工厂方法,返回创建的对象
    public Object getObject() throws Exception {
        System.out.println("MyFactoryBeanImpl...创建对象");
        Book book = new Book();
        book.setBookName(UUID.randomUUID().toString());
        return book;
    }

    //getObjectType:返回创建对象的类型
    //Spring会自动调用这个方法来确认创建的对象是什么类型
    public Class<?> getObjectType() {
        return null;
    }

    //isSingleton:是单例吗? false:不是;true:是
    public boolean isSingleton() {
        return false;
    }
}

2.在Spring配置文件中进行注册

<!--  FactoryBean(Spring规定的一个接口);
      只要是这个接口的实现类,Spring都认为是一个工厂   -->
<bean name="myFactoryBeanImpl" class="com.jiang.factory.MyFactoryBeanImpl"></bean>

生命周期方法的bean

生命周期:bean的创建到销毁;
ioc容器中注册的bean:
1> 单实例bean,容器启动的时候才会创建好,容器的关闭也会销毁创建的bean
2> 多实例bean,获取的时候才会创建
我们可以为bean自定义一些生命周期方法:spring创建或销毁时会调用指定的方法;
init-method:初始化方法; destroy-method:销毁方法;

构造初始化和销毁的方法

public class Book {
    private String bookName;
    private Integer price;

    public void myInit(){
        System.out.println("这是图书的初始化方法..");
    }
    public void myDestory(){
        System.out.println("这是图书的销毁方法..");
    }

xml中通过init-method和destroy-method来指定方法

<bean name="book01" class="com.jiang.bean.Book" 
      init-method="myInit" destroy-method="myDestory">
</bean>

我们运行的结果是:

  • 当我们容器刚启动时候,控制台输出这是图书的初始化方法
  • 当我们调用容器.close()方法 {需要将ApplicationContext接口换一下 ConfigurableApplicationContext}会显示这是图书的销毁方法

特殊:当我们在xml中将我们的bean指定为多实例时

<bean name="book01" class="com.jiang.bean.Book" 
      init-method="myInit" destroy-method="myDestory" scope="prototype">
</bean>

我们的运行结果是:

  • 当我们容器启动时,没有显示初始化方法
  • 当我们获取bean时,显示初始化方法
  • 当我们关闭容器时,没有显示销毁方法

总结:bean的生命周期

单实例: (容器启动)构造器---->初始化方法---->(容器关闭)销毁方法

多实例: (获取bean)构造器---->构造方法---->(容器关闭)不会调用bean的销毁方法

bean的后置处理器

Bean的后置处理器:BeanPostProcessor

Spring有一个接口:后置处理器,可以在bean初始化前后调用方法

1.编写后置处理器的实现类

public class MyBeanPostProcessor implements BeanPostProcessor {

   /*
      postProcessBeforeInitialization:初始化之前调用
      Object bean:将要初始化的bean
      String beanName:bean在xml中配置的id
    */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName+"将要调用初始化方法了...这个bean是这样"+bean);

        return bean;//这里我们要返回传入的bean
    }

    /*
        postProcessAfterInitialization:初始化方法之后调用
     */

    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println(beanName+"初始化方法调用完了...这个bean是这样"+bean);

        return bean;//这里我们要返回传入的bean
    }
}
  • postProcessBeforeInitialization:初始化方法之前调用(Before)
  • postProcessAfterInitialization:初始化方法之后调用(After)
  • Object bean:将要初始化的bean
  • String beanName:bean在xml中配置的id
  • return bean; 最后的return要将我们的bean给返回

2.将后置处理器注册在配置文件中

<bean ></bean>

运行结果

book01将要调用初始化方法了...这个bean是这样Book{bookName='null', price=null}
这是图书的初始化方法..
book01初始化方法调用完了...这个bean是这样Book{bookName='null', price=null}

总结:bean的生命周期

单实例: (容器启动)构造器---->初始化方法---->(容器关闭)销毁方法

多实例: (获取bean)构造器---->构造方法---->(容器关闭)不会调用bean的销毁方法

后置处理器:(容器启动)构造器--= 后置处理器before...=-->初始化方法--= 后置处理器after... =-->bean初始化完成--->(容器关闭)销毁方法

无论bean有没有初始化方法,后置处理器都会默认其有初始化方法,


引用外部属性文件

数据库连接池作为单实例是最好的。
一个项目就一个连接池,连接池里面管理很多连接,连接是直接从连接池拿
可以让Spring帮我们创建连接池对象 (管理连接池)

创建dbconfig.properties 存放数据库信息

jdbc.username=root
jdbc.password=123456
<bean >
    <property name="user" value="${jdbc.username}"></property>
    <!--"{key}" 动态取出properties中key对应的值-->

    <property name="password" value="${jdbc.password}"></property>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/shop?serverTimezone=GMT%2B8"></property>
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
</bean>

通过${key}动态取出properties中key对应的值

PS:在这里如果你是${username} 会出错,因为username是Spring中的一个关键字

如果你这样使用,那么你取出来的值可能会是Spring的username不是数据库中的username

所以我们推荐前面可以加个前缀 比如jdbc.username

//使用DataSource.class来获取连接池
DataSource comboPooledDataSource = app02.getBean("comboPooledDataSource", DataSource.class);
//输出连接 进行测试
System.out.println(comboPooledDataSource.getConnection());

当然你可以自己测试一下 这个username获取的到底是什么

<!-- 测试${username}到底是什么  -->
<bean >
    <property name="bookName" value="${username}"></property>
</bean>

结果:Book{bookName='acer', price=null}

通过${username}获取出来的是我当前的电脑用户名


基于XML的自动装配

自动装配:为自定义类型自动赋值;
javaBean 是基本类型不能自动装配

首先在容器中注册一个Car

<bean >
    <property name="price" value="2000"></property>
    <property name="carName" value="mike"></property>
    <property name="color" value="black"></property>
</bean>

为Person里的自定义类型赋值
property:手动赋值

<bean >
  	<property name="car" ref="car"></property>
</bean>

1.通过XML自动装配

在bean标签里 通过autowire=""进行自动装配;

按照某种规则自动装配:

  • autowire="default/no" (默认,不进行自动装配)
  • autowire="byName"
  • autowire="byType"
  • autowire="constructor":

2.byName

autowire="byName":按照名字

相当于 ioc.getBean("car"); 通过名字

在Person类中有private Car car

当开启自动装配byname的时候,Spring会以属性名(car)作为id去容器中找这个组件,给他赋值

<bean >
</bean>

那么他自动会找寻 bean的id为car的值进行自动装配,也就是自动通过ref进行引入。

当然如果你此时将bean的id改成car01,则会自动装配失败

因为在person类中是private Car car,属性名是car,要么将bean的id改成car,要么private Car car01

3.byType

autowire="byType":按照类型

相当于 ioc.getBean(car.class); 通过类型

<bean >
</bean>

因为我们是按照类型赋值 ,所以此时的bean无论id 是car还是car01 都可以

**注意:如果我们此时的配置文件中当有多个组件,类型相同的话 **

那么通过byType自动装配会报NoUniqueBean错误,即不是唯一的bean错误!

4.constructor

autowire="constructor": 按照构造器赋值

首先先创建一个car 的有参构造器

public Person(Car car) {
    this.car = car;
    System.out.println("可以为car赋值的有参构造器...");
}

然后当我们选择按照构造器来赋值

<bean >
</bean>

过程:public Person(Car car)

1.首先会调用有参构造器,然后先按照有参构造器参数的类型进行装配。

​ 比如有参构造器参数的类型是Car,那么则会先寻找配置文件中的有没有Car类型的bean,进行装配

2.如果按照类型找到了多个,会按照参数的名进行装配car 如果找得到就行装配

3.如果还没有,则会装配为null

补充

如果你的自定义类型是个List集合,比如Person类中的 List<Book> books

那么你通过byType进行自动装配时,假如配置文件中有多个组件都是book类型

那么容器会把容器中所有的book封装成list赋值给这个属性


通过注解进行注入

通过注解分别创建Dao、Service、Controller
通过给bean添加某些注解,可以快速的将bean添加到容器中

给任何一个类加入以下任何一个注解都能将快速的将这个组件添加到ioc容器的管理中

1.Spring中有四个注解:

  • @Controller :控制器,推荐给控制器层的组件使用这个注解,也就是servlet层

  • @Service :业务逻辑层,推荐给业务逻辑层的组件使用这个注解,也就是service层

  • @Repository :持久化层,推荐给数据库层的组件使用这个注解,也就是dao层

  • @Component : 组件的意思,给不属于以上几层的组件添加 这个注解

通过注解将组件快速的加入的ioc容器中过程:

​ 1.给需要添加的组件上标四个注解的任何一个
​ 2.告诉Spring 自动扫描加了注解的组件:依赖context名称空间
​ 3.一定要导入aop包,支持注解模式

使用注解添加到容器中的组件,和使用配置文件加入到容器的组件行为都是默认一样的。
1.组件的id是类名的首字母小写,即BookDao -> bookDao
2.组件的作用域,默认都是单例的。
3.如果要修改默认id,就可以在注解后面("name")即可,@Repository("book")
4.如果要修改作用域,则通过 @Scope 注解进行修改,@Scope(value = "prototype")

2.注解后的扫描

context:component-scan:自动组件扫描
base-package:指定扫描的基础包:把基础包及他下面所有的包的所有加了注解的类,自动扫描进ioc容器中

<context:component-scan base-package="com.jiang.*" >
</context:component-scan>    

规定扫描时排除的组件

context:exclude-filter:排除一些不要的组件

【type="annotation"】 : 指定排除规则:按照注解排除,标注指定的注解的组件不要了
expression="" :注解的全类名 (org.springframework.stereotype.Repository)

【type="assignable"】: 指定排除某个具体的类,按照类排除
expression="" :类的全类名

【type="aspectj"】: 后来的aspectj表达式,没人用过
【type="custom"】:自定义,实现TypeFilter接口,实现match方法,返回true不要,返回false要
【type="regex"】:还可以写正则表达式

<context:exclude-filter type="regex" expression="org.springframework.stereotype.Repository"/>

规定只扫描哪些组件

context:exclude-filter:指定只扫描哪些组件

注意:用之前一定要禁用掉默认规则,因为它默认就是全部扫描进来 use-default-filters="false"

【type="annotation"】 : 指定扫描规则:按照注解扫描,标注指定的注解的组件扫描进来
expression="" :注解的全类名 (org.springframework.stereotype.Repository)

【type="assignable"】: 指定扫描某个具体的类,按照类扫描
expression="" :类的全类名

【type="aspectj"】: 后来的aspectj表达式,没人用过
【type="custom"】:自定义,实现TypeFilter接口,实现match方法,返回true不要,返回false要
【type="regex"】:还可以写正则表达式

<context:component-scan base-package="com.jiang.*" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

@Autowired自动装配

使用@Autowired注解实现根据类型实现自动装配

@Autowired:Spring会自动的为这个属性赋值;

与@Autowired功能相同的还有@Inject @Resource

一定是去容器中找到这个属性对应的组件

(所以要么通过scan扫描包导入容器,要么通过bean标签注入,容器中一定要先有才可以)

Demo:

BookDao.java

@Repository
public class BookDao {
    public void saveBook(){
        System.out.println("dao正在连接数据库保存图书....");
    }

}

BookService

@Service
public class BookService {

    @Autowired
    private BookDao bookDao;

    public void saveBook(){
        System.out.println("service正在通过dao层保存图书,经过service进行验证处理....");
        bookDao.saveBook();
    }
}

BookServlet

@Controller
public class BookServlet {
    @Autowired
    private BookService bookService;

    public void saveBook(){
        System.out.println("servlet用户正在保存图书..");
        bookService.saveBook();
    }
}

@Autowired原理:

@Autowired
private BookService bookService;
1.先按照类型去容器中找到对应的组件bookService = ioc.getBean(BookService.class);
​ 1> 找到一个 直接赋值
​ 2> 没找到,抛异常
​ 3> 找到多个,会装配上
​ 1) 按照变量名作为id继续匹配(private BookService bookService;)
​ 比如一个BookService一个BookServiceExt
​ 1} 匹配上:装配
​ 2} 没有匹配:报错 (private BookService bookService222;)
​ 比如一个BookService一个BookServiceExt
​ 原因:因为我们按照变量名作id继续匹配的,
​ 当我们的变量名默认id和我们的目标bean的id不匹配时,会报错
​ 解决:@Qualifier("bookServiceExt") 指定目标bean的新id

所以我们发现@Autowired标注的自动装配的属性默认是一定装配上的
如果实在找不到不想弄了,则@Autowired(required = false) 找不到就装配上null

区别

@Autowired:功能最强大,Spring规定的标准,离开Spring不能用

@Resource:J2EE,java的标准,扩展性更强:如果切换其他的框架,仍然可以用

@Inject:EJB的


泛型依赖注入

首先我们先来回顾一下之前我们MVC三层架构通过@Autowired自动装配的流程

bean层

Book.java

public class Book{
    
}

User.java

public class User{
    
}

dao层

BaseDao<T>.java 带有泛型的basedao

/*
    在BaseDao中定义了基本的增删改查的方法
 */

public abstract class BaseDao<T> {

    public abstract void save();
}

BookDao

@Repository
public class BookDao extends BaseDao<Book> {
    public void save() {
        System.out.println("BookDao保存图书....");
    }
}

UserDao

@Repository
public class UserDao extends BaseDao<User> {
    public void save() {
        System.out.println("UserDao保存用户...");
    }
}

service层

BookService.java

@Service
public class BookService {
    @Autowired
    BookDao bookDao;
    public void save(){
        bookDao.save();
    }
}

UserService.java

@Service
public class UserService {
    @Autowired
    UserDao userDao;
    public void save(){
        userDao.save();
    }
}

Test测试

public class test {
    public static void main(String[] args) {
        ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookService bookService = app.getBean("bookService", BookService.class);
        UserService userService = app.getBean("userService", UserService.class);
        bookService.save();
        userService.save();
    }
}

BookDao保存图书....
UserDao保存用户...


提取生成 BaseService<T>

public class BaseService<T> {

    @Autowired
    BaseDao<T> baseDao;

    public void save(){
        baseDao.save();
    }

}

此时我们的BookService和UserService 不需要再写注入和方法,只需要继承baseService即可

@Service
public class BookService extends BaseService<Book>{

}
@Service
public class UserService extends BaseService<User>{

}

虽然BaseService中没有标注任何组件的注解,按理来说@Autowired无法自动装配

但是我们最终的BookService和UserService 通过extend继承下来,他们是有组件的注解的,所以可以自动装配

原理分析

1.我们的 UserService extends BaseService<User>BookService extends BaseService<Book>

2.他们最终是要到BaseService中去装配

public class BaseService<T> {
    @Autowired
    BaseDao<T> baseDao;

    public void save(){
        baseDao.save();
    }

}

3.当我们从UserService进入到BaseService中,其实我们的泛型已经规定好了我们是<User>

4.所以我们此时

​ @Autowired
​ BaseDao baseDao; >>> BaseDao baseDao;

5.然后会去Spring容器中找类型为BaseDao的组件

6.即会找到

@Repository
public class UserDao extends BaseDao {

7.所以就可以根据我们Service层的泛型去帮我们找到Dao层的