Spring Ioc容器

逆流者 2020年08月07日 71次浏览

IOC:控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理

Bean获取

新建一个普通的maven项目,加入下面依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

在resources下创建一个spring config文件(确保上面的依赖已经添加完毕,再创建配置)
bean1.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="top.wushanghui.bean.Book" id="book"/>
</beans>

Book.java

public class Book {
    public void msg() {
        System.out.println("msg...");
    }
}

测试代码:

public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    Book book = context.getBean(Book.class);
    System.out.println(book);
    book.msg();
}

测试结果:

top.wushanghui.bean.Book@31ef45e3
msg...

在测试代码中,大家可以看到我是根据Book.class获取bean的,因为目前配置文件中只有一个<bean class="top.wushanghui.bean.Book" id="book"/> ,当有两个类型相同的bean时,

<bean class="top.wushanghui.bean.Book" id="book" name="book3"/>
<bean class="top.wushanghui.bean.Book" id="book2"/>

此测试代码会报错:org.springframework.beans.factory.NoUniqueBeanDefinitionException
这时我们可以根据id来获取bean,比如:

Object book = context.getBean("book");
Object book3 = context.getBean("book3");
System.out.println(book==book3); // true

根据id或name获取bean不会报错,可以看到上面我又加了一个小测试,分别根据id为book和neme为book3获取bean他们是同一个,其实id和name写一个就可以;
上面获取bean返回的使Object对象,你可以自己强转,不想强转可以使用下面这种写法:

Book book2 = context.getBean("book2", Book.class);

属性的注入

构造方法注入

Book.java

public class Book {

    private Integer id;
    private String name;
    private Double price;

    public Book(Integer id, String name, Double price) {
        this.id = id;
        this.name = name;
        this.price = price;
    }

    public Book() {
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Double getPrice() {
        return price;
    }

    public void setPrice(Double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Book{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", price=" + price +
                '}';
    }
}

bean1.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
       
    <bean class="top.wushanghui.bean.Book" id="book4">
        <constructor-arg index="0" value="1"/>
        <constructor-arg index="1" value="Spring"/>
        <constructor-arg index="2" value="100"/>
    </bean>
</beans>

代码测试:

public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    Book book4 = context.getBean("book4", Book.class);
    System.out.println(book4);
}

结果:
Book{id=1, name='Spring', price=100.0}

在xml中constructor-arg 中 index和value是一一对应的,这种在属性不多时赋值还可以,属性一多,容易出错,推荐下面这种:

<bean class="top.wushanghui.bean.Book" id="book5">
        <constructor-arg name="id" value="2"/>
        <constructor-arg name="name" value="Spring MVC"/>
        <constructor-arg name="price" value="100"/>
    </bean>

在name字段中指定属性名

set方法注入

<bean class="top.wushanghui.bean.Book" id="book6">
    <property name="id" value="3"/>
    <property name="name" value="Mybatis"/>
    <property name="price" value="100"/>
</bean>

需要注意:name的值是不是属性名,而是通过set方法分析出来的,我们测试一下这个
修改Book类中setPrice方法名为setPrice2,

public void setPrice2(Double price) {
   this.price = price;
}

测试一下

public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean1.xml");
    Book book4 = context.getBean("book6", Book.class);
    System.out.println(book4);
}

发现报org.springframework.beans.NotWritablePropertyException: Invalid property 'price' of bean class [top.wushanghui.bean.Book]: Bean property 'price' is not writable or has an invalid setter method. Did you mean 'price2'?

我们把上面的xml改一下:

<bean class="top.wushanghui.bean.Book" id="book6">
    <property name="id" value="3"/>
    <property name="name" value="Mybatis"/>
    <property name="price2" value="100"/>
</bean>

发现测试正常:Book{id=3, name='Mybatis', price=100.0}
其实用idea时,就会给提示出来了
在这里插入图片描述

p 名称空间的注入

bean2.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean class="top.wushanghui.bean.Book" id="book" p:id="1" p:name="Spring" p:price="50"/>

</beans>

