欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring学习笔记(一)bean的配置

程序员文章站 2022-05-08 10:59:27
...

Spring学习笔记(一)bean的配置

1.IOC与DI

  • IOC(Inversion of Control):其思想是\color{blue}{反转资源获取的方向}。传统的资源查找方式要求组件向容器发起请求查找资源。作为回应,容器适时的返回资源。而应用了IOC之后,则是容器主动地将资源推送给它所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源。这种行为也被称为查找的被动形式。
    • DI(Dependency injection)-IOC的另一种表述方式:即setter\color{blue}{组件以一些预先定义好的方式(例如:setter方法)接受来自如容器的资源注入}

2.配置bean

2.1基于xml文件配置bean

  • 基于xml配置bean
<!--    配置bean:
    class:bean的全类名,通过反射的方式在IOC容器中创建bean。所以要求bean中必须有无参构造的构造器。
    id:标识容器中的bean。id唯一。
-->
    <bean id="user" class="com.nl.spring.bean.User">
        <property name="name" value="abc"></property>
    </bean>
  • Spring容器

    Spring提供了两种类型的IOC容器实现

    • BeanFactory:IOC容器的基本实现

    • ApplicationContext:是BeanFactory的子接口

    • 几乎所有的应用场合都直接使用ApplicationContext而非底层的BeanFactory

  • ApplicationContext

    ApplicationContext的主要实现类:

    • ClassPathXmlApplicationContext:从类路径下加载配置文件
    • FileSystemXmlApplicationContext:从文件系统中加载配置文件

    ConfigurableApplicationContext扩展与ApplicationContext,新增两个主要方法:refresh()和close(),让ApplicationContext具有启动、刷新和关闭上下文的能力。

    ApplicationContext在初始化上下文时就实例化所有单例的bean。

  • 获取bean

ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //1.利用id查找到容器中的bean
        User user = context.getBean("user", User.class);
        //2.通过类型返回容器中的bean。若有多个相同类型时会报错。
        context.getBean(User.class);

2.2依赖注入的方式:属性注入、构造器注入

User类:

package com.nl.spring.bean;

public class User {
    private String name;

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

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
  • 属性输入

    • 属性注入即通过setter方法注入Bean的属性值或依赖的对象。
    • 属性注入使用<property>元素,使用name属性指定bean的属性名称,value属性或<value>子节点指定属性值。
        <bean id="user" class="com.nl.spring.bean.User">
            <property name="name" value="abc"></property>
        </bean>
    
  • 构造方法注入

    • 通过构造方法注入bean的属性值或依赖对象,它保证了Bean实例在实例化后就可以使用。
    • 构造器注入在<constructor-arg>元素里声明属性,<constructor-arg>中没有name属性。

Student类:

package com.nl.spring.bean;

public class Student {
    private String id;
    private String name;
    private Double avgScore;

    public Student() {
    }

    public Student(String id, String name, Double avgScore) {
        this.id = id;
        this.name = name;
        this.avgScore = avgScore;
    }

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

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

    public void setAvgScore(Double avgScore) {
        this.avgScore = avgScore;
    }

