Spring day02
1. 注入补充【了解】
1.1 注入null值
需要显式的为一个属性赋null值时,需要使用null子标签。
<bean id="addr" class="com.jsd.entity.Address">
<constructor-arg value="硅谷"/>
<constructor-arg><null></null></constructor-arg>
</bean>
1.2 内部bean
当一个bean只为另外一个bean使用时,可以写成内部bean的形式。
示例:
<bean id="p" class="com.jsd.entity.Person">
<property name="personId" value="1"/>
<property name="personName" value="xiaohei"/>
<property name="addr" ref="addr"/>
</bean>
<bean id="addr" class="com.jsd.entity.Address">
<property name="city" value="郑州"/>
<property name="street" value="文化路"/>
</bean>
内部bean:
<bean id="p" class="com.jsd.entity.Person">
<property name="personId" value="1"/>
<property name="personName" value="xiaohei"/>
<property name="addr">
<bean class="com.jsd.entity.Address">
<property name="city" value="郑州"/>
<property name="street" value="文化路"/>
</bean>
</property>
</bean>
2. FactoryBean技术(创建复杂对象)
使用FactoryBean技术创建复杂对象。
FactoryBean的使用步骤:
准备工作:pom.xml导入mysql-connector-java依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
- 定义一个用于创建复杂对象的类,必须实现FactoryBean接口
public class ConnectionFactoryBean implements FactoryBean<Connection> {
@Override
// 用于返回 创建的复杂对象
public Connection getObject() throws Exception {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/shuju?useUnicode=true&characterEncoding=utf-8";
String username = "root";
String password = "root";
return DriverManager.getConnection(url,username,password);
}
@Override
//返回 复杂对象的类型(类对象)
public Class<?> getObjectType() {
return Connection.class;
}
@Override
//决定复杂对象是不是单例
public boolean isSingleton() {
return false;
}
}
- 配置上一步定义的FactoryBean
<!--getBean("conn")获取不是ConnectionFactoryBean类型的对象,获取getObject()返回的复杂对象 -->
<bean id="conn" class="com.jsd.factory.ConnectionFactoryBean"/>
注意:
- 复杂对象的单例控制,由isSingleton()方法返回值决定
- 通过getBean("&id属性"),可以获取自定义的FactoryBean对象
3. Spring的IOC和DI
IOC(Inversion of Control)控制反转、反转控制
DI(Dependency Injection)依赖注入
IOC:属性的赋值权力从代码反转到Spring框架中。
DI:Spring通过依赖注入完成属性赋值。
控制:对于类中依赖(属性)的赋值的控制权。
传统的正向控制:
问题:类和类之间强耦合。
IOC和DI对同一件事,不同角度的描述。IOC更加偏重思想,DI更加偏重于实现手段。
IOC:属性的赋值权力从代码反转到Spring框架中。
DI:Spring通过依赖注入完成属性赋值。
4 Spring Bean的生命周期(概念重点)
对象的生命周期:对象从生到死的过程。
Spring工厂中Bean的生命周期并不像想象的那么简单,Spring对工厂中的Bean的生命周期进行了细致的划分,并允许开发者通过编码或配置的方式定制Bean在生命周期的各个阶段的操作。
- 初始化阶段:在对象创建后,进行一些初始化操作,比如对属性值的检查
- 销毁阶段:在回收对象之前,执行一些销毁操作,比如资源的释放
4.1 初始化阶段
在Spring工厂创建对象并为属性赋值后就进入到了Bean的初始化阶段,Spring会自动调用Bean的初始化方法。在初始化方法中可以做一些初始化操作,比如对刚创建的对象进行检查操作。而初始化方法可以由开发者实现接口或者配置的方式定义。
实现InitializingBean接口
public class XxxServiceImpl implements XxxService, InitializingBean {
private XxxDao xxxDao;
public XxxServiceImpl() {
System.out.println("XxxServiceImpl()");
}
public XxxDao getXxxDao() {
return xxxDao;
}
public void setXxxDao(XxxDao xxxDao) {
System.out.println("XxxServiceImpl.setXxxDao");
this.xxxDao = xxxDao;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("XxxServiceImpl.afterPropertiesSet");
if(xxxDao == null){
throw new RuntimeException("xxxDao must not be null");
}
}
...
}
通过Bean标签的init-method属性
public class XxxServiceImpl implements XxxService, InitializingBean {
private XxxDao xxxDao;
public XxxServiceImpl() {
System.out.println("XxxServiceImpl()");
}
public XxxDao getXxxDao() {
return xxxDao;
}
public void setXxxDao(XxxDao xxxDao) {
System.out.println("XxxServiceImpl.setXxxDao");
this.xxxDao = xxxDao;
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("XxxServiceImpl.afterPropertiesSet");
if(xxxDao == null){
throw new RuntimeException("xxxDao must not be null");
}
}
private void initMethod(){
System.out.println("XxxServiceImpl.init");
}
...
}
<bean id="xxxDao" class="com.jsd.dao.impl.XxxDaoImpl"/>
<bean id="xxxService" class="com.jsd.service.impl.XxxServiceImpl" init-method="initMethod">
<property name="xxxDao" ref="xxxDao"/>
</bean>
InitializingBean接口和init-method属性的应用场景的区别:
接口方式:开发简单,一次实现,该类型的所有Bean配置都自动生效。
init-method属性:对于旧代码而言无需修改,即可完成对旧代码的配置。
实战中,二者一般不会同时出现,如果同时出现执行顺序:先接口中的方法,后init-method属性配置的方法。
4.2 销毁阶段
在关闭Spring工厂时,Spring会销毁其创建的对象,此时就进入到了Bean的销毁阶段。在此阶段,Spring会自动调用Bean的销毁方法,在对象销毁前执行一些操作,比如释放资源。
实现DisposableBean接口
public class XxxServiceImpl implements XxxService, InitializingBean, DisposableBean{
...
@Override
public void destroy() throws Exception {
System.out.println("XxxServiceImpl.destroy");
}
}
@Test
public void testInitializingBean(){
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
ctx.close();
}
通过Bean标签的destroy-method属性
public class XxxServiceImpl implements XxxService, InitializingBean, DisposableBean{ ... @Override public void destroy() throws Exception { System.out.println("XxxServiceImpl.destroy"); } public void destroyMethod(){ System.out.println("XxxServiceImpl.destroyMethod"); }}
<bean id="xxxService" class="com.jsd.service.impl.XxxServiceImpl" init-method="initMethod" destroy-method="destroyMethod"> <property name="xxxDao" ref="xxxDao"/></bean>
实战中,二者一般不会同时出现,如果同时出现执行顺序:先接口中的方法,后destroy-method属性配置的方法。
注意:Spring创建的Bean如果是多例,那么销毁阶段对其不起作用。
5 BeanPostProcessor(后置Bean处理)
BeanPostProcessor可以对Spring工厂中所有的Bean实例化后,对Bean对象再次加工处理。
编码
User.java
package com.jinshida.entity;
import java.io.Serializable;
public class User implements Serializable {
private Integer userId;
private String username;
private String password;
public User() {
}
public User(Integer userId, String username, String password) {
this.userId = userId;
this.username = username;
this.password = password;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User{" +
"userId=" + userId +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
MyBeanPostProcessor.java
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override //在初始化阶段前调用
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor.postProcessBeforeInitialization");
if (bean.getClass() == User.class) {
User user = (User) bean;
user.setPassword("654321");
}
return bean;
}
@Override //在初始化阶段后调用
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("MyBeanPostProcessor.postProcessAfterInitialization");
return bean;
}
}
配置
<bean id="u" class="com.jsd.entity.User" init-method="initMethod">
<property name="userId" value="1"/>
<property name="username" value="xiaohei"/>
<property name="password" value="123456"/>
</bean>
<bean class="com.jsd.factory.MyBeanPostProcessor"/>
BeanPostProcessor可以对Spring中配置的所有Bean进行统一处理,并且Spring中可以配置多个BeanPostProcessor。
6 Spring配置文件的拆分
6.1 applicationContext.xml的拆分
一个复杂的企业应用会拆分成多个模块,不同模块中有各自的spring配置,最终还需要聚合在一起。
<import resource="classpath:datasource/a.xml"/>
classpath:JVM寻找类的起始路径,可以认为java、resources都是classpath。
6.2 Spring配置文件的xsd(了解)
XML的格式约束文件有2种:DTD(Document Type Definition)和XSD(XML Schema Definition)。XSD和DTD一样用来约束配置文件,DTD编写简单,XSD功能强大。
一般地,简单的配置文件用dtd约束(如Struts2的配置文件,MyBatis的配置文件),复杂的配置文件使用xsd约束(如Spring)。
-
xsd的基本使用
-
在一个配置文件中使用多个xsd
Spring不同的模块定义了不同的xsd文件,在一个Spring配置文件中使用多个模块的功能,也就要在配置文件中使用多个xsd。
使用步骤:
- 在跟标签中添加xmlns:前缀=“其它xsd的命名空间”
- 在xsi:schemaLocation=“添加路径对”
- 使用其它xsd中定义的标签需要添加
前缀:
示例:
6.3 Spring 配置文件properties的拆分
程序中关于数据库的参数配置,一般都抽取到jdbc.properties文件中。Spring的applicationContext.xml中关于数据库的配置也一样要抽取。
-
将数据库参数抽取到jdbc.properties中
driverClassName=com.mysql.jdbc.Driverurl=jdbc:mysql://localhost:3306/jsd?useUnicode=true&characterEncoding=utf-8user=rootpassword=root
-
在Spring的配置文件中读取jdbc.properties
<!-- 读取配置文件 使用context.xsd定义property-placeholder标签读取jdbc.properties文件--><context:property-placeholder location="classpath:jdbc.properties"/>
-
在需要使用参数的地方,通过${参数名}获取参数值
<bean id="conn" class="com.jsd.factory.ConnectionFactoryBean" > <property name="url" value="${url}"/> <property name="driverClassName" value="${driverClassName}"/> <property name="username" value="${user}"/> <property name="password" value="${password}"/></bean>
注意:${username}会优先读取操作系统的用户名,jdbc.properties中必须改名
7 Spring整合Struts2
准备工作:创建好一个可运行的struts2项目。
Struts2+MyBatis项目开发步骤:
-
搭建开发环境
-
新建web项目
-
导入依赖
数据库驱动依赖
mybatis依赖
servlet+jsp+jstl依赖
struts2依赖
-
配置文件+工具类
jdbc.properties
log4j.properties
mybatis-config.xml
mapper.xml
struts.xml
MyBatisUtils.java
-
配置文件初始化
mybatis-config.xml
web.xml:开启struts2框架
-
-
建表
-
实体类
-
dao
-
service
-
test
-
Action+jsp
-
集成测试
7.1 整合效果
Spring整合Struts2的效果:
在Spring配置文件中配置Service和Action的Bean,其中Action的bean的scope要配置为"prototype",由Spring工厂创建Struts2需要的Action和Service.
struts.xml中action的class属性不再配置全类名(也就是不再由Struts2框架通过反射创建Action),而是配置action的id(使用Spring创建的Action对象)
7.2 实战
导入spring-web
和 struts2-spring-plugin
依赖
<!-- spring对javaweb开发的支持 版本和spring-context保持一致--><dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.26.RELEASE</version></dependency><!-- 负责黏合Spring和Struts2框架 --><dependency> <groupId>org.apache.struts</groupId> <artifactId>struts2-spring-plugin</artifactId> <version>2.3.16.3</version></dependency>
-
tomcat启动应用时,自动创建Spring工厂
web.xml
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value></context-param><listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class></listener>
-
Struts2从Spring工厂中获取Action
applicationContext.xml
<bean id="userService" class="com.jsd.service.impl.UserServiceImpl"/><bean id="userAction" class="com.jsd.action.UserAction" scope="prototype"> <property name="userService" ref="userService"/></bean>
struts.xml
<package name="day02" extends="struts-default" namespace="/day02"> <!-- class配置的是spring配置文件中Action的id --> <action name="showAllUsers" class="userAction" method="showAllUsers"> <result name="success">/showAllUsers.jsp</result> </action></package>