在xml中我们引入了p命名空间(xmlns:p="http://www.springframework.org/schema/p"

测试:

public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean2.xml");
    Book book4 = context.getBean("book", Book.class);
    System.out.println(book4); // Book{id=1, name='Spring', price=50.0}
}

外部 bean 注入

bean3.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userService" class="top.wushanghui.bean.UserService">
        <property name="userDao" ref="userDaoImpl"/>
    </bean>
    <bean id="userDaoImpl" class="top.wushanghui.bean.UserDaoImpl"/>

</beans>

UserDao.java

public interface UserDao {

    public void add();
}

UserDaoImpl.java

public class UserDaoImpl implements UserDao {
    @Override
    public void add() {
        System.out.println("UserDaoImpl add()");
    }
}

UserService.java

public class UserService {

    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void add() {
        System.out.println("UserService add()");
        userDao.add();
    }
}

测试:

public static void main(String[] args) {
  ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
    UserService userService = context.getBean("userService", UserService.class);
    userService.add();
}

结果:

UserService add()
UserDaoImpl add()

内部 bean 注入

<bean id="emp" class="top.wushanghui.bean.Employee">
    <!--设置两个普通属性-->
    <property name="id" value="1" />
    <property name="name" value="Tom"/>
    <!--设置对象类型属性-->
    <property name="department">
        <bean class="top.wushanghui.bean.Department">
            <property name="name" value="软件开发部"/>
        </bean>
    </property>
</bean>

Department.java

public class Department {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Department{" +
                "name='" + name + '\'' +
                '}';
    }
}

Employee.java

public class Employee {

    private Integer id;
    private String name;
    private Department department;

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

    public void setName(String name) {
        this.name = name;
    }

    public void setDepartment(Department department) {
        this.department = department;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", department=" + department +
                '}';
    }
}

测试:

public static void main(String[] args) {
   ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean3.xml");
    Employee emp = context.getBean("emp", Employee.class);
    System.out.println(emp);
}

结果:
Employee{id=1, name='Tom', department=Department{name='软件开发部'}}

级联赋值

在外部bean 注入中,已经用过ref="",当时引入UserDaoImpl中没有成员变量,下面我们换个例子再演示下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="emp" class="top.wushanghui.bean.Employee">
        <!--设置两个普通属性-->
        <property name="id" value="2" />
        <property name="name" value="Jerry"/>
        <!--设置对象类型属性-->
        <property name="department" ref="dept" />
    </bean>
    <bean id="dept" class="top.wushanghui.bean.Department">
        <property name="name" value="软件开发部"/>
    </bean>
</beans>

注意:

  • 在xml中可以使用<null/>
<property name="age"> 
    <null/> 
</property> 
  • 属性值包含特殊符号
    可以转义,如:把<>进行转义 &lt; &gt;
    也可以带特殊符号内容写到CDATA中
<property name="address"> 
    <value><![CDATA[<<上海>>]]></value> 
</property> 

复杂属性的注入

  • 注入数组类型属性
  • 注入 List 集合类型属性
  • 注入 Map 集合类型属性
  • properties 注入

Course.java

public class Course {

    private String name;

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Course{" +
                "name='" + name + '\'' +
                '}';
    }
}

Student.java

public class Student {

    //1 数组类型属性
    private String[] array;
    //2 list 集合类型属性
    private List<String> list;
    //3 map 集合类型属性
    private Map<String,String> maps;
    //4 set 集合类型属性
    private Set<String> sets;
    // 5 list 中设置对象
    private List<Course> courses;
    // 6 properties 注入
    private Properties properties;

    public void setArray(String[] array) {
        this.array = array;
    }

    public void setList(List<String> list) {
        this.list = list;
    }

    public void setMaps(Map<String, String> maps) {
        this.maps = maps;
    }

    public void setSets(Set<String> sets) {
        this.sets = sets;
    }

    public void setCourses(List<Course> courses) {
        this.courses = courses;
    }

    public void setProperties(Properties properties) {
        this.properties = properties;
    }