    @Override
    public String toString() {
        return "Student{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", avgScore=" + avgScore +
                '}';
    }
}
  1. 普通属性赋值

配置:

<bean id="student" class="com.nl.spring.bean.Student">
    	<!--通过构造器参数索引-->
        <constructor-arg value="0001" index="0"></constructor-arg>
        <constructor-arg value="Andy" index="1"></constructor-arg>
        <constructor-arg value="89.5" index="2"></constructor-arg>
</bean>
<bean id="student1" class="com.nl.spring.bean.Student">
    	<!--通过构造器参数类型-->
       <constructor-arg type="java.lang.String" value="0002"></constructor-arg>
       <constructor-arg type="java.lang.String" value="Sun"></constructor-arg>
       <constructor-arg type="java.lang.Double" value="80"></constructor-arg>
</bean>

​ 两种方式可以混合使用。

​ 若在Student类中aveScore属性类型为double,而在配置文件中type为java.lang.Double时,并不会自动拆装箱。

​ 若value中含有特殊字符,如<>时,可使用<![CDATA[]]>

<bean id="student1" class="com.nl.spring.bean.Student">
        <constructor-arg type="java.lang.String" value="0002"></constructor-arg>
        <constructor-arg type="java.lang.String" >
            <value><![CDATA[<gress>]]></value>
        </constructor-arg>
        <constructor-arg type="java.lang.Double" value="80"></constructor-arg>
</bean>
  1. 引用类型赋值

​ 当bean的属性含有引用类型时,可使用ref属性或ref元素:

<bean id="user1" class="com.nl.spring.bean.User">
        <property name="name" value="root"></property>
    	<!--引用外部bean
	  	<property name="student">
   			<ref bean="student1"></ref>
		</property>
		--> 
    	<!--内部bean。不能被外部引用
    	<property name="student">
            <bean class="com.nl.spring.bean.Student">
                <constructor-arg type="java.lang.String" value="00006"/>
                <constructor-arg type="java.lang.String" value="alice"/>
                <constructor-arg type="java.lang.Double" value="78"/>
            </bean>
        </property>
		-->
        <property name="student" ref="student1"/>
</bean>

​ 赋值为null

<bean id="user1" class="com.nl.spring.bean.User">
        <property name="name">
    		<value><null/></value>
    	</property>
       
</bean>

​ 级联属性赋值:

<constructor-arg ref="stu"></constructor-arg>
<!--级联属性赋值-->
<property name="stu.avgScore" value="89.0"></property>
  1. List属性赋值
 <property name="roles">
            <list>
                <value>student</value>
                <value>son</value>
            </list>
</property>
  1. Map属性赋值
<property name="bookInfo">
            <map>
                <entry key="《世纪》" value="89.5"/>
                <entry key="《外星人》" value="45.8"/>
            </map>
</property>
  1. Properties属性赋值
 <property name="properties">
            <props>
                <prop key="driver">com.mysql.jdbc.Driver</prop>
                <prop key="url">jdbc:mysql://localhost:3306/</prop>
                <prop key="user">root</prop>
                <prop key="password">123456</prop>
            </props>
</property>
  1. 将List等集合属性分离出来
 <util:list id="list">
        <value>teacher</value>
        <value>father</value>
</util:list>

​ 需导入util命名空间

<beans xmlns="http://www.springframework.org/schema/beans"       
       xmlns:util="http://www.springframework.org/schema/util" 
       http://www.springframework.org/schema/util
       https://www.springframework.org/schema/util/spring-util.xsd">
</beans>                                                                   
  1. 通过p命名空间对属性赋值
<bean id="pp" class="com.nl.spring.bean.Person" p:name="tom" p:roles-ref="list">

</bean>

​ 需导入p命名空间。

2.3自动装配(基于xml文件)

  • 使用:在<bean>的autowire属性指定自动装配的模式
  • byType(根据类型自动装配):若有多个相同类型时,则无法进行自动装配。
<bean id="p1" class="com.nl.spring.bean.autowire.Person" p:name="Bot"
            autowire="byType"></bean>
  • byName(根据名称自动装配):需目标bean的名称与属性名相同。
<bean id="person" class="com.nl.spring.bean.autowire.Person"
          p:name="Ancy" autowire="byName"></bean>

2.4通过调用静态工厂方法创建bean

  • 通过调用静态工厂方法创建bean,将对象的创建封装到静态方法中。
  • 在创建bean时,用class属性指定工厂类;factory-method指定获取bean的静态方法。若静态方法有参数,则使用constructor-arg子节点添加参数。
  • 具体实现:

工厂类:

package com.nl.spring.bean.bean_factory;

import java.util.HashMap;
import java.util.Map;

/**
 * 静态工厂方法:直接调用某一个类的静态方法返回bean实例
 */
public class StaticPersonFactory {
    private static Map<String,Person> personMap = new HashMap<>();

    static{
        personMap.put("zhangsan" ,new Person("zhangsan"));
        personMap.put("lisi" ,new Person("lisi"));
    }

    public static Person getPerson(String name){
        return personMap.get(name);
    }
}

配置文件:

<bean id="p1" class="com.nl.spring.bean.bean_factory.StaticPersonFactory"
        factory-method="getPerson">
            <constructor-arg value="zhangsan"></constructor-arg>
</bean>

2.5通过实例工厂方法创建bean

  • 将对象的创建封装到另外一个对象实例的方法中。
  • 具体实现:

工厂类:

package com.nl.spring.bean.bean_factory;

import java.util.HashMap;
import java.util.Map;

/**
 * 实例工厂方法:先创建工厂,再调用工厂的实例方法
 */
public class InstancePersonFactory {
    private Map<String,Person> personMap = null;
    public InstancePersonFactory(){
        personMap  = new HashMap<String, Person>();
        personMap.put("zhangsan",new Person("zhangsan"));
        personMap.put("wangwu",new Person("wangwu"));
    }

    public Person getPerson(String name){
        return personMap.get(name);
    }
}

配置文件:

<!--    配置工厂实例-->
<bean id="personFactory" class="com.nl.spring.bean.bean_factory.InstancePersonFactory"></bean>
<!--    通过实例工厂方法来配置person-->
<!--    factory-bean:实例工厂bean-->
<!--    factory-method:实例工厂方法-->
        <bean id="p2" factory-bean="personFactory" factory-method="getPerson">
            <constructor-arg value="wangwu"></constructor-arg>
        </bean>

2.6通过FactoryBean配置bean

具体实现:

自定义的FactoryBean:

package com.nl.spring.bean.factorybean;

import org.springframework.beans.factory.FactoryBean;
//自定义的FactoryBean需要实现FactoryBean接口
public class PersonFactoryBean implements FactoryBean<Person> {
    private String name;

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

