spring配置文件中加载和读取外部属性文件
如何在spring配置文件中加载并读取外部的属性文件
使用外部属性文件,配置最多的就是数据源信息
<context:property-placeholder location="classpath:out.properties"/>
<bean id="person" class="com.wanbang.outconf.Person" p:age="${age}" p:name="${name}">
</bean>
在conf类路径目录下new个普通File文件重命名为out.properties
out.properties文件中的中文会自动变为其它格式但不影响正常使用
age=20
name=\u8001\u59DC
我们通过数据库连接池的概念来讲解外部属性文件,
连接池的概念:在一个容器中预先创建好指定个数的数据库连接对象,在进行增删改查操作的时候,
从这个容器中拿出一个数据库连接对象来使用,完成操作之后,将数据库连接对象再还回到容器中,
这个容器就叫做数据库连接池。
目前来说,最常用的数据库连接池有两个:
- C3P0【比阿里的Druid要更早出现,现在使用比较少】
- 阿里的Druid【全世界都在用,而且现在是最流行的】
今天使用C3P0,后面我们在讲解SpringBoot 和 Cloud的时候使用阿里的Druid。
步骤一:导入jar文件
c3p0-0.9.1.2.jar
mysql-connector-java-5.1.37-bin.jar
步骤二:在SpringIOC容器xml配置文件中,配置连接池的bean
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 属性注入驱动程序类名 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<!-- 属性注入 数据库连接地址 -->
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/fornum"></property>
<!-- 属性注入 数据库用户名-->
<property name="user" value="root"></property>
<!-- 属性注入 数据库密码 -->
<property name="password" value="3306"></property>
<!-- 属性注入 连接池中最大的连接数量 -->
<property name="maxPoolSize" value="10"></property>
<!-- 属性注入 连接池初始化的连接数量 -->
<property name="initialPoolSize" value="5"></property>
</bean>
步骤三:从连接池中获得数据库连接
public static void main(String[] args) throws SQLException {
ApplicationContext act = new ClassPathXmlApplicationContext("db.xml");
//DataSource 是 ComboPooledDataSource 的实现的子接口,记住是sql包下的DataSource
DataSource dataSource = (DataSource) act.getBean("dataSource");
Connection conn = dataSource.getConnection();//获取数据库连接的另外一种形式
System.out.println(conn);
}
现在的程序已经可以获取到数据库连接,但是数据库的连接信息我们是直接配置在xml中,这种是不提倡的,
一般的开发都会将数据库的连接信息放置在外部的配置文件中。
使用外部属性文件步骤:
- 创建外部属性文件db.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/forum
user=root
password=3306
maxSize=10
initSize=5
- 在Spring配置文件中引入外部属性文件
<context:property-placeholder location=“classpath:db.properties”/>
<!-- 引入外部属性文件,有两种方式:
- 新的写法:在beans标签中使用使用context命名空间 ,context:property-placeholder标签
- 老的写法:配置一个bean,定义外部文件的bean
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location" value="classpath:db.properties"></property>
</bean>
-->
<context:property-placeholder location="classpath:db.properties"/>
- 在bean中使用外部属性文件的配置
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 属性注入驱动程序类名 -->
<property name="driverClass" value="${driver}"></property>
<!-- 属性注入 数据库连接地址 -->
<property name="jdbcUrl" value="${url}"></property>
<!-- 属性注入 数据库用户名-->
<property name="user" value="${user}"></property>
<!-- 属性注入 数据库密码 -->
<property name="password" value="${password}"></property>
<!-- 属性注入 连接池中最大的连接数量 -->
<property name="maxPoolSize" value="${maxSize}"></property>
<!-- 属性注入 连接池初始化的连接数量 -->
<property name="initialPoolSize" value="${initSize}"></property>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driver}"></property>
<property name="jdbcUrl" value="${url}"></property>
<property name="user" value="${user}"></property>
<property name="password" value="${password}"></property>
<property name="maxPoolSize" value="${maxSize}"></property>
<property name="initialPoolSize" value="${initSize}"></property>
</bean>
<context:property-placeholder location="classpath:db.properties"/>
以后的开发中,我们获取数据库连接的方式都是使用此种形式,肯定是不会使用原生的JDBC完成的,
不管以后我们开发使用何种数据库操作的框架[Hibernate,SpringData+JPA,MyBatis],
都是使用连接池技术完成数据库连接的。
总结
属性文件的读取必须先导入属性文件
读取属性文件内容是以el表达式的方式
我们要了解连接池的配置,能够完成数据库连接操作
SpringEL表达式
SpringEL表达式的简介
Spring的表达式语言,简称SpEL,是一个支持运行时检查和操作对象的强大的表达式语言,和我们之前学习过的JSP中的EL表达式类似,SPEL使用的是#{}作为定界符,所有在大括号中的字符都被认为是SPEL,
SPEL为bean的属性进行动态赋值提供了非常大的便利,通过SPEL可以实现:
- 通过bean的ID对bean进行引用,类似于 ref标签,比ref更强大
- 可以调用方法以及引用对象的属性
- 可以进行计算(数学运算、比较运算、逻辑运算、三目运算等)
- 支持正则表达式
体验SPEL
<bean id="car" class="com.wanbangee.spel.Car">
<property name="brand" value="Audi"></property>
<property name="color" value="red"></property>
<property name="maxSpeed" value="200"></property>
<property name="price" value="300000"></property>
<!-- 通过SPEL表达式可以获得类中的常量 -->
<property name="perimeter" value="#{T(java.lang.Math).PI*60}"></property>
</bean>
<bean id="person" class="com.wanbangee.spel.Person">
<property name="car" ref="car2"></property>
<property name="name" value="zwj"></property>
<!-- 使用了三目运算符和 关系运算 -->
<property name="level" value="#{car2.price >= 300000?'金领':'白领'}"></property>
</bean>
//外面用了双引号里面就用单引号,反之亦反。
SpEL的使用
- SPEL表达式可以使用字面量(意义不大),字面量仅限于字符串、基本数据类型
<!-- SPEL使用字面量 外面用了双引号里面就用单引号,反之亦反。-->
<bean id="car3" class="com.wanbangee.spel.Car">
<property name="brand" value="#{'JETU'}"></property>
<property name="color" value='#{"red"}'></property>
<property name="maxSpeed" value="#{200}"></property>
<property name="price" value="#{100000.0}"></property>
<!-- 通过SPEL表达式可以获得类中的常量 -->
<property name="perimeter" value="#{T(java.lang.Math).PI*60}"></property>
<property name="car" ref="car3"></property>
</bean>
- 引用bean、属性、方法
<bean id="person" class="com.wanbangee.spel.Person">
<!-- SpringEL 表达式引用bean 相当于ref属性-->
<property name="car" value="#{car3}"></property>
<!-- SpringEL 表达式引用bean的属性 -->
<!-- <property name="name" value="#{car3.brand}"></property> -->
<!-- SpringEL 表达式引用bean的方法(非私有),还可以链式操作 -->
<property name="name" value="#{car3.getBrand().toLowerCase()}"></property>
<!-- 使用了三目运算符和 关系运算 -->
<property name="level" value="#{car2.price >= 300000?'金领':'白领'}"></property>
</bean>
- SPEL支持数学运算
加减乘除取模
- SPEL支持关系运算
< 、>、 <=、 >=、 ==、 != 所有的关系运算结果 是boolean类型
- SPEL支持逻辑运算:xml中支持使用&& 表示并且,||表示或者,!表示非,但是不推荐使用,xml中推荐使用效果相同的and,or,not
and(&&) 表示并且,连接多个boolean类型的运算符
or(||) 表示或者,连接多个boolean类型的运算符
not(!) 表示非,后面跟上一个boolean类型的运算符
- SPEL支持三目运算符
- SPLE支持正则表达式
<!-- 使用正则判断 -->
<property name="email" value="#{car3.getBrand() matches '\w{3,5}@\w{3,8}.(com|cn)'}"> </property>
- SPEL支持调用静态属性(上面用到的PI)和静态方法(四舍五入方round(100000.534))
value="#{T(java.lang.Math).PI*60
<property name="price" value="#{T(java.lang.Math).round(100000.53452345234)}"></property>
SpringEL表达式操作类似于jsp中的EL表达式,使用#{}作为定界符,可以进行一些常用的操作。
IOC容器中Bean的生命周期
我们之前讲解过Servlet生命周期,生命周期就是从初始化到销毁的过程,那么我们SpringIOC容器是用来管理bean的,
所以又叫做Bean容器或者IOC容器,既然bean是在容器中管理的,那么肯定在容器中存在生命周期的过程。
刚刚我们看到了bean的实例化和bean的设置属性到bean的使用,但是这些过程不能够完全的表示bean的生命周期,
在Spring中,可以配置bean的初始化和销毁操作。
package com.wanbangee.life;
public class Person {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
System.out.println("set属性注入");
}
public Person() {
System.out.println("调用无参构造器");
}
@Override
public String toString() {
return "Person [name=" + name + "]";
}
public void init() {
System.out.println("bean初始化");
}
public void destroy() {
System.out.println("bean销毁");
}
}
<!--
init-method : 配置bean的初始化方法
destroy-method :配置bean的销毁方法
-->
<bean id="person" class="com.wanbangee.life.Person" init-method="init"
destroy-method="destroy">
<property name="name" value="牛魔王"></property>
</bean>
//1 创建IOC容器并获得IOC容器对象
ConfigurableApplicationContext act = new ClassPathXmlApplicationContext("life.xml");
//2 获得IOC容器中管理的bean
Person person = (Person)act.getBean("person");
//3 使用bean
System.out.println(person);
//4 关闭容器,此接口ConfigurableApplicationContext有关闭容器的close方法
//接口ConfigurableApplicationContext是接口ApplicationContext的子接口
act.close();
我们现在可以完整的通过程序运行观察bean的生命周期了:
- IOC容器初始化时,bean被实例化创建 ,调用无参构造器
- 调用bean的setter方法进行属性设置
- bean初始化
- bean可以正常使用了(打印bean对象,Person [name=牛魔王])
- bean销毁(IOC容器关闭的时候bean进行销毁)
为了更加细致的观察bean的生命周期,我们可以使用bean的后置处理器。
bean的后置处理器
BeanPostProcessor这个接口表示的是bean的后置处理器,里面有两个方法:
Ctrl+Shift+t查看BeanPostProcessor这个接口
Object postProcessBeforeInitialization(Object bean, String beanName)throws BeansException
表示在bean初始化方法之前执行
Object postProcessAfterInitialization(Object bean, String beanName)throws BeansException
表示在bean初始化方法之后执行
object : 是IOC容器中管理的bean的对象
beanName : 是IOC容器中管理的bean的名称
实现bean的后置处理器:
- 定义实现类,实现BeanPostProcessor接口,并且复写两个方法
package com.wanbangee.life;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class MyBeanPostProcessor implements BeanPostProcessor {
//postProcessBeforeInitialization : 在bean初始化方法之前执行
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行bean后置处理器的前置方法.....");
return bean;
}
//postProcessAfterInitialization : 在bean初始化方法之后执行
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行bean后置处理器的后置方法.....");
return bean;
}
}
编写好了之后我们需要进行配置
- 配置bean的后置处理器:当做普通的bean一样配置到IOC容器中
<!-- 后置处理器和普通bean一样配置到IOC容器中
beanPostProcessor对象名,com.wanbangee.life.MyBeanPostProcessor接口的实现类-->
<bean id="beanPostProcessor" class="com.wanbangee.life.MyBeanPostProcessor"></bean>
这个时候bean的后置处理器就可以使用了,测试一下,这个时候,bean的生命周期变成了:
- bean实例化
- 调用bean的setter方法
- 执行bean后置处理器的前置方法
- bean初始化
- 执行bean后置处理器的后置方法
- bean可以使用了
- bean销毁(容器关闭的时候bean进行销毁)
这个时候肯定会问:bean的后置处理器有啥作用?
- bean后置处理器的前置和后置方法可以获得bean的ID和bean的信息
- bean的后置处理器的后置方法返回的bean就是IOC容器中管理的bean,我们可以偷偷的将bean换掉
public class MyBeanPostProcessor implements BeanPostProcessor {
/**
postProcessBeforeInitialization : 在bean初始化方法之前执行。
String beanName获取bean就是bean ID。
Object bean就是获取bean信息如控制台打印的Person [name=牛魔王]
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行bean后置处理器的前置方法....." + beanName);
System.out.println("bean..........." + bean);
return bean;
}
/**
postProcessAfterInitialization : 在bean初始化方法之后执行
返回值即是IOC容器中管理的bean
*/
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("执行bean后置处理器的后置方法....." + beanName);
System.out.println("bean..........." + bean);
Person person = new Person();
//这里的person就是个用来接收数据的参数
person.setName("铁扇公主");
return person;
}
}
既然可以偷梁换柱了,我们就可以将bean进行一定的过滤:如果bean的name属性值为“狗蛋”,我们可以换成“Gog egg”
public class MyBeanPostProcessor implements BeanPostProcessor {
//postProcessBeforeInitialization : 在bean初始化方法之前执行
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// postProcessAfterInitialization : 在bean初始化方法之后执行
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//返回值即是IOC容器中管理的bean
Person person = (Person) bean;
if(person.getName().equals("狗蛋")) {
person.setName("Gog egg");
}
return person;
}
}
对于Bean的声明周期只需要了解一下,后期的开发中基本上不会使用
工厂方法和FactoryBean
在以后的开发中,最常用的配置bean的方式,就是通过全类名反射的方式,
这一章节内容工厂方法和FactoryBean,大家只需要了解。
静态工厂方式
通过静态工厂方式创建bean指的是:
- 调用静态方式方法创建bean是将对象的创建的过程封装到静态方法中,
当客户端需要对象时,只需要简单的调用静态方法,而不关系对象创建的细节。
- 要声明通过静态方式创建bean,需要在bean的class属性中指拥有该工厂的方式的类,
同时在Factory-method属性中,指定工厂方法名称, 最后使用<constructor-arg>标签为方法传递参数。
步骤一:新建类Car,brand,color,price
步骤二:创建静态工厂类,提供静态属性和静态块
package com.wanbangee.factorymehodandfactorybean;
import java.util.HashMap;
import java.util.Map;
public class StaticFactory {
//定义静态属性Map
private static Map<String,Car> cars = new HashMap<>();
//定义静态块
static {
cars.put("小一", new Car("Bnze","white",500000.0));
cars.put("小二", new Car("BMW","black",500000.0));
cars.put("小三", new Car("Audi","red",500000.0));
}
//定义静态方法:通过key取得Car对象
public static Car getCar(String key) {
return cars.get(key);
}
}
步骤三:配置bean
<?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,需要在bean的class属性中指拥有该工厂的方式的类,
同时在factory-method属性中,指定工厂方法名称, 最后使用<constructor-arg>元素为方法传递参数
id:配置的bean实际上就是我们要获取的bean
class:注意配置的不是要获取的bean的全类名,而是工厂方式的全类名
factory-method:配置的是静态工厂方法名称,即获取bean的方法名称
constructor-arg:如果静态工厂方法有入参,则使用此标签传递
-->
<bean id="car" class="com.wanbangee.factorymehodandfactorybean.StaticFactory" factory-method="getCar">
<constructor-arg value="小三"></constructor-arg>
</bean>
</beans>
实例工厂方式
通过实例工厂方法创建bean,是将对象的创建过程封装到另外一个对象的实例【构造方法】方法中去,
当客户需要请求对象时,只需要简单的配置实例方法而不用去关心对象创建的细节。
步骤一:新建类Car,brand,color,price
步骤二:创建实例工厂
package com.wanbangee.factorymehodandfactorybean;
import java.util.HashMap;
import java.util.Map;
public class InstanceFactory {
private Map<String,Car> cars = null;
public InstanceFactory() {
cars = new HashMap<>();
cars.put("小一", new Car("Bnze","white",500000.0));
cars.put("小二", new Car("BMW","black",500000.0));
cars.put("小三", new Car("Audi","red",500000.0));
}
public Car getCar(String key) {
return cars.get(key);
}
}
步骤三:配置
<!--
实例工厂本身是需要配置到IOC容器中
factory-bean : 引用实例工厂的bean
factory-method:配置的是实例工厂中获取bean的方法名称
constructor-arg 标签: 如果实例工厂方法中获取bean的名称的方法有参数,则使用此标签传递
-->
<bean id="instanceFactory" class="com.wanbangee.factorymehodandfactorybean.InstanceFactory"></bean>
<bean id="car2" factory-bean="instanceFactory" factory-method="getCar">
<constructor-arg value="小二"></constructor-arg>
</bean>
FactoryBean bean工厂
实现FactoryBean接口在SpringIOC容器中的配置:
- Spring中有两种类型的bean,一种就是普通bean,另外一种就是工厂bean,即FactoryBean
- 工厂Bean跟普通Bean不同,其返回的对象不是指定类的一个实例,
其返回的是该工厂Bean的getObject()方法所返回的对象。
步骤一:创建FactoryBean的实现类,目前我们需要管理的Bean的类型是Car,所以要指定泛型为Car
package com.wanbangee.factorymehodandfactorybean;
import org.springframework.beans.factory.FactoryBean;
public class CarFactoryBean implements FactoryBean<Car> {
//返回的对象即是IOC容器中管理的Bean
@Override
public Car getObject() throws Exception {
return new Car("Chery","god",50000.0);
}
//返回的是bean的类型
@Override
public Class<?> getObjectType() {
return Car.class;
}
// 返回的bean是否是单例的bean
@Override
public boolean isSingleton() {
return true;
}
}
步骤二:配置
<!-- FactoryBean配置-->
<bean id="car3" class="com.wanbangee.factorymehodandfactorybean.CarFactoryBean">
</bean>
获取Bean的四种配置方式:
<!-- 第一种配置bean的方式:通过全类名反射-->
<bean id="car1" class="com.wanbangee.factorymehodandfactorybean.Car">
<property name="brand" value="Ford"></property>
<property name="color" value="Green"></property>
<property name="price" value="100000"></property>
</bean>
<!-- 第二种配置bean的方式:静态工厂方法
要声明通过静态方式创建bean,需要在bean的class属性中指拥有该工厂的方式的类,
同时在factory-method属性中,指定工厂方法名称, 最后使用<constructor-arg>元素为方法传递参数
id:配置的bean实际上就是我们要获取的bean
class:注意配置的不是要获取的bean的全类名,而是工厂方式的全类名
factory-method:配置的是静态工厂方法名称,即获取bean的方法名称
constructor-arg:如果静态工厂方法有入参,则使用此标签传递
-->
<bean id="car" class="com.wanbangee.factorymehodandfactorybean.StaticFactory" factory-method="getCar">
<constructor-arg value="小三"></constructor-arg>
</bean>
<!-- 第三种配置bean的方式:实例工厂方法
实例工厂本身是需要配置到IOC容器中
factory-bean : 引用实例工厂的bean
factory-method:配置的是实例工厂中获取bean的方法名称
constructor-arg 标签: 如果实例工厂方法中获取bean的名称的方法有参数,则使用此标签传递
-->
<bean id="instanceFactory" class="com.wanbangee.factorymehodandfactorybean.InstanceFactory"></bean>
<bean id="car2" factory-bean="instanceFactory" factory-method="getCar">
<constructor-arg value="小二"></constructor-arg>
</bean>
<!-- 第四种配置bean的方式:FactoryBean配置-->
<bean id="car3" class="com.wanbangee.factorymehodandfactorybean.CarFactoryBean">
</bean>
虽然现在有4种Bean的获取方式,但是第一种肯定是最常用,也是最好用的。
就是之前一直用的最常用的配置bean的方式,就是通过全类名反射的方式。