Spring 入门
在大学本科的课程中我们学习的是Java,而在实际开发中应用最广的还是基于Java的Spring框架,Spring入门从这里开始,Spring中有许多优秀的设计理念例如Ioc、AOP,以及反射、代理的充分应用,需要不断的学习总结,这里只是我作为初学者的一点记录。
了解Spring
什么是Spring?
Spring是分层的JavaSE/EE full-stack(一站式) 轻量级开源框架
- 一站式(对三层结构每一层的解决方案) + Web层:SpringMVC + 业务层:IOC、AOP完成Bean管理 + 持久层:JDBC Template
Spring核心
- IoC(Inverse of Control)
控制反转:将对象创建权交给Spring,由Spring自动创建对象 - AOP (Aspect Oriented Programming)面向切面编程
面向对象功能的延伸,不是替换OOP,用来解决OOP开发中的一些问题。
IoC控制反转
什么是控制反转
控制反转(Inversion of Control)是一种是面向对象编程中的一种设计原则,用来减低计算机代码之间的耦合度。其基本思想是:借助于“第三方”实现具有依赖关系的对象之间的解耦。
这个第三方在Spring框架里就是Spring,它来负责所有对象的生命周期和对象间的关系。由于引进了中间位置的“第三方”,也就是Spring,使得各个对象没有了耦合关系,全部对象的控制权全部上缴给“第三方”容器。
什么是依赖注入?
依赖注入(Dependency Injection):DI,简单的说,实例变量传入到一个对象中去。
Spring全权负责组件的装配,它会把符合依赖关系的对象通过属性(JavaBean中的setter)或者是构造子传递给需要的对象。
相对于IoC而言,依赖注入(DI)更加准确地描述了IoC的设计理念。所谓依赖注入,即组件之间的依赖关系由容器在应用系统运行期来决定,也就是由容器动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。
IoC实现原理
提供一个工厂,解析配置后通过反射获得到一个类的实例。
工厂+配置+反射
public class BeanFactory{
public UserService getUserService(){
//反射+配置文件
return Class.forname(类名).newInstance;
}
}
Spring优点
- 方便解耦,简化开发
Spring就是一个大工厂,可以将所有对象创建和依赖关系维护,交给Spring管理 - AOP编程的支持
Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能 - 声明式事务的支持
只需要通过配置就可以完成对事务的管理,而无需手动编程 - 方便程序的测试
Spring对Junit4支持,可以通过注解方便的测试Spring程序 - 方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,其内部提供了对各种优秀框架(如:Struts、Hibernate、MyBatis、Quartz等)的直接支持 - 降低JavaEE API的使用难度
Spring 对JavaEE开发中非常难用的一些API(JDBC、JavaMail、远程调用等),都提供了封装,使这些API应用难度大大降低
Spring简单案例
<?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 id="person" class="Person">
<property name="name" value="TT"/>
<property name="car" ref="tt_car"/>
</bean>
<bean id="tt_car" class="Car">
<property name="name" value="宝马"/>
</bean>
</beans>
Person.java
public class Person {
private Car car;
private String name;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void who(){
System.out.print(getName()+"正在");
}
}
Car.java
public class Car {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void drive(){
System.out.print("开着"+getName()+"车……");
}
}
test.java
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class test {
@Test
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) applicationContext.getBean("person");
person.who();
Car car = person.getCar();
car.drive();
}
}
输出结果:
TT正在开着宝马车……
Spring框架加载配置文件
//加载classpath路径下
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载磁盘路径下
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("applicationContext.xml");
BeanFactory和ApplicationContext的区别?
- ApplicationContext类继承了BeanFactory
- BeanFactory在用到这个类的时候,getBean()方法才会加载
- ApplicationContext加载配置文件的时候,创建了所有类的实例对象
- ApplicationContext是对BeanFactory扩展,提供了更多功能
- 国际化处理
- 事件传递
- Bean自动装配
- 各种不同应用层的Context实现
Ioc容器
Spring 容器是 Spring 框架的核心。容器将创建对象,把它们连接在一起,配置并管理他们的整个生命周期。Spring 容器使用依赖注入(DI)来管理组成一个应用程序的组件。这些对象被称为 Spring Beans。 通过解析配置元数据,容器知道对哪些对象进行实例化,配置和组装。配置元数据可以通过 XML,Java 注释或 Java 代码来表示。Spring IoC 容器利用 Java 的 POJO 类和配置元数据来生成完全配置和可执行的系统或应用程序。 Spring 提供了以下两种不同类型的容器。
容器 | 描述 |
---|---|
BeanFactory 容器 | 它是最简单的容器,给 DI 提供了基本的支持,它用 org.springframework.beans.factory.BeanFactory 接口来定义。BeanFactory 或者相关的接口,如 BeanFactoryAware,InitializingBean,DisposableBean,在 Spring 中仍然存在具有大量的与 Spring 整合的第三方框架的反向兼容性的目的。 |
ApplicationContext 容器 | 该容器添加了更多的企业特定的功能,例如从一个属性文件中解析文本信息的能力,发布应用程序事件给感兴趣的事件监听器的能力。该容器是由 org.springframework.context.ApplicationContext 接口定义。 |
Spring Bean
什么是Bean
由Spring IoC容器所管理的对象称为bean。
Bean的定义
属性 | 描述 |
---|---|
class | 必需,并指定 bean 类被用来创建bean |
name | 指定唯一 bean 标识符。在基于 XML 的配置元数据时,可以使用 id 或 name 属性来指定 bean 标识符 |
scope | bean 作用域 |
constructor-arg | 构造方法的注入 |
properties | 注入的依赖关系 |
lazy-init | IoC 容器在需要时创建 bean 实例,不是在启动时创建 |
init-method | 回调只是在 bean 的所有必要属性后调用已设置的容器 |
destroy-method | 当包含该 bean 容器被销毁所使用的回调 |
Bean的命名 id属性和name属性
一般情况下,装配一个Bean时,通过指定一个id属性作为Bean的名称
- id 属性在IoC容器中必须是唯一的
- id 的命名要满足XML对ID属性命名规范
必须以字母开始,可以使用字母、数字、连字符、下划线、句话、冒号
如果Bean的名称中含有特殊字符,就需要使用name属性 例如:
<bean name="#person" class="Person"/>
- 因为name属性可以相同,所以后出现Bean会覆盖之前出现的同名的Bean
现在开发都使用id即可
Bean的作用域(scope属性)
<bean name="person" class="Person" scope="singleton"/>
类别 | 说明 |
---|---|
singleton (默认) | 在Spring IoC容器中仅存在一个Bean实例,Bean以单例方式存在 |
prototype | 每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时 ,相当于执行new XxxBean() |
request | 每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境 |
session | 同一个HTTP Session 共享一个Bean,不同Session使用不同Bean,仅适用于WebApplicationContext 环境 |
globalSession | 一般用于Porlet应用环境,该作用域仅适用于WebApplicationContext 环境 |
Bean生命周期(init-method、destroy-method属性)
当一个 bean 被实例化时,在它被使用前需要执行一些初始化工作。当bean不再需要,并且从容器中移除时,可能需要做一些清除工作。 还有一些在 Bean 实例化和销毁之间发生的活动,但是现在只讨论两个重要的生命周期回调方法,它们在 bean 的初始化和销毁的时候是必需的。 我们只要声明带有 init-method 、destroy-method 参数 。init-method 属性指定一个方法,在实例化 bean 时,立即调用该方法。同样,destroy-method 指定一个方法,只有从容器中移除 bean 之后,才能调用该方法。
<bean name="exampleBean" class="ExampleBean" init-method="initMethod" destroy-method="destroyMethod"/>
初始化回调
- 可以实现org.springframework.beans.factory.InitializingBean 接口指定的afterPropertiesSet()方法:
public class ExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work } }
- 在基于XML的配置元数据的情况下,你可以使用 init-method 属性来指定带有 void 无参数方法的名称。例如:
public class ExampleBean { public void initMethod() { // do some initialization work } }
销毁回调
- org.springframework.beans.factory.DisposableBean destroy()方法:
public class ExampleBean implements DisposableBean { public void destroy() { // do some destruction work } }
- 在基于 XML 的配置元数据的情况下,你可以使用 destroy-method 属性来指定带有 void 无参数方法的名称。例如:
public class ExampleBean { public void destroyMethod() { // do some destruction work } }
建议不要使用 InitializingBean 或者 DisposableBean 的回调方法,因为 XML 配置在命名方法上提供了极大的灵活性。
生命周期的步骤
- instantiate bean对象实例化
- populate properties封装属性
- 如果 Bean 实现 BeanNameAware 执行 setBeanName
- 如何 Bean 实现 BeanFactoryAware 或者 ApplicationContextAware 设置工厂 setBeanFactory 或者上下文对象 setApplicationContext
- 如果存在实现 BeanPostProcessor(Bean后处理器),执行postProcessBeforeInitialization
- 如果 Bean 实现 InitializingBean 执行 afterPropertiesSet
- 调用
指定初始化方法 init - 如果存在实现 BeanPostProcessor (Bean后处理器),执行 执行postProcessBeforeInitialization
- 执行业务处理
- 如果 Bean 实现 DisposableBean 执行 destory
- 调用
指定销毁方法 customerDestory
Bean的其他参数属性(class属性)
指定用来创建 bean 的 bean 类。
IoC容器装配Bean
基于XML配置
Bean实例化
Spring框架实例化Bean的三种方式
- 构造方法实例化(默认使用无参数的构造方法实例化)
- 静态工厂实例化
<bean id="person2" class="PersonFactory" factory-method="getPersonBean"/>
PersonFactory.java
class PersonFactory { public static Person getPersonBean(){ return new Person(); } }
- 实例工厂实例化
<bean id="person3" class="PersonFactory2" factory-method="getPersonBean"/> <bean id="PersonFactory2" class="PersonFactory2"/>
PersonFactory.java
class PersonFactory2 { public Person getPersonBean(){ return new Person(); } }
Bean依赖注入
通过构造方法注入
<bean name="person" class="Person">
<constructor-arg name="name" value="詹姆斯">
<constructor-arg name="id" value="123456">
<!--构造方法的参数下标索引-->
<!--<constructor-arg index="0" type="java.lang.String" value="詹姆斯"/>-->
<!--<constructor-arg index="1" type="java.lang.String" value="123456"/>-->
</bean>
Person.java
public class Person {
private Car car;
private String name;
private String id;
//通过构造方法注入
public Person(String name, String id) {
this.name = name;
this.id = id;
}
}
通过setter方法注入
普通方法注入
- value: 引入普通属性
- ref: 引入引用其他 Bean 对象属性
<bean name="person" class="Person">
<property name="name" value="TT"/>
<property name="car" ref="tt_car"/>
</bean>
Person.java
public class Person {
private Car car;
private String name;
private String id;
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void who(){
System.out.print(getName()+"正在");
}
}
名称空间p注入property
- 为了简化XML文件配置,Spring从2.5开始引入一个新的p名称空间
- p:<属性名>="xxx" 引入常量值属性名>
- p:<属性名>-ref="xxx" 引用其它Bean对象属性名>
引入名称空间:
xmlns:p="http://www.springframework.org/schema/p
<bean name="person" class="Person" p:name="詹姆斯" P:id="123456"/>
SpEL注入property
SpEL:spring expression language ,spring表达式语言,对依赖注入进行简化。
语法:#{表达式}
<bean id="tt_car" class="Car">
<property name="name" value="宝马"/>
</bean>
<bean name="person" class="Person">
<property name="name" value="#{'詹姆斯'}"/>
<property name="id" value="#{'123456'}"/>
<!--直接通过表达式获取tt_car-->
<property name="car" ref="#{tt_car}"/>
</bean>
集合属性的注入
<bean id="tt_car" class="Car">
<property name="name" value="宝马"/>
<property name="passengerNameList">
<!--注入list集合-->
<list>
<value>乐福</value>
<value>JR</value>
</list>
</property>
<property name="passengerNameSet">
<!--注入set集合-->
<set>
<value>乐福</value>
<value>JR</value>
</set>
</property>
<property name="passengerNameMap">
<!--注入map集合-->
<map>
<entry key="1" value="乐福"></entry>
<entry key="2" value="JR"></entry>
</map>
</property>
<property name="properties">
<!--注入properties-->
<pros>
<pro key="1">乐福</pro>
<pro key="2">JR</pro>
</pros>
</property>
</bean>
Car.java
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
public class Car {
private String name;
private List passengerNameList;
private Set passengerNameSet;
private Map passengerNameMap;
private Properties properties;
public Properties getProperties() { return properties; }
public void setProperties(Properties properties) { this.properties = properties; }
public Map getPassengerNameMap() { return passengerNameMap; }
public void setPassengerNameMap(Map passengerNameMap) { this.passengerNameMap = passengerNameMap; }
public Set getPassengerNameSet() { return passengerNameSet; }
public void setPassengerNameSet(Set passengerNameSet) { this.passengerNameSet = passengerNameSet; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public List getPassengerNameList() { return passengerNameList; }
public void setPassengerNameList(List passengerNameList) { this.passengerNameList = passengerNameList; }
public void drive(){ System.out.print("开着"+getName()+"车……"); }
}
Bean作用范围
<bean name="person" class="Person" scope="singleton"/>
Bean初始化方法和销毁方法
<bean name="exampleBean" class="ExampleBean" init-method="initMethod" destroy-method="destroyMethod"/>
基于注解配置
Bean实例化
@Component,描述框架中的Bean。 除了@Component外,Spring提供了3个功能基本和@Component等效的注解
@Component(value = "person")
//只有一个属性value,可以省略value,直接填入id。
- @Repository 用于对DAO实现类进行标注
- @Service 用于对Service实现类进行标注
- @Controller 用于对Controller实现类进行标注
这三个注解是为了让标注类本身的用途清晰,Spring在后续版本会对其增强
<?xml version="1.0" encoding="UTF-8"?>
<!--引入context命名空间-->
<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">
<!--配置自动扫描-->
<context:annotation-config/>
<context:component-scan base-package="cn.uhfun.web.bean"></context:component-scan>
</beans>
Bean依赖注入
普通属性
@Value("勒布朗")
private String name;
对象属性
- 使用@Autowired 进行自动注入 ** 默认按类型注入**
- 使用@Autowired @Qualifier 进行注入 ** 按名称注入**
- 使用@Resource 进行注入 (@Autowired @Qualifier 效果相同)
Car.java
@Component("jr_car")
public class Car {
@Value("奥迪")
private String name;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Person.java
public class Person {
@Autowired
@Qualifier("jr_car")
private Car car;
@Value("勒布朗")
private String name;
public Car getCar() {return car;}
public void setCar(Car car) {this.car = car;}
}
- 通过@Autowired的required属性,设置一定要找到匹配的Bean
如果设置为false,没有匹配的Bean时忽略异常,注入null
@Autowired(required=false)
Bean作用范围
@Scope注解用于指定Bean的作用范围
@Component()
@Scope("prototype")
public class Car {
//...
}
Bean初始化方法和销毁方法
- 初始化:当 bean 被载入到容器的时候调用 initMethod
注解方式下:
@PostConstruct
- 销毁:当 bean 从容器中删除的时候调用 destoryMethod (scope= singleton有效)
注解方式如下:@PreDestroy
基于Java类配置
**手动方式加载@Configuration配置类 **
Spring3.0以JavaConfig为核心,提供使用Java类定义Bean信息的方法
- @Configuration 指定POJO类为Spring提供Bean定义信息
- @Bean 提供一个Bean定义信息
- Spring提供 AnnotationConfigApplicationContext 用于加载使用
@Configuration
配置注解工厂类 - register方法 用于向 注解上下文对象 添加一个配置类
- refresh 刷新容器以应用这些注册的配置类
BeanConfig.java
package cn.uhfun.web.bean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanConfig {
@Bean
//没有指定name,默认id为方法名
public Car tt_car(){
Car c = new Car();
c.setName("保时捷");
return c;
}
@Bean("JR")
public Person person(){
Person p = new Person();
p.setId("123456");
p.setName("我是JR");
return p;
}
}
Test.java
public class test{
@Test
public void test5(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) applicationContext.getBean("JR");
Car car = (Car) applicationContext.getBean("tt_car");
person.setCar(car);
System.out.print(person);
}
}
输出结果:
Person{car=Car{name='保时捷'}, name='我是JR', id='123456'}
多种装配Bean方式比较
基于XML配置 | 基于注解配置 | 基于Java类配置 | |
---|---|---|---|
Bean定义 | < bean id=”…” class=”…” /> | @Component衍生类 @Repository @Service @Controller | @Configuration标注类,@Bean标注提供Bean方法 |
Bean名称 | 通过 id或name指定 | @Component(“person”) | @Bean(“person”) |
Bean注入 | < property> 或者 通过p命名空间 | @Autowired 按类型注入@Qualifier按名称注入 | 在方法内部编写注入代码逻辑 |
Bean作用范围、生命过程 | scope属性 init-method destroy-method范围 | @Scope设置作用范围@PostConstruct 初始化@PreDestroy销毁 | @Scope指定范围在方法内部调用初始化方法 |
适合场景 | Bean来自第三方,使用其它命名空间 | Bean的实现类由用户自己开发 | 实例化Bean的逻辑比较复杂 |
XML配置文件
配置
添加context命名空间
引入context命名空间
<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">
</beans>
添加context:annotation-config标签
使@Resource、@ PostConstruct、@ PreDestroy、@Autowired注解生效
<context:annotation-config/>
使用多个配置文件
方式一
可以在创建ApplicationContext对象时传入多个配置文件
ApplicationContext applicationContext = new
ClassPathXmlApplicationContext("beans1.xml", "beans2.xml");
方式二
可以在配置文件中通过
<import resource="classpath:bean2.xml"/>
Spring整合Web开发
配置web.xml
- 将Spring容器初始化,交由web容器负责
- 配置核心监听器 ContextLoaderListener
- 配置全局参数 contextConfigLocation
用于指定Spring的框架的配置文件位置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
获得WebApplicationContext对象
因为Spring容器已经交由web容器初始化和管理。 获得WebApplicationContext对象,需要依赖ServletContext对象通常在Servlet中完成
WebApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
另一种方式
WebApplicationContext applicationContext = (WebApplicationContext)
getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
Spring集成Junit测试
- 导入Spring test测试jar包
- spring-test-3.2.0.RELEASE.jar
package cn.uhfun.web.bean;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration( locations = "classpath:applicationContext.xml")
public class SpringTest {
@Autowired
private Person person;
@Test
public void test(){
System.out.print(person.toString());
}
}
测试结果
Person{car=Car{name='奥迪'}, name='勒布朗', id='123456'}
Spring AOP
内容太多了,可以看下一篇Spring AOP入门。