    //返回bean对象
    @Override
    public Person getObject() throws Exception {
        return new Person("Tom");
    }

    //返回bean的类型
    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}

配置文件:

<!--       通过FactoryBean来配置bean-->
<!--        class:FactoryBean的全类名-->
<!--        property:FactoryBean的属性-->

<!--        实际返回的是FactoryBean类的getObject()方法得到的实例-->
<bean id="person" class="com.nl.spring.bean.factorybean.PersonFactoryBean">
    <property name="name" value="Tom"></property>
</bean>

2.7 基于注解配置bean

在classpath下扫描组件

  • 组件扫描(component scanning):Spring能从classpath下自动扫描,实例化具有特定注解的组件。

  • 特定组件:

    - @Component Spring管理的组件。

    -  @Repository 持久层组件

    - @Service 业务层组件

    - @Controller 表现层组件

  • 对于扫描到的组件命名规范:使用类名,且第一个字母小写。也可通过注解的value属性标识组件名称。

  • 使用注解后,还需在配置文件中配置<context:component-scan>节点,配置该节点需要导入context命名空间。

    base-package属性指定需要扫描的基类包,Spring会自动扫描该包及子包的所有类。

    有多个包需要扫描时,可用逗号分割。

    resource-pattern属性指定扫描的资源。

    <ontext:include-filter>子节点表示要包含的目标类。

    <context:exclude-filter>子节点表示排除在外的目标类。

<!--    resource-pattern指定扫描的资源-->
<context:component-scan
            base-package="com.nl.spring.bean.annotation"
            resource-pattern="repository/*.class">
</context:component-scan>
  1. type属性为annotation
<!--    exclude-file节点排除哪些指定表达式的组件-->
<context:component-scan base-package="com.nl.spring.bean.annotation">
    <context:exclude-filter type="annotation" 
           expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

<!--    include-filter节点表示包含哪些指定组件,需配合use-default-filters属性使用-->
<context:component-scan base-package="com.nl.spring.bean.annotation"
     use-default-filters="false">
        <context:include-filter type="annotation" 
             expression="org.springframework.stereotype.Repository"/>
 </context:component-scan>
  1. type属性为assignable
<!--    不包含指定接口和实现类-->
<context:component-scan base-package="com.nl.spring.bean.annotation">
    <context:exclude-filter type="assignable" 
         expression="com.nl.spring.bean.annotation.repository.AnimalRepository"/>
</context:component-scan>

<context:component-scan base-package="com.nl.spring.bean.annotation" 
                        use-default-filters="false">
   <context:include-filter type="assignable"                                              
        expression="com.nl.spring.bean.annotation.repository.AnimalRepository"/>
</context:component-scan>

组件装配

  • <context:component-scan>元素会自动注册AutowiredAnnotationBeanPostProcessor实例,该实例可以自动装配具有@Autowired@Resource注解的属性。

使用@Autowired自动装配bean

  1. 该注解可在属性或者setXxx()方法上。
@Autowired
private UserDao userDao;
  1. 若某个字段有多个实现类时,可配合@Qualifier("")使用,括号内即为指定实例bean。
@Repository("userDao1")
public class UserDaoImpl1 implements UserDao {}
@Repository
public class UserDaoImpl implements UserDao {}
@Service
public class UserServiceImpl implements UserService {
    @Autowired
    @Qualifier("userDao1")
    private UserDao userDao;
}
  1. @Autowired注解中若required属性为false,则表明该字段为null值。也可直接使用@Nullable注解。
@Service
public class UserServiceImpl implements UserService {
    @Autowired(required=false)
    private UserDao userDao;
}

使用@Resource进行自动装配

  • 该注解相当于@Autowired@Qualifier联合使用,若要指定实例bean可通过name属性。
@Resource(name = "userDao1")
private UserDao userDao;

2.9 bean之间的关系(继承、依赖)

  • 继承
 <bean id="addr1" class="com.nl.spring.bean.bean_relation.Address"
            p:city="Beijing" p:road="Zhangshan"></bean>

<!--    <bean id="addr2" class=" com.nl.spring.bean.bean_relation.Address"-->
<!--            p:city="Guangzhou" p:road="Zhangshan"></bean>-->
<!--    bean配置的继承:使用bean的parent属性指定继承哪个bean的配置-->
    <bean id="addr2" parent="addr1" p:city="Guangzhou"></bean>
  • 抽象bean
<!--    抽象bean:bean的abstract属性为true的bean,这样的bean不能被容器实例化,只能用来继承。autowire属性不会被继承-->
<!--    若某一个bean没有指定class属性,则该bean必须是一个抽象bean-->
    <bean id="addr" class="com.nl.spring.bean.bean_relation.Address"
          p:city="Lijiang" p:road="souki" abstract="true"></bean>
  • 依赖

2.10 bean的作用范围

​ 使用bean的scope属性配置bean的作用范围。

​ singleton:默认值。容器初始化时,创建实例。在整个生命周期内只创建一次。单例的。

​ prototype:原型。容器初始化时不创建该实例。在获取bean实例时,会创建一个新的bean实例。