    @Override
    public String toString() {
        return "Student{" +
                "array=" + Arrays.toString(array) +
                ", list=" + list +
                ", maps=" + maps +
                ", sets=" + sets +
                ", courses=" + courses +
                ", properties=" + properties +
                '}';
    }
}

bean5.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="stu" class="top.wushanghui.bean.Student">
        <!--数组类型属性注入-->
        <property name="array">
            <array>
                <value>数组1</value>
                <value>数组2</value>
            </array>
        </property>
        <!--list 类型属性注入-->
        <property name="list">
            <list>
                <value>list1</value>
                <value>list2</value>
            </list>
        </property>
        <!--map 类型属性注入-->
        <property name="maps">
            <map>
                <entry key="key1" value="value1" />
                <entry key="key2" value="value2" />
            </map>
        </property>
        <!--set 类型属性注入-->
        <property name="sets">
            <set>
                <value>set1</value>
                <value>set2</value>
            </set>
        </property>
        <!--在集合里面设置对象类型值-->
        <property name="courses">
            <list>
                <ref bean="course1"/>
                <ref bean="course2"/>
            </list>
        </property>
        <property name="properties">
            <props>
                <prop key="prop1">value1</prop>
                <prop key="prop2">value2</prop>
            </props>
        </property>
    </bean>
    <bean id="course1" class="top.wushanghui.bean.Course">
        <property name="name" value="JVM 课程"/>
    </bean>
    <bean id="course2" class="top.wushanghui.bean.Course">
        <property name="name" value="设计模式"/>
    </bean>
</beans>

测试:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean5.xml");
Student stu = context.getBean("stu", Student.class);
System.out.println(stu);

结果:Student{array=[数组1, 数组2], list=[list1, list2], maps={key1=value1, key2=value2}, sets=[set1, set2], courses=[Course{name='JVM 课程'}, Course{name='设计模式'}], properties={prop2=value2, prop1=value1}}

使用 util 标签完成 list 集合注入提取
在xml配置文件中引入名称空间 util

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">

注意,上面加了xmlns:util="http://www.springframework.org/schema/util" 和 在xsi:schemaLocation=中加了http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd

引入名称空间后,就可以在xml写util标签了

<util:list id="utilList">
   <value>mysql</value>
   <value>oracle</value>
</util:list>

在属性中引入即可

<!--list 类型属性注入-->
<property name="list" ref="utilList"/>

util中还有其他标签的用法,这里不做阐述,和util:list用法类似

FactoryBean

  • Spring 有两种类型 bean,一种普通 bean,另外一种工厂 bean(FactoryBean)
  • 普通 bean:在配置文件中定义 bean 类型就是返回类型(上面属性注入就是普通bean)
  • 工厂 bean:在配置文件定义 bean 类型可以和返回类型不一样

步骤:

1、创建类,让这个类作为工厂 bean,实现接口 FactoryBean

2、实现接口里面的方法,在实现的方法中定义返回的 bean 类型

import org.springframework.beans.factory.FactoryBean;

public class MyBean implements FactoryBean<Course> {

    @Override
    public Course getObject() throws Exception {
        Course course = new Course();
        course.setName("数学");
        return course;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myBean" class="top.wushanghui.bean.MyBean"/>
</beans>
public static void main(String[] args) {
	ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
	Course course = context.getBean("myBean", Course.class);
    System.out.println(course); // Course{name='数学'}
}

bean 作用域

在 Spring 里面,默认情况下,bean 是单实例对象

<bean id="dept" class="top.wushanghui.bean.Department"/>
public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
    Department dept1 = context.getBean("dept", Department.class);
    Department dept2 = context.getBean("dept", Department.class);
    System.out.println(dept1 == dept2); // true
}

dept1 和 dept2 的引用地址相同,bean默认是单例的

设置bean的作用域

bean的作用域:

  • singleton 单实例(默认)
  • prototype 多实例
  • request web环境有效,不常用
  • session web环境有效,不常用

测试下多实例 (prototype)

把上面xml加上scope属性,指定为prototype

<bean id="dept" class="top.wushanghui.bean.Department" scope="prototype"/>

重新执行测试代码,就会发现 System.out.println(dept1 == dept2); // false

singleton 和 prototype 区别

