一、灵活配置DataSource
实现Spring与MyBatis的整合过程中,我们将数据源的配置拿到了Spring配置文件中。实际应用中,Spring还有很多灵活配置方式可以选择。
1、使用外部属性文件配置数据源
既然MyBatis可以引入外部属性文件,那么Spring肯定也是可以的。
在Spring中,使用PropertyPlaceholderConfigurer类可以加载属性文件,在配置文件中采用${...}的方式引用属性文件中的键值对。
<!--可引入外部属性文件,配置数据源-->
<bean class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer">
<property name="location">
<!--指定属性文件的位置-->
<value>classpath:data.properties</value>
</property>
</bean>
引入属性文件之后,我们就可以在配置数据源的时候,应用属性文件中的键值对了,如下:
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${driver}" />
<property name="url">
<value>${url}</value>
</property>
<property name="username" value="${username}" />
<property name="password" value="${password}" />
</bean>
2、使用JNDI数据源
如果应用部署在高性能的服务器上,我们可能更希望使用服务器本身提高的数据源。服务器的数据源使用JNDI方式供使用者调用。在Spring中,提供了专门引用JNDI资源的JndiObjectFactoryBean类。
使用JNDI资源,首先在服务器上要有这个资源。我们以Tomcat为例,在conf文件夹下的context.xml定义如下数据源:
<Resource
name="jdbc/Demo"
auth="Container"
type="javax.sql.DataSource"
maxActive="100"
maxIdle="30"
maxWait="10000"
username="root"
password="****"
driverClassName="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost:3306/Demo?useSSL=true&serverTimezone=UTC"
/>
然后我们修改Spring配置文件,通过JNDI资源定义数据源组件。如下:
<!--使用JNDI资源的方式获取数据库连接-->
<bean id="dataSource_JNDI" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/ShopOrder</value>
</property>
</bean>
这样一来,在需要引用数据源组件的地方,引用这个数据源Bean的id即可。这种通过JNDI活动数据源的方式很简洁,充分体现了Spring追求简洁的目标。
二、Spring中Bean的作用域
在Spring中定义Bean,除了可以创建Bean实例并对其属性进行注入,还可以为其指定作用域。这个作用域的取值决定了Spring创建该组件实例时所采用的策略,进而影响程序的效率以及数据安全问题。目前Spring划分了5中Bean的作用域:
作用域 | 说明 |
singleton | 默认值,以单利模式创建Bean的实例,即容器中只会存在一个该Bean的实例 |
prototype | 每次从容器获取该Bean时,都会创建一个新的实例 |
request | 用于Web应用环境,每次HTTP请求都会创建一个新的实例 |
session | 用于Web应用环境,同一个会话共享同一个实例,不同的会话使用不同的实例,即每一次新的会话都会创建一个新的实例 |
global session | 仅在Portlet的Web应用中使用,同一全局会话共享一个实例。对于非Portlet环境,等同于session |
singleton是默认采用的作用域,即默认情况下Spring为每一个Bean仅创建一个实例,对于不存在线程安全问题的组件,这种方式会大大减少创建对象的开销,提高运行效率。
对于特定的应用场景,比如我们需要每一个用户都相互隔离,使用默认的作用域肯定是实现不了需求的,这时候就需要我们指定Bean组件的作用域为prototype,即每一次获取该组件的实例时都去创建一个新的实例,指定组件的scope属性即可,如下:
<bean id="demoTest" class="ServiceImpl.RoleServiceImpl" scope="prototype">
</bean>
对于使用注解定义的Bean组件,需要使用@Scope注解指定其作用域,如下:
package ServiceImpl;
import Service.BillService;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
/**
* 订单服务接口的实现类
* */
@Scope("prototype") //指定作用域为prototype
@Service("billService") //定义Bean组件
public class BillServiceImpl implements BillService {
//省略实现方法
}
三、Spring的自动装配
这一部分只做了解即可,大型项目中不推荐使用自动装配,还是指定所依赖的组件好。因为虽然它能够简化配置,但是降低了依赖关系的清晰性和透明性,全都交给Spring自动装配组件,弄得我们都不知道谁依赖谁。而且依赖关系的装配仅依赖于源文件的属性名或者类型,导致Bean与Bean之间的耦合度上升,不利于高层次解耦。
先介绍autowire属性的几个值:
属性值 | 说明 |
no | 不适用自动装配,依赖关系必须由我们自己指定 |
byType | 根据属性类型自动装配。BeanFactory查找容器中的全部Bean,如果有一个与需要注入依赖的属性的类型相匹配的Bean,就将这个Bean装配给这个属性。如果有多个相匹配的Bean,Spring将抛出致命异常;如果没有相匹配的Bean,什么都不会发生,最多用的时候会出现空指针。 |
byName | 根据属性名自动装配。BeanFactory查找容器中所有的Bean,找出id与属性的setter方法相匹配的Bean,进行注入 |
constructor | 与byType的方式类似,不同之处在于它应用于构造器函数。如果容器中没有找到与构造器参数类型一致的Bean,那么将会抛出异常。 |
自动装配示例:
<bean id="demoTest" class="ServiceImpl.RoleServiceImpl" scope="prototype" autowire="byType" />
如果要配置的Bean有很多,每个Bean都指定autowire也很繁琐。<beans>元素提供了default-autowire属性,影响全局,减少维护单个Bean的注入方式,不需要为每个Bean指定autowire属性,如下:
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
" default-autowire="byType">
</beans>
指定了default-autowire属性,仍然可以为某个Bean单独指定autowire属性,此时会覆盖全局的default-autowire值。
四、拆分Spring配置文件
使用XML方式进行配置Spring项目,如果项目很大,配置文件的可读性、可维护性较差,庞大的Spring配置文件难以维护、阅读。此外,在团队开发时,多人修改同一配置文件容易产生问题冲突。我们可以将一个大的配置文件进行拆分,每一个小的配置文件只配置功能相似的组件,比如application_dao.xml,application-service.xml,降低配置文件的修改难度。
那么我们启动项目的时候,如何指示Spring找到这些小的配置文件,将它们整合到一起呢?
根据ClassPathXmlApplicationContext类的构造方法的几种重载:
public ClassPathXmlApplicationContext(String configLocation);
public ClassPathXmlApplicationContext(String... configLocations);
我们可以这样:
ApplicationContext context=new ClassPathXmlApplicationContext("application-mybatis.xml",
"application-dao.xml",
"application-service.xml");
也可以这样:
String[] locations={"application-mybatis.xml","application-dao.xml","application-service.xml"};
ApplicationContext context=new ClassPathXmlApplicationContext(locations);
还可以这样,采用通配符:
ApplicationContext context=new ClassPathXmlApplicationContext("application-*.xml");
使用通配符可以导入多个配置文件,很方便,但是命名规范一定要好,遵循一定的命名规律。
此外,Spring配置文件本身也可以通过import子元素导入外部配置文件,将多个配置文件整合到一起,最后只需要加载终极配置文件即可,例如在application-mybatis.xml中引入以下两个配置文件:
<import resource="application-dao.xml" />
<import resource="application-service.xml"/>
加载的时候就这样:
ApplicationContext context=new ClassPathXmlApplicationContext("application-mybatis.xml");