  • singleton(单例)
<bean id="addr" class="com.nl.spring.bean.bean_scope.Address"
            p:city="Qingdao" p:road="xxx" scope="singleton"></bean>

​ 每次从IOC容器中获取的都是同一个bean。

  • prototype(原型)
<bean id="addr" class="com.nl.spring.bean.bean_scope.Address"
            p:city="Qingdao" p:road="xxx" scope="prototype"></bean>

​ 每次都会产生一个新的bean实例。

2.11 外部属性文件

​ 外部属性文件db.properties:

driverclass=com.mysql.jdbc.Driver
jdbcurl=jdbc:mysql://localhost:3306/test
user=root
password=123450

​ xml配置文件:

<!--    导入属性文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <bean id="dataSource"  class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--        使用外部文件的属性:-->
        <property name="driverClass" value="${driverclass}"></property>
        <property name="jdbcUrl" value="${jdbcurl}"></property>
        <property name="user" value="${user}"></property>
        <property name="password" value="${password}"></property>
    </bean>

2.12 Spring表达式语言(SpEL)

  • 字面值赋值
<bean id="addr" class="com.nl.spring.bean.spel.Address">
<!--            1.使用spel为字面值赋值-->
            <property name="city" value="#{'Beijing'}"></property>
            <property name="road" value="Wangfujin"></property>
</bean>
  • 引用类的静态属性
<bean id="car" class="com.nl.spring.bean.spel.Car">
        <property name="brand" value="baoma"></property>
        <property name="price" value="200000"></property>
        <property name="typePerimeter" value="#{T(java.lang.Math).PI * 80}"></property>
    </bean>
  • 引用其他bean及属性
 <bean id="person" class="com.nl.spring.bean.spel.Person">
        <property name="name" value="zhangdan"></property>
        <property name="car" value="#{car}"></property>
<!--        引用其他bean的属性-->
        <property name="address" value="#{addr.city}"></property>
    </bean>

3.bean的生命周期

3.1bean生命周期的流程

  • 通过构造器或工厂方法创建bean实例
  • 调用bean的初始化方法
  • 使用bean
  • 关闭容器,调用bean的销毁方法

在bean的声明中设置init-methoddestory-method属性,为bean指定初始化方法和销毁方法。

<bean id="car" class="com.nl.spring.bean.bean_cycle.Car" 
      init-method="init" destroy-method="destory">
        <property name="brand" value="baoma"></property>
</bean>

3.2创建bean后置处理器

  • bean后置处理器允许在调用初始化方法前后对bean进行额外的处理。
  • bean后置处理器对IOC容器里的所有bean实例逐一处理。
  • 需实现org.springframework.beans.factory.config.BeanPostProcessor接口。在初始化方法被调用前后,Spring会把bean实例传递给该接口的如下两个方法中:
@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

具体实现

Car类:

package com.nl.spring.bean.bean_cycle;

/**
 * bean的生命周期
 */
public class Car {
    private String brand;

    public Car() {
        System.out.println("Car constructor...");
    }

    public void setBrand(String brand) {
        System.out.println("setBrand()");
        this.brand = brand;
    }
    public void init(){
        System.out.println("init...");
    }
    public void destory(){
        System.out.println("destory");
    }

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

后置处理器类:

package com.nl.spring.bean.bean_cycle;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessAfterInitialization:" + bean + "," +beanName);
        return bean;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("postProcessBeforeInitialization:" + bean + "," +beanName);
        return bean;
    }
}

可以在两个方法中修改bean,或是返回一个新的bean。

自定义后置处理器的配置:

<bean class="com.nl.spring.bean.bean_cycle.MyBeanPostProcessor"></bean>

无需配置id,IOC会自动识别

测试类:

package com.nl.spring.bean.bean_cycle;

import org.junit.Test;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class CarTest implements BeanPostProcessor {
    @Test
    public void beanCycleTest(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean_cycle.xml");
        Car car = context.getBean("car", Car.class);
        System.out.println(car);
        //关闭IOC容器
        context.close();
        Car constructor...
        //执行结果:
		//setBrand()
		//postProcessBeforeInitialization:Car{brand='baoma'},car
		//init...
		//postProcessAfterInitialization:Car{brand='baoma'},car
		//Car{brand='baoma'}
		//destory
    }
}