  • singleton 单实例,prototype 多实例

  • 设置 scope 值是 singleton 时候,加载 spring 配置文件时候就会创建单实例对象,设置 scope 值是 prototype 时候,不是在加载 spring 配置文件时候创建 对象,在调用getBean 方法时候创建多实例对象。

其他

id和name的区别

在xml配置中,我们通过id或者name给Bean指定唯一标识,大部分情况下这两个作用是一样的,有个小区别:

  • name支持多个,多个name之间用,隔开
  • id不支持多个,如果强行用,号隔开,比如id="p1,p2",那获取bean,context.getBean("p1,p2", Person.class),p1,p2就是id,不会因为,分隔开
<bean id="p1,p2" class="top.wushanghui.bean.Person" scope="prototype"/>
<bean name="p3,p4" class="top.wushanghui.bean.Person" scope="prototype"/>
public class Person {
}
public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
    Person p1 = context.getBean("p1,p2", Person.class);
    Person p3 = context.getBean("p3", Person.class);
    Person p4 = context.getBean("p4", Person.class);
    System.out.println(p1);
    System.out.println(p3);
    System.out.println(p4);
}

结果:

top.wushanghui.bean.Person@271053e1
top.wushanghui.bean.Person@589838eb
top.wushanghui.bean.Person@42dafa95

如若我们这么测试代码:

Person p2 = context.getBean("p2", Person.class);
System.out.println(p2);

会报错:org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'p2' available

bean 生命周期

从对象创建到对象销毁的过程

一般bean的生命周期

一般bean的生命周期有五步:

  1. 通过构造器创建 bean 实例(无参数构造)
  2. 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)
  3. 调用 bean 的初始化的方法(需要进行配置初始化的方法)
  4. bean 可以使用了(对象获取到了)
  5. 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

代码演示:

<bean id="lifeCycleBean" class="top.wushanghui.bean.LifeCycleBean" init-method="initMethod" destroy-method="destroyMethod">
    <property name="name" value="bean 生命周期演示" />
</bean>
public class LifeCycleBean {

    private String name;

    public LifeCycleBean() {
        System.out.println("1、执行无参构造器创建 bean 实例");
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("2、调用set方法设置属性值");
    }

    public void initMethod() {
        System.out.println("3、执行初始化方法");
    }

    public void destroyMethod() {
        System.out.println("5、执行销毁的方法");
    }

}

测试:

public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean6.xml");
    LifeCycleBean bean = context.getBean("lifeCycleBean", LifeCycleBean.class);
    System.out.println("4、获取到创建 bean 的实例对象");
    System.out.println(bean);
    context.close();
}

结果:

1、执行无参构造器创建 bean 实例
2、调用set方法设置属性值
3、执行初始化方法
4、获取到创建 bean 的实例对象
top.wushanghui.bean.LifeCycleBean@a67c67e
5、执行销毁的方法

配置 bean 的后置处理器

当配置bean的后置处理器之后,bean的生命周期会变为七步:

  1. 通过构造器创建 bean 实例(无参数构造)

  2. 为 bean 的属性设置值和对其他 bean 引用(调用 set 方法)

  3. bean实例传递给bean后置处理器(postProcessBeforeInitialization )

  4. 调用 bean 的初始化的方法(需要进行配置初始化的方法)

  5. bean实例传递给bean后置处理器( postProcessAfterInitialization

  6. bean 可以使用了(对象获取到了)

  7. 当容器关闭时候,调用 bean 的销毁的方法(需要进行配置销毁的方法)

代码演示:

创建 bean后置处理器类

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化方法之前执行");
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("在初始化方法之后执行");
        return null;
    }
}

配置后置处理器

<bean id="myBeanPostProcessor" class="top.wushanghui.bean.MyBeanPostProcessor"/>

再次执行上面一般bean的生命周期的测试代码:

