Spring 實踐
標籤: Java與設計模式
Spring簡介
Spring是分層的JavaSE/EE Full-Stack輕量級開源框架.以IoC(Inverse of Control 控制反轉)和AOP(Aspect Oriented Programming 面向切面編程)爲內核, 取代EJB的臃腫/低效/脫離現實.
主頁http://spring.io/
IoC與DI
- IOC: 即控制反轉, 解決程序對象緊密耦合問題(方式: 工廠+反射+配置文件), 將程序中原來構造對象的操作,交給IoC容器, 當程序真正需要對象時,再找IoC容器獲取.
- DI: 即依賴注入, IoC容器需要爲程序提供依賴對象,而所依賴的對象又依賴於其他對象,因此可以一次獲取該對象所依賴的所有對象(如Controller依賴於Service, Service依賴於DAO, 因此Controller找Ioc容器獲取Service, 當IoC容器提供Service的同時,DAO也同時注入到Service中)
詳細可參考: IoC框架(依賴注入 DI)
Spring
- 方便解耦,簡化開發
Spring就是一個大工廠,可將所有對象創建
和依賴
關係的維護交給Spring管理; - AOP支持
Spring支持面向切面編程
,可以方便的實現對程序進行權限攔截/運行監控/緩存實現等功能; - 聲明式事務管理
只需通過配置就可完成對事務的管理,而無需手動編程; - 方便程序的測試
Spring提供對Junit4
支持,通過註解方便測試Spring程序; - 集成各種優秀框架
Spring不排斥各種優秀的開源框架,其內部提供了對各種優秀框架(如:MyBatis/iBatis/Hibernate等)的直接支持; - 降低JavaEE API的使用難度
Spring對JavaEE開發的一些API
(如JDBC/JavaMail/遠程調用等)提供了封裝,大大降低API使用難度;
初識Spring
需求- 模擬用戶註冊過程:
- Spring依賴
進行Spring的IoC/DI開發,只需要導入Spring最核心依賴:core/beans/context/expression,爲了看到DEBUG信息,我們還可以加上commons-logging, 而junit, 則是做單元測試必備的:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>4.2.0.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
</dependencies>
- Controller
/**
* Created by jifang on 15/12/5.
*/
public class UserController {
/**
* 依賴注入(DI): 在Spring構造UserController對象時, 可以同時將構造好的UserService對象注入(下同)
*/
private IUserService userService;
public IUserService getUserService() {
return userService;
}
public void setUserService(IUserService userService) {
this.userService = userService;
}
public void register(String userName, String password) {
System.out.println("用戶: " + userName + " 進行註冊...");
userService.register(userName, password);
}
}
- Service
public interface IUserService {
void register(String userName, String password);
}
public class UserServiceImpl implements IUserService {
private IUserDao userDao;
public IUserDao getUserDao() {
return userDao;
}
public void setUserDao(IUserDao userDao) {
this.userDao = userDao;
}
@Override
public void register(String userName, String password) {
System.out.println("用戶: " + userName + " 進行註冊...");
userDao.add(userName, passProcess(password));
}
// 對密碼進行加密處理
private String passProcess(String password) {
System.out.println("密碼: " + password + "加密處理...");
return password;
}
}
- DAO
public interface IUserDao {
void add(String userName, String password);
}
public class UserDaoImpl implements IUserDao {
@Override
public void add(String userName, String password) {
System.out.println("用戶: " + userName + ", 密碼: " + password + " 加入數據庫");
}
}
- 配置Bean
<?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="userDao" class="com.fq.first.dao.impl.UserDaoImpl">
</bean>
<bean id="userService" class="com.fq.first.service.impl.UserServiceImpl">
<property name="userDao" ref="userDao"></property>
</bean>
<bean id="userController" class="com.fq.first.controller.UserController">
<property name="userService" ref="userService"></property>
</bean>
</beans>
- 測試
/**
* Created by jifang on 15/12/5.
*/
public class UserControllerTest extends TestCase {
/**
* 加載Spring容器
*/
private ApplicationContext context;
@Before
public void setUp() throws Exception {
context = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
}
@Test
public void testRegister() throws Exception {
UserController controller = context.getBean("userController", UserController.class);
controller.register("翡青", "123");
}
}
- 在程序中通過ApplicationContext接口加載Spring容器, 獲取Spring工廠對象
ClassPathXmlApplicationContext
//讀取src下配置文件FileSystemXmlApplicationContext
//讀取WEB-INF下配置文件
- Spring對象工廠- BeanFactory與ApplicationContext:
ApplicationContext
是BeanFactory
的子接口,BeanFactory
是Spring最核心工廠接口。ApplicationContext
提供更多功能(如國際化處理/自動裝配Bean/不同應用層的Context實現)ApplicationContext
會在容器初始化時對其中管理Bean對象進行創建,BeanFactory
會在對象獲取時才進行初始化.
XML裝配
Spring提供了兩種裝配Bean的方式, XML與註解,其中XML方式Spring支持較早,現在在配置一些不是自己寫的Bean時(如數據庫連接池等從Jar包種引入的Bean)時是非常有用,而註解方式則常用於裝配自己寫的Bean.
三種實例化Bean的方式
- 構造器實例化
<!--使用構造器(默認無參)構造對象-->
<bean id="constructBean" class="com.fq.instance.ConstructBean">
</bean>
- 靜態工廠的靜態方法實例化
<!--使用靜態工廠構造對象, 注: class應爲工廠類-->
<bean id="staticBean" class="com.fq.instance.StaticBeanFactory" factory-method="getInstance">
</bean>
- 實例工廠的實例方法實例化
<!--使用實例工廠構造對象, 注: 要先實例化工廠-->
<bean id="beanFactory" class="com.fq.instance.InstanceBeanFactory">
</bean>
<!-- 再通過工廠對象的實例方法,構造目標對象 -->
<bean id="instanceBean" factory-bean="beanFactory" factory-method="getInstance">
</bean>
Bean作用域
類別 | 說明 |
---|---|
singleton | 在容器中僅存在一個實例(單例模式) |
prototype | 每次從容器中獲取都返回一個新的實例,即每次調用getBean()時,都執行一次構造方法(lazy,原型模式) |
request | 每次HTTP請求都會創建一個新的Bean,該作用域僅適用於WebApplicationContext 環境(不常用) |
session | 同一個Session 共享一個Bean,僅適用於WebApplicationContext 環境(不常用) |
globalSession | 一般用於Porlet應用環境,該作用域僅適用於WebApplicationContext 環境(不常用) |
- scope
<!--Spring使用scope標籤來制定bean的作用域(默認爲Singleton)-->
<bean id="singletonBean" class="com.fq.instance.SingletonBean" scope="singleton">
</bean>
<bean id="prototypeBean" class="com.fq.instance.PrototypeBean" scope="prototype">
</bean>
Bean生命週期
Spring初始化/銷燬bean時, 有時需要作一些處理工作, 因此Spring可以在創建和銷燬bean的時候調用bean的兩個生命週期方法;
/**
* Created by jifang on 15/12/6.
*/
public class LifecycleBean {
public LifecycleBean() {
System.out.println("Constructor ...");
}
/**
* 聲明週期方法需: 無參, 無返回值, 非static
*/
public void setUp() {
System.out.println("SetUp ...");
}
/**
* 同上
*/
public void tearDown() {
System.out.println("TearDown ...");
}
}
- 配置:
<!-- init-method屬性配置初始化方法,destroy-method屬性配置銷燬方法-->
<bean id="lifecycleBean" class="com.fq.bean.LifecycleBean" init-method="setUp" destroy-method="tearDown">
</bean>
- 測試
/**
* Created by jifang on 15/12/6.
*/
public class LifecycleBeanTest extends TestCase {
private ClassPathXmlApplicationContext context;
@Before
public void setUp() throws Exception {
context = new ClassPathXmlApplicationContext("spring/applicationContext.xml");
}
@Test
public void testLifecycle(){
LifecycleBean bean = context.getBean("lifecycleBean", LifecycleBean.class);
System.out.println(bean);
}
@After
public void tearDown() throws Exception {
// 必須手動調用context的close方法, 纔會執行bean的銷燬方法
context.close();
}
}
初始化方法與構造方法的區別?
1) 構造方法爲對象申請空間, 完成對象基本屬性的初始化;
2) 初始化方法主要完成對象複雜構造過程;
3) Java建議將對象複雜構造過程單獨抽取出初始化方法, 如javax.servlet.GenericServlet
的init
方法
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
後處理器
Spring提供了BeanPostProcessor
接口,在構造Bean對象執行對象初始化(init-method
)方法時可以對Bean進行處理;
/**
* Created by jifang on 15/12/6.
*/
public class PrintBeanProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// 可以根據beanName來決定對那個Bean進行後處理操作
if (beanName.equals("lifecycleBean")) {
System.out.println("後處理bean -- process before ...");
}
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
// 如果不制定beanName, 則默認處理所有Bean
System.out.println("後處理bean -- process after ...");
return bean;
}
}
- 配置
<!-- 爲Spring容器所用的bean, 不需配置id -->
<bean class="com.fq.processor.PrintBeanProcessor"></bean>
這樣在執行init-method[setUp]
的前後, 會分別執行BeanPostProcessor
中的兩個方法.
後處理器可以在對象構造過程中提供代理,這是AOP自動代理的核心.
XML依賴注入
Spring配置文件支持構造參數屬性注入和Setter方法屬性注入;
1. 構造參數注入
<bean id="bean" class="com.fq.di.Bean">
<!--
index 代表參數順序(從0開始)
name 代表參數名
type 參數類型
value 注入的參數值
ref 引用另一個bean元素的id
-->
<constructor-arg index="0" type="java.lang.String" value="fei_qing"></constructor-arg>
<constructor-arg index="1" type="java.lang.Double" value="3.14"></constructor-arg>
</bean>
2. Setter方法注入
<bean id="bean" class="com.fq.di.Bean">
<!--
name 屬性名(congSetter方法獲得)
value 注入的參數值
ref 引用的另一個bean的id
-->
<property name="name" value="fei_qing"></property>
<property name="price" value="88.8"></property>
</bean>
3. p名稱空間注入
P名稱空間在spring2.5版本後引入, 目的是爲了簡化屬性依賴注入(setter方法)
<!--
p:屬性名="XXX", 引入常量值
p:屬性名-ref="XXX", 引用其他Bean對象
-->
<bean id="bean" class="com.fq.di.Bean" p:name="feiqing" p:price="1188">
</bean>
4. SpEL表達式
在spring3.0之後,引入SpEL表達式,以簡化屬性注入.
#{表達式}
, 通過value
屬性注入: 可以引用一個Bean對象/對象屬性/對象方法… 詳細可參考Spring 表達式語言(SpEL)
- Bean
public class Car {
private String logo;
private double price;
private String owner;
public String getLogo() {
return logo;
}
public void setLogo(String logo) {
this.logo = logo;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getOwner() {
return owner;
}
public void setOwner(String owner) {
this.owner = owner;
}
}
public class Employ {
private String name;
private Car car;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
}
- 配置
<!--SpEL 使用#{}來引用/獲取對象-->
<bean id="car" class="com.fq.di.Car">
<property name="logo" value="#{'logo.pic'}"/>
<property name="price" value="#{18.8}"/>
<property name="owner" value="#{'feiqing'}"/>
</bean>
<bean id="employ" class="com.fq.di.Employ">
<!-- 可以直接使用value來引用到對象, 而不是ref -->
<property name="car" value="#{car}"/>
<!-- 可以直接引用一個對象的屬性 -->
<!--<property name="name" value="#{car.owner}"/>-->
<!-- 還可以直接調用對象的方法 -->
<property name="name" value="#{car.getOwner().toUpperCase()}"/>
</bean>
4. 集合屬性注入
java常見集合: List
/Set
/Map
/Properties
等, Spring爲每種集合都提供一個標籤進行注入;
- Bean
public class CollectionBean {
private List<String> list;
private Set<String> set;
private Map<String, String> map;
private Properties properties;
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public Set<String> getSet() {
return set;
}
public void setSet(Set<String> set) {
this.set = set;
}
public Map<String, String> getMap() {
return map;
}
public void setMap(Map<String, String> map) {
this.map = map;
}
public Properties getProperties() {
return properties;
}
public void setProperties(Properties properties) {
this.properties = properties;
}
}
- 配置
<bean id="collectionBean" class="com.fq.di.CollectionBean">
<property name="list">
<list>
<value>aa</value>
<value>bb</value>
<value>cc</value>
<value>dd</value>
</list>
</property>
<property name="set">
<set>
<value>11</value>
<value>12</value>
<value>11</value>
</set>
</property>
<property name="map">
<map>
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
</property>
<property name="properties">
<props>
<prop key="key1">value_1</prop>
<prop key="key2">value_2</prop>
</props>
</property>
</bean>
註解裝配
註解配置Bean
- 在需要Spring管理的類上添加
@Component
註解
(@Component
還可以指定組件名@Component(value = "xxx")
)
@Component
public class Bean {
private String name;
private Double price;
public Bean() {
}
public Bean(String name, Double price) {
this.name = name;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Double getPrice() {
return price;
}
public void setPrice(Double price) {
this.price = price;
}
}
- 引入context命名空間並批量掃描
<?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">
<context:component-scan base-package="com.fq.di"/>
</beans>
Spring細化@Component
以細分組件功能,提供了以下三個等價註解:
註解 | 說明 |
---|---|
@Controller | 控制器,web層組件 |
@Service | 業務類,業務層組件 |
@Repository | 持久層組件 |
Bean作用域
通過@Scope
註解指定作用域
@Component
@Scope("prototype")
public class Bean {
// ...
}
Bean生命週期
@PostConstruct |
初始化 |
---|---|
@PreDestroy |
銷燬 |
- Bean
public class Bean {
@PostConstruct
public void setUp(){
System.out.println("setUp ...");
}
@PreDestroy
public void tearDown(){
System.out.println("tearDown ...");
}
}
註解依賴注入
1. @Value
- 簡單類型
@Component
public class Bean {
@Value("feiqing")
private String name;
@Value("88.88")
private Double price;
// ....
}
- 複雜屬性(使用SpEL表達式)
@Component
public class Bean {
@Value("#{car}")
private Car car;
// ...
}
2. @Autowired
@Autowired
默認按照類型進行注入(如果容器中存在兩個相同類型對象,則@Autowired
無法注入)
@Component
public class Bean {
@Autowired
private Car car;
// ....
}
@Autowired
+@Qualifier
指定注入Bean的id
@Component
public class Bean {
@Autowired
@Qualifier("car")
private Car car;
// ...
}
3. @Resource
Spring支持JSR-250規範,可以使用@Resource()
進行屬性注入,功能和@Autowired
相同:
@Controller(value = "bean")
public class Bean {
@Resource(name = "car")
private Car car;
//...
}
註解/XML混合
Bean定義使用XML,Bean關係依賴注入使用註解:
需要在applicationContext.xml中配置:
<context:annotation-config/>
該配置可以使@Resource
、@PostConstruct
、@PreDestroy
、@Autowired
註解生效.
如果在配置文件中使用了
<context:component-scan base-package="xxx.xx"/>
則具有了<context:annotation-config/>
的效果, 不必再單獨配置.