-
有关Spring配置的概述
在Spring的开发中我们可以选的配置方式有基于配置文件的配置、代码的配置、注解的配置。上述的三种配置方式主要是描
- While XML has been the traditional format for defining configuration metadata you can instruct the container to use Java annotations or code as the metadata format by providing a small amount of XML configuration to declaratively enable support for these additional metadata formats.
述上的差异,但他们的作用是相似的都是描述Bean以及Bean之间的一个依赖,在官方的文档中我们把配置称为configuration metadata(配置元数据),程序会根据这些配置的信息来知道对象的实例如何创建(例如我们要创建那个对象,这个对象是通过什么来创建——默认构造函数、还是实例的创建方法?),如何配置,以及有关这个实例的相关信息。Spring的IOC容器可以通过我们这些配置的内容以及程序来给我们产出一个可以配置的系统程序。
- The container gets its instructions on what objects to instantiate, configure, and assemble by reading configuration metadata.
- Your application classes are combined with configuration metadata so that after the
ApplicationContext
is created and initialized, you have a fully configured and executable system or application.
配置文件的文件格式并不只有XML一种还有其他的,在我们使用的过程中主要体现在语法上的差异。三种配置方式各有优劣,在实际开发中如果我们有选择的空间那么我们应该最求便利,而不是某一种技术。一般来说但使用第三方库的时候我们我们会使用配置文件的方式来进行,在配置我们自己写的类的时候我们会选择注解,这样的话更加的清晰、方便。 关于代码中的配置我们实际上是通过注解类标识配置类来实现的
-
基于XML配置文件的开发
基于配置文件的开发我们已经在《Java的工厂模式解耦与Spring的 IOC容器应用》中有所展示,这里就不再做代码的展示。在这里我们主要是描述一下他的实现思路。不管是我们自己编写的工厂模式还是说我们现在所讲的Spring框架下,我们都需要指出我们的配置文件是什么。在工厂模式中我们也是需要加载文件流的:
InputStream in = BeanFactory1.class.getClassLoader().getResourceAsStream("bean.properties");
通过这文件流的相关信息来获得我们要通过工厂创建那些示例,以及与这些实例相关的信息。这时候在我们的工厂模式中还会产生一个容器用来存放我们创建出来的实例,在我们需要使用这些实例的时候需要调用工厂类的实例,来给我们传递我们需要的:
IAccountService as = (IAccountService) BeanFactory1.getBean("accountService");
这里我们可以看出创建实例的时候并没有通过new,而是通过一个名称来的而这个名称又称ID他有与之唯一对应的类型,这个对应关系就是在配置文件中体现的,工厂通过这个ID来获取一个全类名,通过这个全类名通过创建一个实例:
配置文件:
accountService=com.itheima.service.impl.AccountServiceImpl
accountDao=com.itheima.dao.impl.AccountDaoImpl
根据配置文件传过来的类全路径信息创建实例:
Object value = Class.forName(beanPath).newInstance();
我们也可以通过配置文件附带跟多的与实例相关的信息。在Spring框架中道理是一样的只不过这里由我们自己写的工厂,这一部分是由Spring的框架来完成了,在Spring中我们想要一个实例的时候也是需要先获取一个容器:
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
在这里我们可以看到我们的这个容器,也是需要读取一个配置文件的。这时候我们想要获取相关的实例是通过容器来得到的,这里说的容器我们可以看到他是一个ApplicationContext对象的实例ac:
IAccountService as = (IAccountService)ac.getBean("accountService");
关于XML的还有一个名称空间的问题,名称空间主要是为了解决名称冲突的问题。
-
基于注解的开发配置
在Spring中基于配置文件的开发,首先要在配置文件中配置我们的实例的相关信息,在我们需要相关实例的时候我们通过告知配置文件的位置来穿件一个相关的容器,框架会根据我们配置文件中相关的信息来给我们实现相关的实例,然后我们通过这些容器可以获取到实例。简单的来说这也就是一个在配置文件中配置相关的实例、根据这个配置文件来创建相关容器、在需要的时候来通过容器获得相应实例的过程。
这里我们显示把原来XML中,我们关于Bean之间的依赖关系配置出来,这时候就像关于连接数据库中的配置信息仍然留在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">
<!-- 告知spring在创建容器时要扫描的包 -->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入数据源-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</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/eesy"></property>
<property name="user" value="root"></property>
<property name="password" value="1234"></property>
</bean>
</beans>
在这里我们关于我们项目有关service、dao的配置不见了:
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
</bean>
<!--配置Dao对象-->
<bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="runner" ref="runner"></property>
</bean>
这一部分是属于对依赖实例依赖关系的描述,我们通过@Component(@Service、@Controller、@Repository)来完成id、class这个属性所代表的功能。<property name="runner" ref="runner"></property> 是告诉框架在创建实例化的时候要给某个实例域赋一个什么样的值,这一部分的功能我们可以通过@Autowired、@Qualifier、 @Resource、@Value,这一类的注解来实现我们的功能。
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
@Autowired
private QueryRunner runner;
}
@Component、@Service、@Controller、@Repository这四个注解有着相同的功能就是告诉框架把这个类在容器中创建,区别就是后三者相比于第一个有着更加明确的语义他们分别用于三层架构的服务层、控制层、持久层。如果注解中有且只有一个属性要赋值时,且名称是 value,value在赋值是可以不写,这也就是我们看到Repository的后面只跟了一个字符串,正如上边所言这是他的简略写法,他也可以写成value="accountDao",在使用这四个注解的时候如果不指定 value 属性,默认 bean 的 id 是当前类的类名,首字母小写。
如果说上边的四个注解是描述依赖关系的话,那么@Autowired、@Qualifier、 @Resource、@Value就是从容器中获取相应的实例,对比这里段代码就会显而易见:
IAccountService as = (IAccountService)ac.getBean("accountService");
@Autowired
private QueryRunner runner;
这四个注解的差别在于他们对于获取容器中的那个实例的规则不同,大概可以说成是@Autowired是按照类型获取的——只要在容器中找到唯一一个与之相匹配类型的实例,就把这个实例赋值给这个变量。在我们通过配置文件来配置依赖的时候,关于注入的方法中有一个叫做set注入,这种注入方法要求在类中要有给实例域复制到额set方法,然而我们的@Autowired方法不需要set在类中写入set方法就可以完成相应的注入赋值,当然肯定也不需要绕弯通过构造函数,而是Spring框架内部的一种实现。当有多个 类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到 就报错。这时候如果我们不想强迫的去改变我们的变量名称去迎合id的匹配我们就可以使用@Qualifier注解,不过@Qualifier的使用要与@Autowired注解的使用相结合,@Quality注解在类型匹配的基础上指明id。然而我们的@Resource就可以完全的通过id实现注入。上边说道的三个都是注入其他Bean是用的,那么@value就是用于注入基本数据类型和String类型的。
讲到这里可能就会有疑问了,如果我们的项目中会用很多的包,那我们的框架怎么知道应该去哪里找我们被注解了的类呢,目前在我们XML仍然存在的情况下,我们可以看到这样的一个配置,他的作用就是告诉框架应该扫描那些包:
<context:component-scan base-package="com.itheima"></context:component-scan>
我们也可以看到这里的标签出现了context,这就需要我们配置相关的名称空间。
-
用配置类形式实现的配置
在上边的配置中我们还有一部分有关数据连接相关的配置仍然放在XML的配置文件中,接下来我们看看如何通过注解实现配置类来将这一部分的配置通过代码的形式来实现。我们的这个配置类相当于是一个配置文件,那么我们以应该把这个特殊的类标记出来作为配置类存在,再这里我们是通过@Configure的注解实现。既然我们在配置有使用XML文件的时候还需要通过添加一个标签来告诉我们的框架,在容器实例化或是装配实例的时候应该扫描那些包,这一部分我们在配置类中也是必不可少的,我们通过@ComponentScan的这个注解来实现。到这里我们已经把一个类标记为配置类,并且告诉了框架可以去哪里扫描,但是如果我们需要进行许多配置这时候我们的一个配置类就显得有些杂乱无章,这时候我们可以通过@Import的注解来实现父子配置类的思路,大致的思想就是通过在父配置类上添加这个Import的注解导入一个类的名称,告诉他这个是他的这个类中也有相关的配置信息。
@Configuration
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
这个SpringConfiguration的类就相当于是一个父配置类,在这里我们可以看到还有一个@PropertySource的注解,并且引入了一个properties格式的配置文件,我们都说了要摆脱配置文件来实现配置了,为什么还要配置文件呢?因为就像数据源、驱动类型这类的配置信息写到配置文件是一个更加正确的选择,这样我们就可以不用重启项目重新编译直接通过更改配置文件就实现实现对我们数据源的更改:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/eesy
jdbc.username=root
jdbc.password=1234
我们在文件的名称前面加了一个classpath这是告诉框架我们需要加载的这个文件是位于我们的类目录下。在配置文件中我们添加进去的配置信息如何使用呢,这里用到的是Spring el表达式:
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="ds2")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
@Bean(name="ds1")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}
}
通过配置文件的值我们可以在我们的配置类中通过${ }的方式来实现,JdbcConfig类与properties配置文件的关系就是通过我们的@Import以及@PropertyResource注解作用于父配置类来实现的。一个告诉父配置类有哪类类是他的子配置类,将这个子配置类归纳到父配置类的范畴,一个告诉父配置类以及他的属于他的西配置类哪里的配置文件信息他可以使用,这样配置文件以及子父配置类就联系起来了。
在这里我们还看到了@Bean的注解,这个注解的作用就是告诉框架这个方法是一个创建实例的方法,并且通过name属性见返回的对象标记一个id。在上面的代码中通过两个个@Bean注解标记的方法我们就得到了关于数据连接相关的一些配置。同时我们还有@Scope、@PostConstruct、@PreDestory注解来来说明有关实例的作用范围、指定初始化方法,是定销毁方法。