1、执行无参构造器创建 bean 实例
2、调用set方法设置属性值
在初始化方法之前执行
3、执行初始化方法
在初始化方法之后执行
4、获取到创建 bean 的实例对象
top.wushanghui.bean.LifeCycleBean@544fe44c
5、执行销毁的方法

在控制台上我们可以看到后置处理器的已经打印出来了。

xml 自动装配

根据指定装配规则(属性名称或者属性类型),Spring 自动将匹配的属性值进行注入

bean 标签属性autowire,配置自动装配

autowire 属性常用两个值:

  • byName 根据属性名称注入 ,注入值 bean 的 id 值和类属性名称一样
  • byType 根据属性类型注入

根据属性名称自动注入

bean7.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="person" class="top.wushanghui.bean.Person" autowire="byName"></bean>
    <bean id="dog" class="top.wushanghui.bean.Dog"></bean>
</beans>
public class Person {

    private Dog dog;

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Person{" +
                "dog=" + dog +
                '}';
    }
}
public class Dog {
}
public static void main(String[] args) {
    ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean7.xml");
    Person person = context.getBean("person", Person.class);
    System.out.println(person);
}

结果: Person{dog=top.wushanghui.bean.Dog@71318ec4}

看测试结果,Dog类自动注入Person里了,需要注意的是,现在是根据autowire="byName" 名称自动注入Dog类的,这个名称不是属性名,而是setDog方法处理处理后的值 也就是 set后面Dog 首字母小写,dog是<bean id="dog"名称,读者可以尝试改变setDog方法名称,若xml中<bean id="dog" class="top.wushanghui.bean.Dog"></bean> 这个id和set后名称不一致,那测试结果就会是Person{dog=null}

根据属性类型自动注入

<bean id="person" class="top.wushanghui.bean.Person" autowire="byType"></bean>
<bean id="dog" class="top.wushanghui.bean.Dog"></bean>

根据属性类型只需把autowire属性设置为byType,这是id="dog" 可以随意改,改成id="dog13423423423",不影响测试结果。
因为是根据top.wushanghui.bean.Dog类型去查找bean,这时容器中只能有一个这个类型被bean。

引入外部属性文件

下面操作我们需要引入数据库连接池,使用druid。

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.23</version>
</dependency>

直接配置数据库信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"/>
        <property name="username" value="root"/>
        <property name="password" value="12345"/>
    </bean>
</beans>

引入外部属性文件配置数据库连接池

  1. 创建外部属性文件(properties )配置数据库信息
    jdbc.properties
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password="12345
  1. 把外部 properties 属性文件引入到 spring 配置文件中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
        
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--配置数据库连接池-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driverClass}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

注意:需要引入context命名空间,xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd

基于注解方式

注解

自动化扫描标记注解

Spring 针对 Bean 管理中创建对象提供注解

  • @Component 不知道什么层时使用
  • @Service service层使用
  • @Controller controller层使用
  • @Repository dao层使用

上面四个注解功能是一样的,都可以用来创建 bean 实例

对象注入注解

  • @Autowired :根据属性类型进行自动装配
  • @Resource:可以根据类型注入,可以根据名称注入
  • @Qualifier:根据名称进行注入,和上面@Autowired 一起使用
  • @Value:注入普通类型属性

xml配置自动化扫描

UserController.java

package top.wushanghui.annotation.controller;

import org.springframework.stereotype.Controller;

@Controller
public class UserController {
}

UserServic.java

package top.wushanghui.annotation.service;

import org.springframework.stereotype.Service;

@Service
public class UserService {
}

UserDao.java

package top.wushanghui.annotation.dao;

import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
}

上面的三个类都加上自动扫描的注解了

示例 1

bean9.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--示例 1-->
    <context:component-scan base-package="top.wushanghui.annotation"/>
</beans>

根据上面xml扫描规则配置,扫描top.wushanghui.annotation包下面标注自动扫描注解的类
我们测试一下:

public static void main(String[] args) {
   ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("bean9.xml");
    String[] beanDefinitionNames = ctx.getBeanDefinitionNames();
    for (String beanDefinitionName : beanDefinitionNames) {
        System.out.println(beanDefinitionName);
    }
}

