Spring资料
转载自以下博客:
20170531 spring配合xml详解http://blog.csdn.net/zzjjiandan/article/details/22922847/-------------------------------------
Spring property-placeholder相关
1 Spring中property-placeholder的使用与解析 http://www.cnblogs.com/leftthen/p/5615066.html
2 Spring property文件配置方法以及如何与工程分离 http://www.cnblogs.com/pekkle/p/6568751.html
3 Spring基础—— 在 Spring Config 中使用外部属性文件 http://www.cnblogs.com/solverpeng/p/5681839.html
4 spring c3p0数据源配置 http://www.cnblogs.com/cxyzl/archive/2012/07/09/2582428.html
--java知识点---
浅析Java中的final关键字 http://www.cnblogs.com/dolphin0520/p/3736238.html
Java内部类详解 http://www.cnblogs.com/dolphin0520/p/3736238.html
spring 3.0 应用springmvc 构造RESTful URL 详细讲解 (转载)
http://www.cnblogs.com/zzjjian333/archive/2012/11/14/rest1.html
(转载自:http://blog.csdn.net/yczz/article/details/5905893)
SpringMVC介绍之视图解析器ViewResolver http://www.cnblogs.com/zhangshitong/p/5782689.html
(mvc-dispatcher-servlet.xml视图解析器配置)
==========================================================
Spring中property-placeholder的使用与解析
我们在基于spring开发应用的时候,一般都会将数据库的配置放置在properties文件中.
代码分析的时候,涉及的知识点概要:
- NamespaceHandler 解析xml配置文件中的自定义命名空间
- ContextNamespaceHandler 上下文相关的解析器,这边定义了具体如何解析property-placeholder的解析器
- BeanDefinitionParser 解析bean definition的接口
- BeanFactoryPostProcessor 加载好bean definition后可以对其进行修改
- PropertySourcesPlaceholderConfigurer 处理bean definition 中的占位符
我们先来看看具体的使用吧
property的使用
在xml文件中配置properties文件
<?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-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<context:property-placeholder location="classpath:foo.properties" />
</beans>
这样/src/main/resources/foo.properties文件就会被spring加载
如果想使用多个配置文件,可以添加order字段来进行排序
使用PropertySource注解配置
Spring3.1添加了@PropertySource注解,方便添加property文件到环境.
@Configuration
@PropertySource("classpath:foo.properties")
public class PropertiesWithJavaConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
properties的注入与使用
-
@Value( "${jdbc.url}" ) private String jdbcUrl;
还可以添加一个默认值
@Value( "${jdbc.url:aDefaultUrl}" )
private String jdbcUrl;
-
在Spring的xml配置文件中获取
<bean id="dataSource"> <property name="url" value="${jdbc.url}" /> </bean>
源码解析
properties配置信息的加载
Spring在启动时会通过AbstractApplicationContext#refresh启动容器初始化工作,期间会委托loadBeanDefinitions解析xml配置文件.
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
loadBeanDefinitions通过层层委托,找到DefaultBeanDefinitionDocumentReader#parseBeanDefinition解析具体的bean
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
这边由于不是标准类定义,所以委托BeanDefinitionParserDelegate解析
通过NamespaceHandler查找到对应的处理器是ContextNamespaceHandler,再通过id找到PropertyPlaceholderBeanDefinitionParser解析器解析
@Override
public void init() {
// 这就是我们要找的解析器
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
PropertyPlaceholderBeanDefinitionParser是这一轮代码分析的重点.
我们来看看它的父类吧.
-
BeanDefinitionParser
被DefaultBeanDefinitionDocumentReader用于解析个性化的标签
这边只定义了一个解析Element的parse apipublic interface BeanDefinitionParser { BeanDefinition parse(Element element, ParserContext parserContext); }
- AbstractBeanDefinitionParser
BeanDefinitionParser接口的默认抽象实现.spring的拿手好戏,这边提供了很多方便使用的api,并使用模板方法设计模式给子类提供自定义实现的钩子
我们来看看parse时具体的处理逻辑把:- 调用钩子parseInternal解析
- 生成bean id,使用BeanNameGenerator生成,或者直接读取id属性
- 处理name 与别名aliases
- 往容器中注册bean
- 进行事件触发
-
AbstractSingleBeanDefinitionParser
解析,定义单个BeanDefinition的抽象父类
在parseInternal中,解析出parentName,beanClass,source;并使用BeanDefinitionBuilder进行封装 -
AbstractPropertyLoadingBeanDefinitionParser
解析property相关的属性,如location,properties-ref,file-encoding,order等 -
PropertyPlaceholderBeanDefinitionParser
这边处理的事情不多,就是设置ingore-unresolvable和system-properties-mode
properties文件的加载,bean的实例化
接下来,我们再看看这个bean是在什么时候实例化的,一般类的实例化有2种,一种是单例系统启动就实例化;一种是非单例(或者单例懒加载)在getBean时实例化.
这边的触发却是通过BeanFcatoryPostProcessor.
BeanFactoryPostProcessor是在bean实例化前,修改bean definition的,比如bean definition中的占位符就是这边解决的,而我们现在使用的properties也是这边解决的.
这个是通过PostProcessorRegistrationDelegate#invokeBeanFactoryPostProcessors实现的.
扫描容器中的BeanFactoryPostProcessor,找到了这边需要的PropertySourcesPlaceholderConfigurer,并通过容器的getBean实例化
protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, getBeanFactoryPostProcessors());
}
PropertySourcesPlaceholderConfigurer实例化完成后,就直接进行触发,并加载信息
OrderComparator.sort(priorityOrderedPostProcessors);
invokeBeanFactoryPostProcessors(priorityOrderedPostProcessors, beanFactory);
我们再来看看PropertySourcesPlaceholderConfigurer的继承体系把
-
BeanFactoryPostProcessor
定义一个用于修改容器中bean definition的属性的接口.其实现类在一般类使用前先实例化,并对其他类的属性进行修改.
这跟BeanPostProcessor有明显的区别,BeanPostProcessor是修改bean实例的. -
PropertiesLoaderSupport
加载properties文件的抽象类.
这边具体的加载逻辑是委托PropertiesLoaderUtils#fillProperties实现 -
PropertyResourceConfigurer
bean definition中占位符的替换就是这个抽象类实现的.
实现BeanFactoryPostProcessor#postProcessBeanFactory,迭代容器的中的类定义,进行修改
具体如何修改就通过钩子processProperties交由子类实现 -
PlaceholderConfigurerSupport
使用visitor设计模式,通过BeanDefinitionVisitor和StringValueResolver更新属性
StringValueResolver是一个转化String类型数据的接口,真正更新属性的api实现竟然是在PropertyPlaceholderHelper#parseStringValue -
PropertySourcesPlaceholderConfigurer
覆写postProcessorBeanFactory api定义解析流程
==========================================================
2 Spring property文件配置方法以及如何与工程分离
工程中难免出现一些需要每次部署都需要配置的参数,如数据源连接参数等,测试环境跟实际运行环境是不一样的。
使用spring框架的话,这些参数可能独立分布在不同的springContex配置文件里面。
可以考虑将这些参数独立到一个配置文件并可以让spring方便加载注入。可选的一个方案是使用java的property文件,将所有的配置参数都写到property文件里面,使用${key}来在spring配置文件里面得到这个参数。
例子:
property文件global-config-file.properties:
- #FOR dataSource
- jdbc.dataSource.url=jdbc:postgresql://192.168.1.118:5432/DB_name
- jdbc.dataSource.username=postgres
- jdbc.dataSource.password=123
#FOR dataSource jdbc.dataSource.url=jdbc:postgresql://192.168.1.118:5432/DB_name jdbc.dataSource.username=postgres jdbc.dataSource.password=123
示例配置的是数据源参数。
之后在springContext的配置文件中,加入下面代码,:
- <bean id="propertyConfigurer"
- class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="ignoreUnresolvablePlaceholders" value="true" />
- <property name="location" value="classpath:global-config-file.properties"/>
- </bean>
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="location" value="classpath:global-config-file.properties"/> </bean>
即引入PropertyPlaceholderConfigurer来读取property配置文件,spring框架会从其那里获取到需要的配置参数。
之后再用${key}的格式取代你数据源配置参数:
- <bean id="myBatisDataSource" class="org.apache.commons.dbcp.BasicDataSource">
- <property name="driverClassName" value="org.postgresql.Driver">
- </property>
- <property name="url"
- value="${jdbc.dataSource.url}">
- </property>
- <property name="username" value="${jdbc.dataSource.username}"></property>
- <property name="password" value="${jdbc.dataSource.password}"></property>
- </bean>
<bean id="myBatisDataSource" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="org.postgresql.Driver"> </property> <property name="url" value="${jdbc.dataSource.url}"> </property> <property name="username" value="${jdbc.dataSource.username}"></property> <property name="password" value="${jdbc.dataSource.password}"></property> </bean>
例如:${jdbc.dataSourcurl}:框架会将global-config-file.properties读到jdbc.dataSource.url的值“jdbc:postgresql://192.168.1.118:5432/DB_name”填入${jdbc.dataSource.url}所在的位置。
2,将property配置文件与工程分离(独立到服务器中)
为什么想到要这样做呢?
这是在实际开发部署中,每次将工程打包部署到服务器,都需要在服务器端修改工程的配置项(如数据源等),感觉很麻烦。
如果工程优先读取放置在服务器上的配置文件,没有此配置文件才从工程目录里面读取,那样就可以将只在服务器存放一份配置文件并修改成与服务器运行环境一致,今后部署即可不用每次都修改工程配置文件了。
2.1 做法:
刚才我们在第1点添加了一个propertyConfigurer,现将其改名为“propertyConfigurer2”,并添加一个“propertyConfigurer1”,两者配置为:
- <bean id="propertyConfigurer1"
- class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="order" value="1"/>
- <property name="ignoreResourceNotFound" value="true"/>
- <property name="ignoreUnresolvablePlaceholders" value="true" />
- <property name="location" value="file:C:/tempSys/config/global-config-file.properties"/>
- </bean>
- <bean id="propertyConfigurer2"
- class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="order" value="2"/>
- <property name="ignoreUnresolvablePlaceholders" value="true" />
- <property name="location" value="classpath:global-config-file.properties"/>
- </bean>
<bean id="propertyConfigurer1" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="order" value="1"/> <property name="ignoreResourceNotFound" value="true"/> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="location" value="file:C:/tempSys/config/global-config-file.properties"/> </bean> <bean id="propertyConfigurer2" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="order" value="2"/> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="location" value="classpath:global-config-file.properties"/> </bean>
order属性表示加载顺序,这种配置表示先加载propertyConfigurer1,在加载propertyConfigurer2,两者配置文件中有同样的参数,后加载的参数不会覆盖先加载的。
propertyConfigurer1需要添加一个 <property name="ignoreResourceNotFound" value="true"/>属性,表示当找不到这个配置文件时,则跳过,这样就不会抛出FileNotFoundException了。
这样配置之后,工程会在启动时,首先检查C:/tempSys/config/global-config-file.properties是否存在,如果存在则加载器配置,随后再加载项目目录下的global-config-file.properties;如果不存在,则直接加载项目目录下的global-config-file.properties。
这样做不单可将配置与开发环境分离出来,避免每次部署时繁琐的配置修改工作。同时这也提高了系统部署时的安全性,比如,实际运行的正式数据库账户密码信息将是十分机密的信息,由运维人员保管维护,可能对开发人员都不能公开(开发人员只知道开发测试数据库的信息),将这些信息直接配置在服务器上,服务器也由运维人员进行管理,这样可以减少机密信息的知晓人群,提高安全性。
一、在 Spring Config 文件中配置 Bean 时,有时候需要在 Bean 的配置里添加 系统部署的细节信息, 如文件路径,数据源配置信息。而这些部署细节实际上需要在配置文件外部来定义。
二、Spring 提供了一个 PropertyPlaceholderConfigurer 的 BeanFactory 后置处理器。这个处理器允许用户将 Bean 的配置部分内容外移到属性文件中,然后可以在 Bean 的配置文件
里使用形式为 ${var}的变量,PropertyPlaceholderConfigurer 从属性文件里加载属性,并使用这些属性来替换变量。
三、Spring 还允许在属性文件中使用 ${key},以属性间的互相引用。
四、使用:需要注册 PropertyPlaceholderConfigurer 。通过 <context:property-placeholder location="props.properties"/> 这种方式来指定属性文件。
五、例子:
1.目录结构
2. spring-config.xml
<context:property-placeholder location="db.properties"/> <bean class="com.nucsoft.spring.bean.Employee" id="employee" p:empName="${abc}" p:age="${cde}" scope="prototype"/>
3.db.properties
abc=123456 cde=${abc}
4.测试
@Test public void test01() { Employee employee = ctx.getBean(Employee.class); System.out.println(employee); }
5.控制台输出
Employee{empName='123456', age=123456}
property文件中:
url = jdbc:mysql://localhost:3306/resume
user= root
pwd = 123
java代码读取:
package com.util;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
public class DBUtil {
//建立连接
public static Connection getConnection() throws Exception {
Connection conn = null;
String property=getProperty();
String[] propertys=property.split(" ");
String url = propertys[0];
String user = propertys[1];
String pwd = propertys[2];
try {
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(url, user, pwd);
} catch (Exception e) {
e.printStackTrace();
throw e;
}
return conn;
}
//获取property的配置文件
public static String getProperty()
{
Properties pro= new Properties();
String property="";
try
{
InputStream is = DBUtil.class.getResourceAsStream("../../config.property");
pro.load(is);
String url = pro.getProperty("url").trim();
String user = pro.getProperty("user").trim();
String pwd = pro.getProperty("pwd").trim();
property=url+" "+user+" "+pwd;
if(is!=null) is.close();
}catch(Exception e) {
System.out.println("there is error to read config file...");
e.printStackTrace();
}
return property;
}
}