看一下容器里有哪些对象被注入了
结果:
userController
userDao
userService
org.springframework.context.annotation.internalConfigurationAnnotationProcessor
org.springframework.context.annotation.internalAutowiredAnnotationProcessor
org.springframework.context.annotation.internalCommonAnnotationProcessor
org.springframework.context.event.internalEventListenerProcessor
org.springframework.context.event.internalEventListenerFactory

我们看到加注解的类在容器里了
userController、userDao、userService
加注解时,不指定名称,默认使用类名首字母小写的形式
请忽略spring容器中默认的对象,像上面打印出来的以org.springframework.context.包开头的,后面我移除掉,重点不在这里。

示例 2

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--示例 2
    use-default-filters="false" 表示现在不使用默认 filter,
    自己配置 filter  context:include-filter ,设置扫描哪些内容
-->
    <context:component-scan base-package="top.wushanghui.annotation" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
   
</beans>

用示例2的扫描规则,我们再测试下:
测试结果:
userController
... 省略spring默认加到容器的对象

我们看到只有 userController 加到容器里了,解释下配置的属性:
use-default-filters="false":不适用默认的过滤策略
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> : 包含类型为annotation(注解)且标注了Controller注解的类加到spring容器中。

示例 3

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <!--示例 3
     下面配置扫描包所有内容
     context:exclude-filter: 设置哪些内容不进行扫描
    -->
    <context:component-scan base-package="top.wushanghui.annotation">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

测试结果:
userDao
userService
... (省略spring默认加到容器的对象)

除了标注controller注解的对象都加到spring容器中

下面改造下上面的例子,来演示下对象注入注解的使用(@Autowired、@Resource、@Qualifier、@Value)

@Controller
public class UserController {

    @Autowired
    private UserService userService;

    public void add() {
        System.out.println("UserController add()");
        userService.add();
    }
}
public interface UserService {

    public void add();
}

@Service
public class UserServiceImpl implements UserService{

    @Resource(name = "userDaoImpl1")
//    @Autowired
//    @Qualifier("userDaoImpl1")
//    @Resource
    private UserDao userDao;

    @Value(value = "abc")
    private String value;

	@Override
    public void add() {
        System.out.println("UserService add() "+value);
        userDao.add();
    }
}
public interface UserDao {

    public void add();
}

@Repository
public class UserDaoImpl1 implements UserDao{

    @Override
    public void add() {
        System.out.println("UserDaoImpl1 add()");
    }
}

@Repository
public class UserDaoImpl2 implements UserDao{

    @Override
    public void add() {
        System.out.println("UserDaoImpl2 add()");
    }
}

执行测试代码:

public static void main(String[] args) {
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("bean9.xml");
    UserController bean = ctx.getBean(UserController.class);
    bean.add();
}

结果:

UserController add()
UserService add() abc
UserDaoImpl1 add()

我们简单分析这注解的用法,在UserController 类中我们使用@Autowired注解来自动装配userService,userService是一个接口,实际上装配的是它的实现类UserServiceImpl,这里说一下,@Autowired注解是根据属性类型进行自动装配,这里userService只能有一个实现类,在UserServiceImpl 实现类中又自动装配了userDao,userDao有两个实现类,spring 容器中会有userDaoImpl1和userDaoImpl2对象,这里就需要指定要装配那个实现了,我们可以用@Resource(name = "userDaoImpl1")或者@Autowired和@Qualifier("userDaoImpl1")一起使用。需要注意的是使用@Resource注解不指定name属性的话,用法和单独使用@Autowired注解作用是相同的,都是根据类型自动装配,强制使用,会报org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'top.wushanghui.annotation.dao.UserDao' available: expected single matching bean but found 2: userDaoImpl1,userDaoImpl2 异常

大家可以看下@Resource注解并不是spring 的注解,它在javax.annotation.Resource 包下,所以建议:
自动装配对象时,只有一个实现类,单独使用@Autowired注解,多个实现类时@Autowired和@Qualifier一起使用,并在@Qualifier注解中指定装配那个实现类。