文章目錄
- 概述
-
- 介紹
- 環境搭建
-
- 建立項目
- 添加依賴
- 編寫`Bean`
- 添加配置檔案
- 執行個體化`Bean`
- `IOC`
-
- 概念
- 配置檔案`Bean`管理
-
- 構造器注入
-
- 無參構造
- 索引注入
- 類型注入
- 參數名注入
- 引用注入
- `Set`注入
- 注入特殊值
-
- 注入`null`
- 注入特殊符号
- 注入級聯屬性
- 注入數組
- 注入`List`
- 注入`Map`
- `Bean`的作用域
- `Bean`的生命周期
- 自動裝配
- `Bean`的别名
- 導入外部`Bean`
- 注解`Bean`管理
-
- 元件掃描
- 示例
- 注解說明
- 掃描過濾器
- 屬性注入
-
- `@Value`
- `@Autowired`
- `@Qualifier`
- `@Resource`
- 完全注解開發
- `AOP`
-
- 概念
- 切入點表達式
- 切面操作
-
- 添加依賴
- 開啟掃描器
-
- 使用配置檔案
- 使用注解
- 啟用代理
-
- 使用配置檔案
- 使用注解
- 被增強類
- 增強類
- 切入點抽取
- 注解詳解
-
- 被增強類
- `@Pointcut`
- `@Before`
- `@After`
- `@Around`
- `@AfterReturning`
- `@AfterThrowing`
- 連接配接點
-
- `JoinPoint`接口
- `ProceedingJoinPoint`接口
- `SpringMVC`
-
- 項目建立
-
- `Maven`多子產品
- 添加依賴
- 核心配置
- 改變資源路徑
- 建立控制類
- 視圖解析器
- 請求處理過程
- 處理器深入
-
- `@RequestMapping`
-
- `@RequestMapping`的定義
- 請求路徑
- 請求方法
- 請求參數
- 請求頭
- `@RequestMapping`修飾類
- 處理器參數
-
- `Servlet`參數
- 請求參數
- 編碼過濾器
- `@RequestParam`
- 對象參數
- 處理器傳回
-
- 空傳回
- 傳回靜态資源
- 傳回`JSON`
- 傳回`List`
- 傳回`Map`
- 傳回`ModelAndView`
- `@ResponseBody`修飾類
- `@RestController`
- 異常處理
-
- 全局異常處理類
- 完善配置
- 自定義異常類
- 控制類抛出異常
- 攔截器
-
- `HandlerInterceptor`接口
- 定義攔截器
- 注冊攔截器
- 攔截器鍊
- 攔截器與過濾器的差別
- `RESTful`
-
- `REST`風格
- `RESTful`注解
- 資源設計規則
- 動作設計規則
- 路徑傳參
- 傳回結果規則
- `RESTful-API`
概述
介紹
Spring
是一個開源的
JavaEE
應用程式架構,主要核心是
IOC(Inversion Of Control,控制反轉)
和
AOP(Aspect Oriented Programming,面向切面程式設計)
技術。參照官方文檔。
環境搭建
建立項目
建立
Maven
項目,選擇普通
Java
項目模闆:
1.依次點選File -> New -> Project
2.在彈出的視窗中選擇Maven
3.選擇Project SDK,指定JDK版本
4.勾選Create from archetype(從模闆建立),
選擇org.apache.maven.archetypes:maven-archetype-quickstart
5.點選Next,輸入項目名和項目位置
6.在Artifact Coordinates中輸入倉庫資訊:
GroupId指定項目組辨別,一般為"com.公司名";
ArtifactId指定目前項目在項目組下的辨別,一般為"項目名";
Version指定工程版本,格式為"X.X.X-裡程碑"
7.配置Maven環境(如果之前IDEA成功配置了Maven環境,此步會自動配置完善)
8.為src/main和src/test中添加resources目錄:
(1).建立resources目錄
(2).右擊建立的resources目錄,選擇Mark Directory as,選擇Resources Root
添加依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.5.RELEASE</version>
</dependency>
編寫 Bean
Bean
建立包并添加
Bean
類:
package com.oner.bean;
public class User {
private String name;
private String password;
public User(){super();}
public User(String name,String password) {
this.name = name;
this.password = password;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getPassword() {return password;}
public void setPassword(String password) {this.password = password;}
@Override
public String toString() {
return "name is : " + name + " , " + "password is : " + password +" .";
}
}
添加配置檔案
在
src/main/resources
目錄下添加配置檔案
beans.xml
(官方模闆):
beans.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 将Bean加載到容器中管理,id為Bean指定唯一引用辨別,class指定Bean的全類名 -->
<bean id="user" class="com.oner.beans.User">
<!-- property标簽用于設定Bean對象的屬性值,name指定屬性名,value指定屬性值 -->
<property name="name" value="Tony"/>
<property name="password" value="123456"/>
</bean>
</beans>
執行個體化 Bean
Bean
public class App {
public static void main(String[] args) {
/* 擷取Spring上下文環境(加載配置檔案),ClassPathXmlApplicationContext()
為可變形參構造器,可傳入多個配置檔案 */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 擷取Spring容器執行個體化好的Bean對象,getBean()傳入bean的id */
User user = (User)context.getBean("user");
/* 調用Bean對象的方法 */
System.out.println(user);
}
}
IOC
IOC
概念
IOC(Inversion Of Control,控制反轉)
是一種設計思想,在沒有
IOC
的程式中,對象的建立以及對象之間的調用關系寫死在程式中,對象的建立和對象間的調用由程式控制;使用
IOC
後,對象的建立及對象間的調用由
IOC
容器控制,降低代碼的耦合度。
Spring
就是一種
IOC
容器。
配置檔案 Bean
管理
Bean
Bean
管理主要分為建立對象和注入屬性,
Bean
管理的方式有使用
XML
配置檔案和使用注解。
構造器注入
使用
bean
标簽建立對象時,可以使用有參構造器注入屬性。
無參構造
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 通過無參構造器建立Bean對象 -->
<bean id="user" class="com.oner.beans.User"></bean>
</beans>
索引注入
索引注入适用于定義了有參構造器的
Bean
類。
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<!-- 通過構造器的參數索引注入屬性 -->
<constructor-arg index="0" value="Tony"/>
<constructor-arg index="1" value="123456"/>
</bean>
</beans>
類型注入
類型注入适用于定義了有參構造器的
Bean
類。
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<!-- 通過構造器的參數類型注入屬性,多個同類型的參數按參數排列順序注入;
type必須傳入類的全限定名 -->
<constructor-arg type="java.lang.String" value="Tony"/>
<constructor-arg type="java.lang.String" value="123456"/>
</bean>
</beans>
參數名注入
參數名注入适用于定義了有參構造器的
Bean
類。
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<!-- 通過構造器的參數名注入屬性,name指定構造器參數名,value指定參數值 -->
<constructor-arg name="name" value="Tony"/>
<constructor-arg name="password" value="123456"/>
</bean>
</beans>
引用注入
引用注入适用于定義了有參構造器且有參構造器的參數類型為其它
Bean
類的
Bean
類。
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="bean1" class="com.oner.beans.Bean1">
<!-- 通過索引、類型或參數名等形式注入其它Bean類型對象,ref指定被引用Bean對象的id或别名 -->
<constructor-arg index="..." ref="bean2"/>
<constructor-arg type="..." ref="bean3"/>
<constructor-arg name="..." ref="bean3"/>
</bean>
<bean id="bean2" class="com.oner.beans.Bean2">...</bean>
<bean id="bean3" class="com.oner.beans.Bean3">...</bean>
</beans>
Set
注入
Set
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<!-- Set注入屬性值 -->
<property name="name" value="Tony"/>
<!-- Set注入其它Bean類型對象,ref指定被引用Bean對象的id或别名 -->
<property name="department" ref="user1"/>
</bean>
</beans>
注入特殊值
注入 null
null
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<!-- 注入null -->
<property name="name">
<null/>
</property>
</bean>
</beans>
注入特殊符号
XML
的屬性值中不可出現類似于
<,>,&...
等的符号,可以使用轉義字元,也可以使用
CDATA
.
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<!-- "<"是"<"的轉義,">"是">"的轉義,"&"是"&"的轉義 -->
<property name="password" value="<123&456>"/>
<!-- 使用CDATA包含特殊字元,形式為 : <![CDATA[字元串]]> -->
<property name="password">
<value><![CDATA[<123&456>]]></value>
</property>
</bean>
</beans>
注入級聯屬性
一個
Bean
類中的其它
Bean
類型屬性稱為級聯屬性,可以使用嵌套
bean
标簽實作級聯屬性的注入。
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<property name="department">
<!-- 嵌套bean标簽實作級聯屬性的注入 -->
<bean id="departmentA" class="com.oner.beans.Department">
<property name="name" value="sercurity"/>
</bean>
</property>
</bean>
</beans>
注入數組
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<property name="skill">
<!-- 使用array标簽注入數組 -->
<array>
<value>Java</value>
<value>JavaScript</value>
<value>C</value>
<!-- 對于成員是非普通類型的數組,需要引用其它Bean對象進行成員注入;
bean屬性指定被引用Bean對象的id或别名 -->
<ref bean="bean1"/>
</array>
</property>
</bean>
</beans>
注入 List
List
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<property name="hobby">
<!-- 使用list标簽注入List集合 -->
<list>
<value>book</value>
<value>tv</value>
<value>game</value>
<!-- 對于成員是非普通類型的集合,需要引用其它Bean對象進行成員注入;
bean屬性指定被引用Bean對象的id或别名 -->
<ref bean="bean1"/>
</list>
</property>
</bean>
</beans>
注入 Map
Map
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 建立對象 -->
<bean id="user" class="com.oner.beans.User">
<property name="grades">
<!-- 使用map标簽注入Map -->
<map>
<!-- entry标簽指定鍵值對 -->
<entry key="math" value="149"/>
<entry key="physics" value="99"/>
<!-- 對于key和value是非普通類型的Map,需要引用其它Bean對象進行成員注入;
key-ref屬性指定被引用的鍵的Bean對象的id或别名;
value-ref屬性指定被引用的值的Bean對象的id或别名 -->
<entry key-ref="bean1" value-ref="bean2"/>
</map>
</property>
</bean>
</beans>
Bean
的作用域
Bean
在
Spring
中,
Bean
對象預設為單執行個體對象,即在不同位置使用同一個
id
的
Bean
對象均為同一個對象。可以通過
bean
标簽的
scope
屬性設定
Bean
對象是單執行個體還是多執行個體。
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- "singleton"指定Bean對象為單執行個體,Spring加載配置檔案時即建立對象 -->
<bean id="..." class="..." scope="singleton">...</bean>
<!-- "prototype"指定Bean對象為多執行個體,調用getBean()方法時建立對象 -->
<bean id="..." class="..." scope="prototype">...</bean>
<!-- "request"指定Bean對象建立時放置到request域中 -->
<bean id="..." class="..." scope="request">...</bean>
<!-- "session"指定Bean對象建立時放置到session域中 -->
<bean id="..." class="..." scope="session">...</bean>
</beans>
Bean
的生命周期
Bean
Bean
對象在
Spring
容器調用構造器時建立,在
Spring
容器關閉時銷毀,可以通過配置檔案監聽
Bean
的建立與銷毀。在
Bean
中添加以下方法:
public class User {
public void userInit() {
System.out.println("Hello Spring.");
}
public void userDestroy() {
System.out.println("Bye bye Spring.");
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("beans.xml");
/* 手動銷毀Bean對象 */
context.close();
}
}
在配置檔案中配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- init-method屬性指定初始化方法名;destroy-method屬性指定銷毀方法名 -->
<bean id="user"
class="com.oner.User"
init-method="userInit"
destroy-method="userDestroy">
...
</bean>
</beans>
可在
main
方法中調用方法手動銷毀
bean
:
public static void main(String[] args) {
/* 擷取Spring上下文環境(加載配置檔案) */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 使用别名擷取Bean對象 */
User user = (User)context.getBean("user");
System.out.println(user);
/* 手動銷毀bean */
((ClassPathXmlApplicationContext)context).close();
}
自動裝配
Spring
可以根據
Bean
的屬性自動注入其它
Bean
:
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 設定autowire屬性為"byName"指定将id值與指定Bean類屬性名相同
的其它Bean對象注入到指定Bean對象的指定屬性中 -->
<bean id="user" class="com.oner.beans.User" autowire="byName"></bean>
<bean id="department" class="...">...</bean>
<!-- 設定autowire屬性為"byType"指定将class指定的類型與指定Bean類屬性類型相同
的其它Bean對象注入到指定Bean對象的指定屬性中 -->
<bean id="user" class="com.oner.beans.User" autowire="byType"></bean>
<bean id="..." class="com.oner.beans.Department">...</bean>
</beans>
Bean
的别名
Bean
可以使用
alias
标簽為
Bean
對象起别名,即使用
alias
标簽配置的别名代替
bean
标簽配置的
id
.
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<bean id="user" class="com.oner.beans.User">
<!-- name指定構造器參數名,value指定參數值 -->
<constructor-arg name="name" value="Tony"/>
<constructor-arg name="password" value="123456"/>
</bean>
<!-- name指定Bean對象的id,alias指定别名 -->
<alias name="user" alias="userInfo"/>
</beans>
使用别名擷取
Bean
對象:
public class App {
public static void main(String[] args) {
/* 擷取Spring上下文環境(加載配置檔案) */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 使用别名擷取Bean對象 */
User userInfo = (User)context.getBean("userInfo");
/* 調用Bean對象的方法 */
System.out.println(userInfo);
}
}
使用
bean
标簽的
name
屬性也可以為
Bean
對象起别名:
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- name屬性可為Bean對象起多個别名,多個别名之間可以使用",/;/空格"分隔 -->
<bean id="user" class="com.oner.beans.User" name="a,b;c d">
<!-- name指定構造器參數名,value指定參數值 -->
<constructor-arg name="name" value="Tony"/>
<constructor-arg name="password" value="123456"/>
</bean>
</beans>
使用别名擷取
Bean
對象:
public class App {
public static void main(String[] args) {
/* 擷取Spring上下文環境(加載配置檔案) */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 使用别名擷取Bean對象 */
User a = (User)context.getBean("a");
User b = (User)context.getBean("b");
User c = (User)context.getBean("c");
User d = (User)context.getBean("d");
System.out.println(a);
System.out.println(b);
System.out.println(c);
System.out.println(d);
}
}
導入外部 Bean
Bean
使用
import
标簽可以導入外部
Bean
,将多個配置檔案合為一個:
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 使用import标簽導入外部配置檔案,将多個配置檔案合為一個 -->
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>
</beans>
示例:
beans1.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<bean id="user1" class="com.oner.beans.User">
<property name="name" value="Tony"/>
<property name="password" value="abcdef"/>
</bean>
</beans>
beans2.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<bean id="user2" class="com.oner.beans.User">
<property name="name" value="Steven"/>
<property name="password" value="123456"/>
</bean>
</beans>
spring.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 使用import标簽導入外部配置檔案,将多個配置檔案合為一個 -->
<import resource="beans1.xml"/>
<import resource="beans2.xml"/>
</beans>
加載主配置檔案,擷取
Bean
對象:
public class App {
public static void main(String[] args) {
/* 擷取Spring上下文環境(加載配置檔案) */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 使用别名擷取Bean對象 */
User user1 = (User)context.getBean("user1");
User user2 = (User)context.getBean("user2");
System.out.println(user1);
System.out.println(user2);
}
}
注解 Bean
管理
Bean
Spring
中用于執行個體化
Bean
的注解有
@Component,@Service,@Controller,@Repository
,它們的功能完全一緻。推薦
@Component
用在任意普通
Bean
類上,
@Service
用在
service
層,
@Controller
用在
controller
層,
@Repository
用在
dao
層。
元件掃描
使用注解管理
Bean
之前,需要開啟
Spring
元件掃描器。元件掃描器的作用是掃描指定包下的所有類,掃描到使用執行個體化
Bean
的注解的類,即建立該類的對象。開啟方法為:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 步驟1:在beans标簽中添加context命名空間(IDEA可自動導入) -->
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 步驟2:指定基礎掃描包,多個包之間使用","分隔 -->
<context:component-scan base-package="com.xxx.package1,com.xxx.package2"/>
</beans>
開啟元件掃描器後,
Spring
将掃描指定的基礎掃描包下的所有類或資源,掃描到使用相關注解的類,即建立該類的對象。
示例
指定基礎掃描包:
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 指定基礎掃描包 -->
<context:component-scan base-package="com.oner.beans"/>
</beans>
使用注解:
package com.oner.bean;
/* value屬性指定Bean對象的id,如果省略不寫,預設為類名首字母小寫 */
@Component(value = "user")
public class User {
private String name;
private String password;
public User(){super();}
public User(String name,String password) {
this.name = name;
this.password = password;
}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public String getPassword() {return password;}
public void setPassword(String password) {this.password = password;}
@Override
public String toString() {
return "name is : " + name + " , " + "password is : " + password +" .";
}
}
擷取
Bean
對象:
public class App {
public static void main(String[] args) {
/* 擷取Spring上下文環境(加載配置檔案) */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 擷取Bean對象 */
User user = (User)context.getBean("user");
user.setName("Tony");
user.setPassword("123456");
System.out.println(user);
}
}
注解說明
@Component,@Service,@Controller,@Repository
都隻有一個
value
屬性,
value
屬性對應配置檔案中
bean
标簽的
id
屬性,通過
getBean()
方法傳入對應的
value
值可擷取
Bean
對象。不傳入
value
值,預設為類名小寫。
public @interface Component {
String value() default "";
}
@Component
public @interface Service {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Component
public @interface Controller {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
@Component
public @interface Repository {
@AliasFor(
annotation = Component.class
)
String value() default "";
}
掃描過濾器
不指定情況下,
Spring
使用預設掃描過濾器,即掃描指定包下的所有類。可以自定義掃描過濾器:
<?xml version="1.0" encoding="UTF-8"?>
<beans ... >
<!-- 不指定情況下使用預設掃描過濾器 -->
<context:component-scan base-package="com.oner.beans"/>
<!-- user-default-filter="false"指定不使用預設掃描過濾器 -->
<context:component-scan base-package="com.oner.beans" user-default-filter="false">
<!-- include-filter指定過濾出的類被掃描;
type="annotation"指定過濾類型為根據注解過濾;
expression指定掃描表達式,根據注解過濾時,可指定過濾的注解類型 -->
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Service"/>
<!-- exclude-filter指定過濾出的類不被掃描;
type="annotation"指定過濾類型為根據注解過濾;
expression指定掃描表達式,根據注解過濾時,可指定過濾的注解類型 -->
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Service"/>
</context:component-scan>
</beans>
屬性注入
@Value
@Value
@Value
用于
Set
注入,修飾屬性并直接指定屬性值。示例:
@Component(value = "bean")
public class Bean {
@Value(value="Tony")
private String name;
@Value(value="123456")
private String password;
public void test() {
System.out.println("name is : " + name + " , " + " password is : " + password + " .");
}
}
擷取
Bean
對象:
public class App {
public static void main(String[] args) {
/* 擷取Spring上下文環境(加載配置檔案) */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 擷取Bean對象 */
Bean bean = (Bean)context.getBean("bean");
bean.test();
}
}
@Autowired
@Autowired
使用
@Autowired
注解可實作根據類型自動注入屬性,
@Autowired
需要加載對應的屬性上。示例:
@Component(value = "bean1")
public class Bean1 {
/* @Autowired注解是屬性注解 */
@Autowired
private Bean2 bean;
public void test() {
bean.test();
}
}
自動裝配
Bean
:
/* 被@Autowired自動注入給指定屬性的類必須使用相應注解執行個體化 */
@Component(value = "bean2")
public class Bean2 {
public void test() {
System.out.println("Hello Spring!");
}
}
擷取
Bean
對象:
public class App {
public static void main(String[] args) {
/* 擷取Spring上下文環境(加載配置檔案) */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 擷取Bean對象 */
Bean1 bean = (Bean1)context.getBean("bean1");
bean.test();
}
}
@Autowired
注解的工作機制是掃描指定包下使用執行個體化注解的類,掃描到與
@Autowired
注解修飾的屬性同類型的類,将該類的執行個體賦給屬性。
@Autowired
注解的缺點是無法應付指定包下具有多個同類型
Bean
的場景,例如實作同一個接口或繼承同一個類的多個
Bean
@Qualifier
@Qualifier
@Qualifier
注解可以實作根據執行個體化注解指定的
Bean
的
value
值注入屬性。
@Qualifier
用于修飾
Bean
的屬性,掃描到的
Bean
類使用執行個體化注解指定的
value
值與
@Qualifier
注解指定的
value
值相同時,使用該
Bean
類的執行個體注入屬性。
@Qualifier
可與
@Autowired
配合使用,提高準确性。例有以下場景:
public interface IBean {
void test();
}
IBeanImpl1
實作接口
IBean
:
@Component(value = "bean1")
public class IBeanImpl1 implements IBean {
@Override
public void test() {
System.out.println("Hello Spring,I am IBeanImpl1");
}
}
IBeanImpl2
實作接口
IBean
:
@Component(value = "bean2")
public class IBeanImpl2 implements IBean {
@Override
public void test() {
System.out.println("Hello Spring,I am IBeanImpl2");
}
}
Bean
使用
IBean
類型注入屬性:
@Component(value = "bean")
public class Bean {
@Autowired
@Qualifier(value = "bean1")
private IBean bean;
public void test() {
bean.test();
}
}
擷取
Bean
對象:
public class App {
public static void main(String[] args) {
/* 擷取Spring上下文環境(加載配置檔案) */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 擷取Bean對象 */
Bean bean = (Bean)context.getBean("bean");
bean.test();
}
}
@Resource
@Resource
@Resource
可以看作為
@Autowired
與
@Qualifier
的合體,即可根據類型注入,也可根據
value
值注入。
@Component(value = "bean")
public class Bean {
/* 根據類型注入 */
@Resource
private Bean1 bean1;
/* 根據value值注入 */
@Resource(name = "bean3")
private Bean2 bean2;
public void test() {
bean1.test();
bean2.test();
}
}
完全注解開發
掃描器的配置也可以使用注解實作,進而實作完全注解開發。定義配置類:
/* @Configuration指定類為Spring的配置類;
@ComponentScan的basePackages屬性指定基礎掃描包,多個基礎掃描包通過數組形式指定 */
@Configuration
@ComponentScan(basePackages = {"com.xxx.package1","com.xxx.package2"})
public class SpringConfiguration {}
加載配置類:
public class App {
public static void main(String[] args) {
/* 使用AnnotationConfigApplicationContext類加載配置類 */
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfiguration.class);
/* 擷取Bean對象 */
Bean bean = (Bean)context.getBean("bean");
bean.test();
}
}
AOP
AOP
概念
AOP(Aspect Oriented Programming,面向切面程式設計)
底層使用動态代理技術,用于在不改變原有代碼的情況下增強代碼功能。
Spring
基于
AspectJ
架構實作
AOP
功能。
AOP
中的常見術語:
1.連接配接點(JoinPoint):類中可以被增強的方法稱為連接配接點
2.切入點(PointCut):連接配接點中實際被增強的方法稱為切入點
3.增強(Advice):切入點中增加的邏輯部分稱為增強
(1).前置增強:在原方法執行之前執行的增強為前置增強
(2).後置增強:在原方法執行之後執行的增強為後置增強
(3).環繞增強:在原方法執行之前及之後執行的增強為環繞增強
(4).異常增強:原方法發生異常之後執行的增強為異常增強(等同于catch(...){...})
(5).最終增強:原方法執行之後必定執行的增強為最終增強(等同于finally{...})
4.切面(Aspect):将增強應用到切入點的動作稱為切面
切入點表達式
切入點表達式是一個字元串資訊,用于指定切入點的資訊(例如切入點的權限、傳回、所在類的路徑及參數清單等資訊),切入點表達式使得
Spring
能夠精确定位到目标切入點。切入點表達式的基本形式為:
/* 基本形式 */
execution([權限修飾] [傳回類型] [類全路徑.方法名稱](參數清單));
/* 例 */
execution(private int com.xxx.test.add(int,java.lang.String));
/* 使用通配 */
/* 省略權限修飾表示任意權限 */
execution(int com.xxx.test.add(int,java.lang.String));
/* "*"通配傳回類型表示任意傳回類型 */
execution(* com.xxx.test.add(int,java.lang.String));
/* "*"通配方法名表示指定包下的所有方法 */
execution(* com.xxx.test.*(int,java.lang.String));
/* "*"通配類名表示指定包下的類的指定方法 */
execution(* com.xxx.*.*(int,java.lang.String));
/* "*"通配參數清單表示任意數量及類型的參數,前提是必須有參數 */
execution(* com.xxx.test.add(*));
/* ".."通配參數清單表示任意數量及類型的參數,有參無參均可 */
execution(* com.xxx.test.add(..));
切面操作
添加依賴
使用
Spring
的
AOP
操作需要添加以下依賴:
<!-- 添加aspectjweaver依賴 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
開啟掃描器
使用配置檔案
引入命名空間,指定基礎掃描包:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 步驟1:在beans标簽中添加context命名空間(IDEA可自動導入) -->
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 步驟2:指定基礎掃描包,多個包之間使用","分隔 -->
<context:component-scan base-package="com.xxx.package1,com.xxx.package2"/>
</beans>
加載配置檔案:
public class App {
public static void main(String[] args) {
/* 加載配置檔案 */
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
/* 擷取Bean對象 */
Bean bean = (Bean)context.getBean("bean");
}
}
使用注解
定義配置類:
/* @Configuration指定類為Spring的配置類;
@ComponentScan的basePackages屬性指定基礎掃描包,多個基礎掃描包通過數組形式指定 */
@Configuration
@ComponentScan(basePackages = {"com.xxx.package1","com.xxx.package2"})
public class SpringConfiguration {
// do nothing
}
加載配置類:
public class App {
public static void main(String[] args) {
/* 使用AnnotationConfigApplicationContext類加載配置類 */
ApplicationContext context =
new AnnotationConfigApplicationContext(SpringConfiguration.class);
/* 擷取Bean對象 */
Bean bean = (Bean)context.getBean("bean");
}
}
啟用代理
使用配置檔案
引入命名空間,啟用生成代理對象:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 步驟1:在beans标簽中添加aop命名空間(IDEA可自動導入) -->
<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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 開啟AspectJ自動代理 -->
<aop:aspectj-autoproxy/>
</beans>
使用注解
定義配置類:
/* @Configuration指定類為Spring的配置類 */
@Configuration
/* @ComponentScan的basePackages屬性指定基礎掃描包,多個基礎掃描包通過數組形式指定 */
@ComponentScan(basePackages = {"com.xxx.package1","com.xxx.package2"})
/* @EnableAspectJAutoProxy的proxyTargetClass屬性用于開啟自動代理 */
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfiguration {
// do nothing
}
被增強類
@Component(value = "bean")
public class Bean {
/* 切入點 */
public void test() {
System.out.println("Hello Spring!");
}
}
增強類
/* @Component注解用于生成執行個體 */
@Component(value = "beanProxy")
/* @Aspect注解用于生成代理對象 */
@Aspect
public class BeanProxy {
/* 前置增強 */
@Before(value = "execution(* com.oner.beans.Bean.test(..))")
public void before() {
System.out.println("before...");
}
/* 後置增強 */
@After(value = "execution(* com.oner.beans.Bean.test(..))")
public void after() {
System.out.println("after...");
}
/* 後置增強:傳回後執行 */
@AfterReturning(value = "execution(* com.oner.beans.Bean.test(..))")
public void afterReturning() {
System.out.println("after returning...");
}
/* 後置增強:異常後執行 */
@AfterThrowing(value = "execution(* com.oner.beans.Bean.test(..))")
public void afterThrowing() {
System.out.println("after throwing...");
}
/* 環繞增強 */
@Around(value = "execution(* com.oner.beans.Bean.test(..))")
public void around(ProceedingJoinPoint pjp) {
System.out.println("before proceeding...");
try{
/* proceed()方法用于執行被增強方法 */
pjp.proceed();
} catch(Throwable t) {
t.printStackTrace();
}
System.out.println("after proceeding...");
}
}
各種增強的執行順序:
無異常:
1.前置環繞增強(@Around,proceed()之前)
2.前置增強(@Before)
3.切入點
4.後置傳回增強(@AfterReturning)
5.後置增強(@After)
6.後置環繞增強(@Around,proceed()之後)
有異常:
1.前置環繞增強(@Around,proceed()之前)
2.前置增強(@Before)
3.切入點
4.後置異常增強(@AfterThrowing)
5.後置增強(@After)
6.後置環繞增強(@Around,proceed()之後)
切入點抽取
可以将切入點表達式抽取為一個對象,簡化代碼:
/* @Component注解用于生成執行個體 */
@Component(value = "beanProxy")
/* @Aspect注解用于生成代理對象 */
@Aspect
public class BeanProxy {
/* 定義空方法,使用@Pointcut注解抽取切入點 */
@Pointcut(value = "execution(* com.oner.beans.Bean.test(..))")
public void pointcut() {}
/* 前置增強 */
/* 傳入抽取方法名引用切入點 */
@Before(value = "pointcut()")
public void before() {
System.out.println("before...");
}
/* 後置增強 */
@After(value = "pointcut()")
public void after() {
System.out.println("after...");
}
/* 後置增強:傳回後執行 */
@AfterReturning(value = "pointcut()")
public void afterReturning() {
System.out.println("after returning...");
}
/* 後置增強:異常後執行 */
@AfterThrowing(value = "pointcut()")
public void afterThrowing() {
System.out.println("after throwing...");
}
/* 環繞增強 */
@Around(value = "pointcut()")
public void around(ProceedingJoinPoint pjp) {
System.out.println("before proceeding...");
try{
/* proceed()方法用于執行被增強方法 */
pjp.proceed();
} catch(Throwable t) {
t.printStackTrace();
}
System.out.println("after proceeding...");
}
}
注解詳解
被增強類
@Component(value = "bean")
/* 修改被增強類 */
public class Bean {
public String test(int a,int b) {
return a+b+"";
}
}
@Pointcut
@Pointcut
@Pointcut
注解的定義為:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Pointcut {
/* 指定切入點表達式 */
String value() default "";
/* 指定抽取方法的參數名 */
String argNames() default "";
}
@Pointcut
注解進階用法:
/* 切入點表達式中的切入訓示"&&args(...)"用于為切入點參數指定别名;
argNames屬性用于指定抽取方法的參數名;
切入點參數别名 && argNames指定的抽取方法參數名 && 抽取方法參數名 三者必須相同;
通過這種方式可以将 抽取方法參數 映射到 切入點參數 提供增強方法使用 */
@Pointcut(value = "execution(* com.oner.beans.Bean.test(..)) && args(arg1,arg2)",
argNames = "arg1,arg2")
public void pointcut(int arg1,int arg2) {}
增強方法通過
@Pointcut
注解擷取切入點參數:
/* 傳入抽取方法名引用切入點;
抽取方法傳入增強方法參數名;
通過這種方式可以将 增強方法參數 映射到 切入點參數 */
@Before(value = "pointcut(var1,var2)")
public void before(int var1,int var2) {
/* 通過增強方法參數擷取切入點參數 */
System.out.println("argument 1 is " + var1 + " , argument 2 is " + var2);
}
@Before
@Before
@Before
注解的定義為:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Before {
/* value用于指定切入點表達式 */
String value();
/* argNames用于擷取切入點參數清單 */
String argNames() default "";
}
擷取切入點參數:
/* @Component注解用于生成執行個體 */
@Component(value = "beanProxy")
/* @Aspect注解用于生成代理對象 */
@Aspect
public class BeanProxy {
/* 切入訓示"&&args(...)"用于為切入點的參數指定别名;
argNames屬性用于指定抽取方法的參數名;
通過這種方式可以将 切入點參數 映射到 抽取方法參數 */
@Before(value = "execution(* com.oner.beans.Bean.test(..)) && args(arg1,arg2)",
argNames = "arg1,arg2")
public void before(int arg1,int arg2) {
System.out.println("before...");
/* 通過增強方法參數擷取切入點參數 */
System.out.println("argument 1 is " + arg1 + " , argument 2 is " + arg2);
}
}
使用切入點抽取方法擷取切入點參數:
/* @Component注解用于生成執行個體 */
@Component(value = "beanProxy")
/* @Aspect注解用于生成代理對象 */
@Aspect
public class BeanProxy {
/* 定義空方法,使用@Pointcut注解抽取切入點;
切入訓示"&&args(...)"用于為切入點的參數指定别名;
@Pointcut注解的argNames屬性用于指定抽取方法的參數名;
通過這種方式可以将 切入點參數 映射到 抽取方法參數 */
@Pointcut(value = "execution(* com.oner.beans.Bean.test(..)) && args(arg1,arg2)",
argNames = "arg1,arg2")
public void pointcut(int arg1,int arg2) {}
/* 前置增強 */
/* 傳入抽取方法名引用切入點;
抽取方法傳入增強方法參數名;
通過這種方式可以将 增強方法參數 映射到 切入點參數 */
@Before(value = "pointcut(var1,var2)")
public void before(int var1,int var2) {
System.out.println("before...");
/* 通過增強方法參數擷取切入點參數 */
System.out.println("argument 1 is " + var1 + " , argument 2 is " + var2);
}
}
@After
@After
@After
注解的定義為:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface After {
/* 指定切入點表達式 */
String value();
/* 指定增強方法參數名 */
String argNames() default "";
}
@After
注解的進階用法與
@Before
注解相同。
@Around
@Around
@Around
注解的定義為:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Around {
/* 指定切入點表達式 */
String value();
/* 指定增強方法參數名 */
String argNames() default "";
}
@Around
注解的進階用法與
@Before
注解相同。
@AfterReturning
@AfterReturning
@AfterReturning
注解的定義為:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AfterReturning {
/* 指定切入點表達式 */
String value() default "";
String pointcut() default "";
/* 用于擷取切入點傳回值 */
String returning() default "";
/* 用于擷取切入點參數 */
String argNames() default "";
}
@AfterReturning
注解的進階用法:
/* @Component注解用于生成執行個體 */
@Component(value = "beanProxy")
/* @Aspect注解用于生成代理對象 */
@Aspect
public class BeanProxy {
/* 定義空方法,使用@Pointcut注解抽取切入點;
切入訓示"&&args(...)"用于為切入點的參數指定别名;
@Pointcut注解的argNames屬性用于指定抽取方法的參數名;
通過這種方式可以将 切入點參數 映射到 抽取方法參數 */
@Pointcut(value = "execution(* com.oner.beans.Bean.test(..)) && args(arg1,arg2)",
argNames = "arg1,arg2")
public void pointcut(int arg1,int arg2) {}
/* returning屬性用于指定用來擷取切入點傳回值的增強方法參數名 */
@AfterReturning(value = "pointcut(var1,var2)",returning = "rtn")
public void afterReturning(int var1,int var2,String rtn) {
System.out.println("after returning...");
/* 通過增強方法參數擷取切入點參數 */
System.out.println("argument 1 is " + var1 + " , argument 2 is " + var2);
/* 通過增強方法參數擷取切入點傳回值 */
System.out.println("returning is " + rtn);
}
}
@AfterThrowing
@AfterThrowing
@AfterThrowing
注解的定義為:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface AfterThrowing {
/* 指定切入點表達式 */
String value() default "";
String pointcut() default "";
/* 用于擷取切入點抛出的異常對象 */
String throwing() default "";
/* 用于擷取切入點參數 */
String argNames() default "";
}
@AfterThrowing
注解的進階用法:
/* @Component注解用于生成執行個體 */
@Component(value = "beanProxy")
/* @Aspect注解用于生成代理對象 */
@Aspect
public class BeanProxy {
/* 定義空方法,使用@Pointcut注解抽取切入點;
切入訓示"&&args(...)"用于為切入點的參數指定别名;
@Pointcut注解的argNames屬性用于指定抽取方法的參數名;
通過這種方式可以将 切入點參數 映射到 抽取方法參數 */
@Pointcut(value = "execution(* com.oner.beans.Bean.test(..)) && args(arg1,arg2)",
argNames = "arg1,arg2")
public void pointcut(int arg1,int arg2) {}
/* throwing屬性用于指定用來擷取切入點傳回值的增強方法參數名 */
@AfterThrowing(value = "pointcut(var1,var2)",throwing = "e")
public void afterThrowing(int var1,int var2,Throwable e) {
System.out.println("after throwing...");
/* 通過增強方法參數擷取切入點參數 */
System.out.println("argument 1 is " + var1 + " , argument 2 is " + var2);
/* 通過增強方法參數擷取切入點抛出的異常 */
e.printStackTrace();
}
}
連接配接點
JoinPoint
接口
JoinPoint
JoinPoint
接口用于表示連接配接點,通過
JoinPoint
對象可以擷取連接配接點的參數清單、傳回值等資訊。
JoinPoint
接口的常用方法有:
/* 擷取切入點所在的對象 */
Object getTarget();
/* 擷取切入點的參數清單 */
Object[] getArgs();
應用示例,被增強類:
@Component(value = "bean")
public class Bean {
public String test(int a,int b) {
return a+b+"";
}
public void hello() {
System.out.println("Hello world!");
}
}
增強類:
@Component(value = "beanProxy")
@Aspect
public class BeanProxy {
/* 前置增強 */
@Before(value = "execution(* com.oner.beans.Bean.test(..))")
public void before(JoinPoint point) {
System.out.println("before...");
/* 擷取切入點的參數清單 */
Object[] args = point.getArgs();
for(Object obj : args) {
System.out.println(obj);
}
/* 擷取切入點所在對象 */
Bean bean = (Bean)point.getTarget();
/* 調用切入點所在對象的成員 */
bean.hello();
}
}
ProceedingJoinPoint
接口
ProceedingJoinPoint
ProceedingJoinPoint
接口是
JoinPoint
接口的子接口,用于環繞增強,常用方法有:
/* ProceedingJoinPoint接口是JoinPoint接口的子接口 */
public interface ProceedingJoinPoint extends JoinPoint {
/* 執行切入點,擷取切入點的傳回值 */
Object proceed() throws Throwable;
/* 改變參數執行切入點,擷取切入點的傳回值,參數args傳入要改變的參數 */
Object proceed(Object[] args) throws Throwable;
}
應用示例,被增強類:
@Component(value = "bean")
public class Bean {
public String test(int a,int b) {
return a+b+"";
}
public void hello() {
System.out.println("Hello world!");
}
}
增強類:
@Component(value = "beanProxy")
@Aspect
public class BeanProxy {
/* 環繞增強 */
@Around(value = "execution(* com.oner.beans.Bean.test(..))")
public void around(ProceedingJoinPoint pjp) {
System.out.println("before proceeding...");
try{
/* 改變切入點的參數執行切入點,擷取切入點的傳回值 */
String rtn = (String)pjp.proceed(new Integer[]{50,50});
System.out.println("returning of pointcut is " + rtn);
} catch(Throwable t) {
t.printStackTrace();
}
System.out.println("after proceeding...");
}
}
SpringMVC
SpringMVC
SpringMVC
是一個基于
Spring
的
Web
開發架構。可以了解為
Servlet
的更新。
SpringMVC
的工作原理為:
1.使用Servlet對象DispatcherServlet接收使用者請求
2.賦予@Controller注解處理請求的新功能
3.DispatcherServlet對象将請求轉發至@Controller注解生成的執行個體(控制對象)處理
[email protected]注解生成的執行個體(控制對象)響應資料
項目建立
Maven
多子產品
Maven
1.建立parent子產品
(1).File -> New -> Project -> Maven,不選擇模闆(不勾選Create from archetype)
(2).設定項目名稱、工作空間等
(3).設定項目坐标和版本
2.在parent子產品下建立dao子產品
(1).右擊parent -> New -> Module -> Maven
(2).選擇普通Java項目模闆(勾選Create from archetype,
并選擇org.apache.maven.archetypes:maven-archetype-quickstart)
(3).設定子產品名稱、工作空間
(4).設定子產品坐标和版本
(5).配置Maven
3.在parent子產品下建立service子產品
(1).右擊parent -> New -> Module -> Maven
(2).選擇普通Java項目模闆(勾選Create from archetype,
并選擇org.apache.maven.archetypes:maven-archetype-quickstart)
(3).設定子產品名稱、工作空間
(4).設定子產品坐标和版本
(5).配置Maven
4.在parent子產品下建立controller子產品
(1).右擊parent -> New -> Module -> Maven
(2).選擇JavaWeb項目模闆(勾選Create from archetype,
并選擇org.apache.maven.archetypes:maven-archetype-webapp)
(3).設定子產品名稱、工作空間
(4).設定子產品坐标和版本
(5).配置Maven
5.修改每個pom.xml(修改JDK版本,去除不必要的部分,添加插件等)
6.添加必要檔案夾(src/main和src/test中的java檔案夾和resources檔案夾)
添加依賴
<dependecies>
<!-- Servlet依賴 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- Spring WebMVC依賴 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
...
</dependecies>
<build>
<plugins>
<!-- Tomcat伺服器插件 -->
<plugin>
<!-- 插件參數 -->
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<!-- Tomcat伺服器參數 -->
<configuration>
<!-- 端口号 -->
<port>8080</port>
<!-- 站點對外通路路徑,必須是以"/"開頭的字元串 -->
<path>/test</path>
<!-- 字元集編碼,預設為ISO-8859-1 -->
<uriEncoding>UTF-8</uriEncoding>
<!-- 伺服器名 -->
<server>tomcat7</server>
</configuration>
</plugin>
...
</plugins>
...
</build>
核心配置
核心對象
DispatcherServlet
被建立時調用
init()
方法實作的功能:
1.加載SpringMVC配置檔案,建立容器對象:
ApplicationContext context = new ClassPathXmlApplicationContext("spring-mvc-config.xml");
2.将容器對象存入ServletContext域中,項目中可以直接擷取該對象:
obj.setAttribute(key,context);
SpringMVC
的核心配置需要修改
web.xml
檔案并添加
SpringMVC
配置檔案:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 必須為最新版本 -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<servlet>
<servlet-name>DispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 指定SpringMVC配置檔案路徑 -->
<init-param>
<!-- 固定的初始化參數名 -->
<param-name>contextConfigLocation</param-name>
<!-- 指定SpringMVC配置檔案的類路徑 -->
<param-value>classpath:spring-mvc-config.xml</param-value>
</init-param>
<!-- 預設為-1,即伺服器啟動後不建立該Servlet,此标簽必須置于最後 -->
<load-on-startup>0</load-on-startup>
</servlet>
<!-- 路徑映射 -->
<servlet-mapping>
<servlet-name>DispatcherServlet</servlet-name>
<!-- 使用SpringMVC架構時,url-pattern可以是兩種類型:
1.自定義擴充名,例如"*.do,*.action...",指定通路路徑為指定擴充名時,
交由DispatcherServlet處理
2."/"指定所有請求均交由DispatcherServlet處理 -->
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
在
Controller
子產品的
resources
目錄中添加
SpringMVC
配置檔案(與
Spring
的配置檔案相同):
spring-mvc-config.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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 開啟元件掃描器并指定基礎掃描包 -->
<context:component-scan base-package="com.oner.controllers"/>
</beans>
改變資源路徑
Web
工程的目錄結構為:
Controller
|---src
|---main
|---java
|---resources
|---webapp
|---WEB-INF
|---web.xml
|---index.jsp
webapp
根目錄下的資源(例如
index.html
)對使用者公開,使用者可使用站點名+資源名的形式直接通路,但這樣會破壞伺服器端的請求邏輯,導緻使用者無法得到正确的響應。
webapp/WEB-INF
目錄下的資源對使用者不可見,使用者無法直接通路,是以可将非公開資源置于
webapp/WEB-INF
目錄下。
Controller
|---src
|---main
|---java
|---resources
|---webapp
|---WEB-INF
|---view
|---hello.html
|---web.xml
|---index.html
建立控制類
建立控制類,在控制類中建立控制方法,使用控制方法處理請求。
@Controller(value = "myController")
public class MyController {
/* 建立控制方法,@RequestMapping指定映射路徑,請求路徑可以不以"/"開頭 */
@RequestMapping(value = "test.do")
public ModelAndView test() {
System.out.println("Hello world!");
ModelAndView mv = new ModelAndView();
/* 指定視圖,即請求轉發,相當于request.getRequestDispatcher(...).forward() */
mv.setViewName("/WEB-INF/view/hello.html");
return mv;
}
}
通路
http://localhost:8080/oner/test.do
即可通路到指定資源。
視圖解析器
通路視圖時,每次加上
"/WEB-INF/view/xxx.html"
會導緻代碼備援,可以配置視圖解析器簡化資源的通路路徑。在
SpringMVC
配置檔案下添加一下内容:
spring-mvc-config.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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!-- 開啟元件掃描器并指定基礎掃描包 -->
<context:component-scan base-package="com.oner.controllers"/>
<!-- 配置内部資源視圖解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 内部資源通路路徑的字首 -->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 内部資源通路路徑的字尾 -->
<property name="suffix" value=".html"/>
</bean>
</beans>
視圖解析器配置的是視圖的通路路徑,并非請求的通路路徑。配置之後的内部資源通路:
@Controller(value = "myController")
public class MyController {
@RequestMapping(value = "test.do")
public ModelAndView test() {
System.out.println("Hello world!");
ModelAndView mv = new ModelAndView();
/* 省略前字尾 */
mv.setViewName("hello");
return mv;
}
}
請求處理過程
1.請求發起
2.Tomcat伺服器根據web.xml将請求交由DispatcherServlet處理
3.DispatcherServlet掃描指定的基礎包,根據注解将請求交由控制類的控制方法處理
4.控制方法處理請求,傳回ModelAndView對象
5.SpringMVC架構擷取控制方法傳回的ModelAndView對象,将請求轉發至指定路徑
處理器深入
控制類中用于處理請求的方法稱為處理器(
Handler
)。
@RequestMapping
@RequestMapping
@RequestMapping
的定義
@RequestMapping
/* @RequestMapping可以修飾方法和類 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {
/* RequestMapping的名稱 */
String name() default "";
/* 映射路徑,一個處理器可映射多個路徑,與path屬性等價 */
@AliasFor("path")
String[] value() default {};
/* 映射路徑,一個處理器可映射多個路徑,與value屬性等價 */
@AliasFor("value")
String[] path() default {};
/* 指定可接受的請求方法,enum類型,可取值為:
RequestMethod{GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS,TRACE} */
RequestMethod[] method() default {};
/* 指定可接受的請求參數格式 */
String[] params() default {};
/* 指定可接受的請求頭的格式 */
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
請求路徑
@RequestMapping
注解的
value(path)
屬性為數組類型,可指定一個處理器映射多個路徑。
@Controller(value = "myController")
public class MyController {
/* 一個處理器映射多個路徑 */
@RequestMapping(value = {"test1.do","test2.do","test3.do"})
public ModelAndView test() {...}
}
請求方法
@RequestMapping
注解的
method
屬性為數組類型,指定處理器可接受哪些請求方法,如果使用者發送的請求方法不符合
method
屬性,将無法通路指定資源。
@Controller(value = "myController")
public class MyController {
@RequestMapping(value = "test.do",
method = {RequestMethod.GET,RequestMethod.POST})
public ModelAndView test() {...}
}
請求參數
@RequestMapping
注解的
params
屬性為數組類型,指定處理器可接受的請求參數格式,如果使用者發送的請求參數格式不符合
params
屬性,将無法通路指定資源。
params
屬性具有簡單的表達式文法:
1.多個參數表達式之間為邏輯與關系
@RequestMapping(params = {"...","...","..."})
2.params的值或params成員的值為"參數名"的形式,指定請求必須攜帶該參數
@RequestMapping(params = {"param1","param2"})
3.params的值或params成員的值為"!參數名"的形式,指定請求必須不攜帶該參數
@RequestMapping(params = {"param1","!param2"})
4.params的值或params成員的值為"參數名=值"的形式,指定請求必須攜帶該參數且等于指定值
@RequestMapping(params = {"name=Tony","password=123456"})
5.params的值或params成員的值為"參數名!=值"的形式,指定請求必須攜帶該參數且不等于指定值
@RequestMapping(params = {"name=Tony","password=123456","sex!=m"})
示例:
@Controller(value = "myController")
public class MyController {
@RequestMapping(value = "test.do",
params = {"id","name=Tony","password=123456","sex!=m"})
public ModelAndView test() {...}
}
請求頭
@RequestMapping
注解的
heasers
屬性為數組類型,指定處理器可接受的請求頭格式,如果使用者發送的請求頭格式不符合
headers
屬性,将無法通路指定資源。
headers
屬性的用法與
params
屬性一緻:
@Controller(value = "myController")
public class MyController {
@RequestMapping(value = "test.do",
headers = {"Accept-Language=en-US,zh"})
public ModelAndView test() {...}
}
@RequestMapping
修飾類
@RequestMapping
@RequestMapping
修飾類可實作公共路徑抽取。
/* 抽取公共路徑 */
@RequestMapping(value="test")
@Controller(value = "myController")
public class MyController {
/* 實際路徑為"/test/test1.do" */
@RequestMapping(value = "test1.do")
public ModelAndView test1() {...}
/* 實際路徑為"/test/test2.do" */
@RequestMapping(value = "test2.do")
public ModelAndView test2() {...}
/* 實際路徑為"/test/test3.do" */
@RequestMapping(value = "test3.do")
public ModelAndView test3() {...}
}
處理器參數
處理器可定義一些形參,
SpringMVC
調用處理器時可自動傳入參數。
Servlet
參數
Servlet
@Controller(value = "myController")
public class MyController {
/* 參數清單可以是Servlet相關的參數:HttpServletRequest,HttpServletResponse,HttpSession */
@RequestMapping(value = "test.do")
public void test(HttpServletRequest request,
HttpServletResponse response,
HttpSession session) throws IOException,ServletException {
/* 設定請求編碼 */
request.setCharacterEncoding("UTF-8");
/* 擷取請求參數 */
String username = request.getParameter("username");
String password = request.getParameter("password");
/* 設定響應内容及編碼 */
response.setContentType("text/html;charset=UTF-8");
String message = "user name : " + username + " , " + "password : " + password + " .";
System.out.println(message);
/* 向頁面輸出内容 */
response.getOutputStream().write(message.getBytes("UTF-8"));
}
}
通路
url
:
http://localhost:8080/oner/test.do?username=Tony&password=123456
請求參數
處理器的形參清單可以是與請求參數同名的參數,這種情況下
SpringMVC
将根據請求中的參數名查詢處理器中的形參名,如果有同名形參,則将參數值傳遞給形參。
@Controller(value = "myController")
public class MyController {
/* 使用同名形參擷取請求參數 */
@RequestMapping(value = "test.do")
public void test(HttpServletResponse response,
String username,String password) throws IOException,ServletException {
/* 設定響應内容及編碼 */
response.setContentType("text/html;charset=UTF-8");
String message = "user name : " + username + " , " + "password : " + password + " .";
System.out.println(message);
/* 向頁面輸出内容 */
response.getOutputStream().write(message.getBytes("UTF-8"));
}
}
通路
url
:
http://localhost:8080/oner/test.do?username=Tony&password=123456
編碼過濾器
在
web.xml
中添加以下内容,可解決所有請求及響應的編碼問題:
web.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 必須為最新版本 -->
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
...
<filter>
<!-- 編碼過濾器 -->
<filter-name>CharacterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<!-- 設定編碼格式 -->
<init-param>
<param-name>encoding</param-name>
<param-value>utf-8</param-value>
</init-param>
<!-- 強制請求使用設定的編碼格式 -->
<init-param>
<param-name>forceRequestEncoding</param-name>
<param-value>true</param-value>
</init-param>
<!-- 強制響應使用設定的編碼格式 -->
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<!-- 過濾器路徑映射 -->
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<!-- 對所有路徑有效 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
@RequestParam
@RequestParam
@RequestParam
注解為處理器參數起别名,用于解決請求參數與處理器參數名稱不一緻的問題。定義為:
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequestParam {
/* 參數别名,與name等價 */
@AliasFor("name")
String value() default "";
/* 參數别名,與value等價 */
@AliasFor("value")
String name() default "";
/* 指定請求中是否必須攜帶對應的請求參數,
為true時,如果請求中未攜帶對應的請求參數,将無法通路指定資源 */
boolean required() default true;
String defaultValue() default "\n\t\t\n\t\t\n\ue000\ue001\ue002\n\t\t\t\t\n";
}
示例:
@Controller(value = "myController")
public class MyController {
/* @RequestParam的value屬性為處理器參數起别名;
required屬性指定請求中是否必須攜帶對應的請求參數 */
@RequestMapping(value = "test.do")
public void test(HttpServletResponse response,
@RequestParam(value="username",required=false)String user,
@RequestParam(value="password",required=false)String pswd)
throws IOException,ServletException {
/* 設定響應内容及編碼 */
response.setContentType("text/html;charset=UTF-8");
String message = "user name : " + user + " , " + "password : " + pswd + " .";
System.out.println(message);
/* 向頁面輸出内容 */
response.getOutputStream().write(message.getBytes("UTF-8"));
}
}
通路
url
:
http://localhost:8080/oner/test.do?username=Tony&password=123456
對象參數
處理器的參數為
pojo
對象時,
SpringMVC
将根據請求參數名查詢
pojo
對象屬性名,如果查詢到同名屬性,則将參數值賦給屬性,
SpringMVC
還支援
pojo
對象中的級聯屬性。示例
pojo
:
public class Employee {
private int id;
private String name;
private Department department;
public Employee() {super();}
public Employee(int id,String name,Department department) {
this.id = id;
this.name = name;
this.department = department;
}
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
public Department getDepartment() {return department;}
public void setDepartment(Department department) {this.department = department;}
@Override
public String toString() {
return "employee id : " + id + " , "
+ "employee name : " + name + " , "
+ department.toString();
}
}
級聯屬性:
public class Department {
private int id;
private String name;
public Department() {super();}
public Department(int id,String name) {
this.id = id;
this.name = name;
}
public int getId() {return id;}
public void setId(int id) {this.id = id;}
public String getName() {return name;}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "department id : " + id + " , "
+ "department name : " + name + " .";
}
}
示例:
@Controller(value = "myController")
public class MyController {
@RequestMapping(value = "test.do")
public void test(HttpServletResponse response,Employee employee)
throws IOException,ServletException {
/* 設定響應内容及編碼 */
response.setContentType("text/html;charset=UTF-8");
System.out.println(employee);
/* 向頁面輸出内容 */
response.getOutputStream().write(employee.toString().getBytes("UTF-8"));
}
}
通路
url
:
# department.id和department.name均為級聯屬性
http://localhost:8080/oner/test.do?id=1&name=Tony&department.id=1&department.name=development
處理器傳回
處理器的傳回結果為視圖,視圖即與頁面展示相關的資料,如靜态資源(傳回靜态資源的路徑),
JSON
資料(傳回
JSON
)等。類型可以是
void
、
String
、對象和
ModelAndView
.
空傳回
空傳回處理器适用不傳回視圖的場景。
@Controller(value = "myController")
public class MyController {
/* 空傳回處理器适用不反回視圖場景 */
@RequestMapping(value = "test.do")
public void test(HttpServletRequest request,
HttpServletResponse response,
HttpSession session) throws IOException,ServletException {
...
}
}
傳回靜态資源
處理器的傳回類型為
String
時,代表傳回靜态資源的路徑,處理器傳回後頁面将呈現該靜态資源。
@Controller(value = "myController")
public class MyController {
/* 傳回的字元串代表請求轉發的路徑 */
@RequestMapping(value = "test.do")
public String test() {
System.out.println("test success.");
/* 請求轉發至路徑(配置了視圖處理器)/WEB-INF/view/hello.html */
return "hello";
}
}
處理器傳回
String
類型且使用
@ResponseBody
注解(下述介紹)時,傳回結果是
HTML
文本,即傳回的字元串内容将在頁面原樣展示。
@Controller(value = "myController")
public class MyController {
/* @ResponseBody注解會将處理器的傳回結果處理為響應體,
是以傳回字元串時,字元串将在頁面上原樣展示 */
@ResponseBody
@RequestMapping(value = "test.do")
public Employee test() {
System.out.println("test success.");
return "Hello SpringMVC!"
}
}
傳回 JSON
JSON
處理器傳回
JSON
适用于需要響應頁面發送的
Ajax
請求的場合。通過配置,
SpringMVC
可将處理器傳回的對象處理為
JSON
響應給頁面。
SpringMVC
使用
Jackson
工具,是以需要引入依賴:
<!-- Jackson依賴 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
在
SpringMVC
的配置檔案中添加注解驅動:
spring-mvc-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入mvc命名空間(IDEA可自動導入) -->
<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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 開啟元件掃描器并指定基礎掃描包 -->
<context:component-scan base-package="com.oner.controllers"/>
<!-- 配置内部資源視圖解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 内部資源通路路徑的字首 -->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 内部資源通路路徑的字尾 -->
<property name="suffix" value=".html"/>
</bean>
<!-- 添加注解驅動 -->
<mvc:annotation-driven/>
</beans>
使用
@ResponseBody
注解修飾處理器時,
@ResponseBody
注解會根據處理器的傳回資料類型,将傳回結果處理為不同類型的響應體追加到
HTTP
封包中。
@Controller(value = "myController")
public class MyController {
/* 通過通路".../hello.do"轉至"...WEB-INF/view/hello.html" */
@RequestMapping("hello.do")
public String hello() {
/* 轉發至"...WEB-INF/view/hello.html" */
return "hello";
}
/* @ResponseBody的作用:
1.将處理器傳回的對象轉換為JSON對象
2.将JSON對象響應到頁面 */
@ResponseBody
@RequestMapping(value = "test.do")
public Employee test() {
System.out.println("test success.");
Employee employee = new Employee(1,"Tony",new Department(1,"development"));
/* SpringMVC将傳回的對象轉換為JSON發送至頁面 */
return employee;
}
}
頁面使用
Ajax
接收
JSON
:
hello.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello</title>
<!-- 遠端jQuery -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<style type="text/css">
*{
font-family: "Youyuan"
}
</style>
<script type="text/javascript">
$(function(){
var employeeId = $("input[name='employee-id']");
var employeeName = $("input[name='employee-name']");
var departmentId = $("input[name='department-id']");
var departmentName = $("input[name='department-name']");
/* 點選按鈕後擷取JSON資料 */
$("#get").on("click",function(){
$.ajax({
type:"POST",
url:"http://localhost:8080/oner/test.do",
async:true,
/* 要求響應格式為JSON,如果響應格式不正确,将跳至error回調 */
dataType:"json",
success:function(data){
/* 擷取JSON資料 */
employeeId.val(data.id);
employeeName.val(data.name);
/* 支援級聯屬性(原理是JSON嵌套) */
departmentId.val(data.department.id);
departmentName.val(data.department.name);
},
error:function(){
alert("error");
}
})
});
});
</script>
</head>
<body>
<h1>你的資訊</h1><br/>
員工編号<input name="employee-id" type="text"/><br/>
員工姓名<input name="employee-name" type="text"/><br/>
部門編号<input name="department-id" type="text"/><br/>
部門名稱<input name="department-name" type="text"/><br/>
<button id="get">擷取資訊</button>
</body>
</html>
級聯屬性的轉換原理:
/* 級聯屬性轉換為嵌套JSON */
Employee -> {id:1,name:Tony,department:{id:1,name:development}}
傳回 List
List
處理器傳回數組或
List
時,
SpringMVC
架構會将數組或
List
轉換為
JSON
數組響應給頁面。
/* List轉換為JSON數組 */
List<Employee> -> {{id:1,name:Tony, department:{id:1,name:development}},
{id:2,name:Steven,department:{id:2,name:management},
{id:3,name:Ellen, department:{id:3,name:marketing}}
處理器傳回
List
:
@Controller(value = "myController")
public class MyController {
/* 通過通路".../hello.do"轉至"...WEB-INF/view/hello.html" */
@RequestMapping("hello.do")
public String hello() {
/* 轉發至"...WEB-INF/view/hello.html" */
return "hello";
}
/* @ResponseBody的作用:
1.将處理器傳回的對象轉換為JSON對象
2.将JSON對象響應到頁面 */
@ResponseBody
@RequestMapping(value = "test.do")
public List<Employee> test() {
System.out.println("test success.");
Employee e1 = new Employee(1,"Tony",new Department(1,"development"));
Employee e2 = new Employee(2,"Steven",new Department(2,"management"));
Employee e3 = new Employee(3,"Ellen",new Department(3,"marketing"));
ArrayList<Employee> list = new ArrayList<Employee>();
list.add(e1);
list.add(e2);
list.add(e3);
return list;
}
}
頁面使用
Ajax
接收
JSON
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello</title>
<!-- 遠端jQuery -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<style type="text/css">
*{
font-family: "Youyuan"
}
table,tr,td{
border: 1px solid lightseagreen;
padding: 10px;
}
</style>
<script type="text/javascript">
$(function(){
/* 點選按鈕後擷取JSON資料 */
$("#get").on("click",function(){
$.ajax({
type:"POST",
url:"http://localhost:8080/oner/test.do",
async:true,
/* 要求響應格式為JSON,如果響應格式不正确,将跳至error回調 */
dataType:"json",
success:function(data){
var $row;
for(var i=0;i<data.length;i++) {
$row = $("#inf").find("tr").eq(i+1);
/* 為表格的單元指派 */
$row.find("td").eq(0).html(data[i].id);
$row.find("td").eq(1).html(data[i].name);
/* 級聯屬性 */
$row.find("td").eq(2).html(data[i].department.id);
$row.find("td").eq(3).html(data[i].department.name);
}
},
error:function(){
alert("error");
}
})
});
});
</script>
</head>
<body>
<h1 align="center">員工資訊</h1><br/>
<table id="inf" align="center">
<tr>
<td align="center">員工編号</td>
<td align="center">員工姓名</td>
<td align="center">部門編号</td>
<td align="center">部門名稱</td>
</tr>
<tr>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</table>
<br/>
<center><button id="get">擷取資訊</button></center>
</body>
</html>
傳回 Map
Map
處理器傳回
Map
時,
SpringMVC
架構會将
Map
轉換為
key
為
Map
中的
key
,
value
為
Map
中的
value
的
JSON
對象響應給頁面。
Map<String,Employee> -> {Tony: {id:1,name:Tony, department:{id:1,name:development}},
Steven:{id:2,name:Steven,department:{id:2,name:management}},
Ellen: {id:3,name:Ellen, department:{id:3,name:marketing}}}
處理器傳回
Map
:
@Controller(value = "myController")
public class MyController {
/* 通過通路".../hello.do"轉至"...WEB-INF/view/hello.html" */
@RequestMapping("hello.do")
public String hello() {
/* 轉發至"...WEB-INF/view/hello.html" */
return "hello";
}
/* @ResponseBody的作用:
1.将處理器傳回的對象轉換為JSON對象
2.将JSON對象響應到頁面 */
@ResponseBody
@RequestMapping(value = "test.do")
public Map<String,Employee> test() {
System.out.println("test success.");
Employee e1 = new Employee(1,"Tony",new Department(1,"development"));
Employee e2 = new Employee(2,"Steven",new Department(2,"management"));
Employee e3 = new Employee(3,"Ellen",new Department(3,"marketing"));
HashMap<String,Employee> map = new HashMap<String,Employee>();
map.put(e1.getName(),e1);
map.put(e2.getName(),e2);
map.put(e3.getName(),e3);
return map;
}
}
頁面使用
Ajax
接收
JSON
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello</title>
<!-- 遠端jQuery -->
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
<style type="text/css">
*{
font-family: "Youyuan"
}
table,tr,td{
border: 1px solid lightseagreen;
padding: 10px;
}
</style>
<script type="text/javascript">
$(function(){
/* 點選按鈕後擷取JSON資料 */
$("#get").on("click",function(){
$.ajax({
type:"POST",
url:"http://localhost:8080/oner/test.do",
async:true,
/* 要求響應格式為JSON,如果響應格式不正确,将跳至error回調 */
dataType:"json",
success:function(data){
var $row;
$row = $("#inf").find("tr").eq(1);
$row.find("td").eq(0).html(data.Tony.id);
$row.find("td").eq(1).html(data.Tony.name);
$row.find("td").eq(2).html(data.Tony.department.id);
$row.find("td").eq(3).html(data.Tony.department.name);
$row = $("#inf").find("tr").eq(2);
$row.find("td").eq(0).html(data.Steven.id);
$row.find("td").eq(1).html(data.Steven.name);
$row.find("td").eq(2).html(data.Steven.department.id);
$row.find("td").eq(3).html(data.Steven.department.name);
$row = $("#inf").find("tr").eq(3);
$row.find("td").eq(0).html(data.Ellen.id);
$row.find("td").eq(1).html(data.Ellen.name);
$row.find("td").eq(2).html(data.Ellen.department.id);
$row.find("td").eq(3).html(data.Ellen.department.name);
},
error:function(){
alert("error");
}
})
});
});
</script>
</head>
<body>
<h1 align="center">員工資訊</h1><br/>
<table id="inf" align="center">
<tr>
<td align="center">員工編号</td>
<td align="center">員工姓名</td>
<td align="center">部門編号</td>
<td align="center">部門名稱</td>
</tr>
<tr>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</table>
<br/>
<center><button id="get">擷取資訊</button></center>
</body>
</html>
傳回 ModelAndView
ModelAndView
ModelAndView
中的常用方法:
/* 向request域中添加屬性 */
addObject(String attributeName,Object attributeValue);
/* 設定轉發路徑 */
setViewName(String viewName);
@ResponseBody
修飾類
@ResponseBody
@ResponseBody
修飾控制器類時,指定控制器類中的所有處理器的傳回結果均為
JSON
資料或
HTML
文本。
@Controller
/* @ResponseBody修飾控制器類時,指定所有處理器的傳回結果均為JSON資料或HTML文本 */
@ResponseBody
public class MyController {
@RequestMapping("test1.do")
public String test1() {
...
return "Hello SpringMVC.";
}
@RequestMapping("test2.do")
public Employee test2() {
...
return employee;
}
}
@RestController
@RestController
@RestController
注解是
@Controller
注解與
@ResponseBody
注解的結合,用于
@Controller
注解與
@ResponseBody
注解同時修飾類的場景。
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
/* @RestController是@Controller與@ResponseBody的結合 */
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(
annotation = Controller.class
)
/* 指定Bean類的名稱,與@Controller中的value屬性作用相同 */
String value() default "";
}
@RestController
修飾控制器類時,控制器類中的所有處理器的傳回結果均為
JSON
或
HTML
文本。
/* @RestController(value = "myController")是
@Controller(value = "myController")注解與@ResponseBody注解的結合 */
@RestController(value = "myController")
public class MyController {
@RequestMapping("test1.do")
public String test1() {
...
/* 傳回HTML文本 */
return "Hello SpringMVC.";
}
@RequestMapping("test2.do")
public Employee test2() {
...
/* 傳回JSON */
return employee;
}
}
異常處理
SpringMVC
采用
AOP
思想,使用全局統一的異常處理方式,将所有異常放在同一個位置處理,将業務邏輯與異常處理分開,實作代碼解耦。
全局異常處理類
SpringMVC
将業務邏輯中的所有異常統一交給一個類處理,這個類需要使用
@ControllerAdvice
注解修飾,稱為全局異常處理類。具體的異常類型由全局異常處理類中的方法處理,方法使用
@ExceptionHandler
注解修飾并指定此方法可處理的異常類型。
1.使用@ControllerAdvice注解指定全局異常處理類
2.使用@ExceptionHandler注解指定異常處理方法
[email protected]注解指定異常方法處理的異常類型
@ExceptionHandler
注解的定義為:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExceptionHandler {
/* 指定異常處理方法可處理的異常類型,可為方法指定多個異常類型 */
Class<? extends Throwable>[] value() default {};
}
在
JavaWeb
項目下建立包并建立全局異常處理類:
package com.oner.handler;
/* @ControllerAdvice注解用于指定類為全局異常處理類 */
@ControllerAdvice
public class GlobalExceptionHandler {
/* @ExceptionHandler注解用于指定方法為目标異常處理方法,
僅有value一個屬性,用于指定目标異常類 */
@ExceptionHandler(value = NameException.class)
/* 異常處理方法可以像處理器方法一樣響應内容到頁面上 */
@ResponseBody
/* 異常處理方法中的異常參數可以用來擷取異常資訊 */
public String handleNameException(Exception e) {
e.printStackTrace();
return "name wrong.";
}
@ExceptionHandler(value = PasswordException.class)
@ResponseBody
public String handlePasswordException(Exception e) {
e.printStackTrace();
return "password wrong.";
}
/* 未指定value屬性的@ExceptionHandler注解用于處理未知的其它異常 */
@ExceptionHandler
public String handleOthers(Exception e) {
e.printStackTrace();
/* 響應頁面:"WEB-INF/view/unknown-exception.html" */
return "unknown-exception";
}
}
完善配置
添加掃描路徑(用于掃描全局異常處理類的注解),添加注解驅動。在
SpringMVC
的配置檔案中添加以下内容:
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入mvc命名空間(IDEA可自動導入) -->
<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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 開啟元件掃描器并指定基礎掃描包,多個包之間使用","隔開 -->
<context:component-scan base-package="com.oner.controllers,com.oner.handler"/>
<!-- 配置内部資源視圖解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 内部資源通路路徑的字首 -->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 内部資源通路路徑的字尾 -->
<property name="suffix" value=".html"/>
</bean>
<!-- 添加注解驅動 -->
<mvc:annotation-driven/>
</beans>
自定義異常類
定義
name
錯誤異常類:
package com.oner.handler.exceptions;
public class NameException extends Exception {
public NameException() {super();}
public NameException(String message) {super(message);}
}
定義
password
錯誤異常類:
package com.oner.handler.exceptions;
public class PasswordException extends Exception {
public PasswordException() {super();}
public PasswordException(String message) {super(message);}
}
控制類抛出異常
@Controller(value = "myController")
public class MyController {
@RequestMapping(value = "test.do")
public String test(String username,String password) throws Exception {
if(!"Tony".equals(username)) {
throw new NameException("name wrong.");
}
if(!"123456".equals(password)) {
throw new PasswordException("password wrong.");
}
if("Tony".equals(username) && "123456".equals(password)) {
System.out.println("login success.");
/* 請求轉發至"WEB-INF/view/hello.html" */
return "hello";
}
return null;
}
}
通路
url
:
# 抛出NameException
http://localhost:8080/oner?username=Tom&password=123456
# 抛出PasswordException
http://localhost:8080/oner?username=Tony&password=12345
# 登陸成功
http://localhost:8080/oner?username=Tony&password=123456
攔截器
攔截器與
JavaWeb
中的
Filter(過濾器)
類似,在處理請求之前先進行預處理,用于權限檢查、身份驗證等資料通路權限操作。
SpringMVC
中定義攔截器需要實作
HandlerInterceptor
接口。
HandlerInterceptor
接口
HandlerInterceptor
public interface HandlerInterceptor {
/* preHandle()在處理器處理請求之前(SpringMVC調用處理器之前)被SpringMVC調用;
參數request用于擷取請求資訊,可用于請求轉發
參數response用于響應資訊;
參數handler為處理器所在的控制器對象;
preHandle()傳回false,請求被攔截,請求不到達處理器;傳回true,請求被放行,由處理器處理 */
default boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
return true;
}
/* postHandle()在處理器處理請求之後響應之前(處理器傳回之前)被SpringMVC調用,
用于對處理器的處理結果作二次修改;
參數request用于擷取請求資訊;
參數response用于響應資訊;
參數handler為處理器所在的控制器對象;
參數modelAndView用于修改處理器的處理結果,即修改modelAndView對象可影響最終的響應結果 */
default void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
}
/* afterCompletion()在處理器響應之後(處理器傳回之後)被SpringMVC調用,用于資源回收及異常處理
參數request用于擷取請求資訊;
參數response用于響應資訊;
參數handler為處理器所在的控制器對象;
參數exception用于擷取處理器抛出的異常資訊(如果有) */
default void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable Exception exception) throws Exception {
}
}
定義攔截器
在
JavaWeb
工程下建立包并自定義攔截器類實作
HandlerInterceptor
接口:
package com.oner.handler.intercepters;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws Exception {
System.out.println("preHandle() is executed.");
return true;
}
@Override
public void postHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable ModelAndView modelAndView) throws Exception {
System.out.println("postHandle() is executed.");
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
@Nullable Exception exception) throws Exception {
System.out.println("afterCompletion() is executed.");
}
}
注冊攔截器
在
SpringMVC
的配置檔案中添加以下内容:
spring-mvc-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!-- 引入mvc命名空間(IDEA可自動導入) -->
<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"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<!-- 開啟元件掃描器并指定基礎掃描包,多個包之間使用","隔開 -->
<context:component-scan base-package="com.oner.controllers,com.oner.handler"/>
<!-- 配置内部資源視圖解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<!-- 内部資源通路路徑的字首 -->
<property name="prefix" value="/WEB-INF/view/"/>
<!-- 内部資源通路路徑的字尾 -->
<property name="suffix" value=".html"/>
</bean>
<!-- 添加注解驅動 -->
<mvc:annotation-driven/>
<!-- 可注冊多個攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<!-- 目标攔截器攔截的路徑,"xxx/**"可指定攔截"xxx"下的所有路徑,
例如"user/**"可指定攔截"user"下的所有路徑 -->
<mvc:mapping path="test.do"/>
<!-- 攔截器所在類路徑 -->
<bean class="com.oner.handler.interceptors.MyInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>
</beans>
攔截器鍊
當一個資源的請求路徑的不同層次的路徑設有攔截器時,這些攔截器組成攔截器鍊。攔截器鍊中的攔截器按照路徑層次從外層向内層依次攔截請求。攔截器中方法的執行順序為:
1.preHandle()按照路徑層次從外層向内層依次執行:
interceptor1.preHandle() ->
interceptor2.preHandle() ->
interceptor3.preHandle()
2.處理器執行
3.postHandle()按照路徑層次從内層向外層依次執行:
interceptor3.postHandle() ->
interceptor2.postHandle() ->
interceptor1.postHandle()
4.視圖解析器執行
5.afterCompletion()按照路徑層次從内層向外層依次執行:
interceptor3.afterCompletion() ->
interceptor2.afterCompletion() ->
interceptor1.afterCompletion()
攔截器的
preHandle()
方法傳回
false
将導緻攔截器鍊斷開,攔截器鍊斷開後,内層的請求無法到達,因而内層的處理器和攔截器不執行,攔截器鍊将從斷開位置的
postHandle()
方法開始迂回執行。
攔截器與過濾器的差別
1.過濾器用于過濾請求,攔截器用于攔截處理器的執行
2.過濾器先于攔截器執行
3.如果DipatcherServlet的請求被攔截,對應的攔截器将不執行
RESTful
RESTful
REST
風格
REST
REST(Representational State Transfer)
意為表述性狀态轉移,
RESTful
即為
REST
風格,是一種通過
URL
表示資源,使用請求方法表示對資源的操作的
Web
程式設計風格,一種人為規範,并非實際的代碼架構。傳統的
Web
程式設計中,對資源的每個操作都必須使用特的
URL
實作:
@RestController
@RequestMapping(value = "users")
public class UserController {
/* 通路http://.../users/get.do查詢資訊 */
@RequestMapping(value = "get.do")
public User get(int id) {
// ...
}
/* 通路http://.../users/add.do增添資訊 */
@RequestMapping(value = "add.do")
public String add(User user) {
// ...
}
/* 通路http://.../users/update.do更新資訊 */
@RequestMapping(value = "update.do")
public String update(User user) {
// ...
}
/* 通路http://.../users/delete.do删除資訊 */
@RequestMapping(value = "delete.do")
public String delete() {
// ...
}
}
使用
RESTful
程式設計時,對同一個資源的所有操作都通路同一個
URL
,使用請求方法規定具體操作:
@RestController
/* 對user資源的所有操作都通路"http://.../users" */
@RequestMapping(value = "users")
public class UserController {
/* 規定使用GET方法進行查詢操作 */
@RequestMapping(method = RequestMethod.GET)
public User get(int id) {
// ...
}
/* 規定使用POST方法進行增添操作 */
@RequestMapping(method = RequestMethod.POST)
public String add(User user) {
// ...
}
/* 規定使用PUT方法進行更新操作 */
@RequestMapping(method = RequestMethod.PUT)
public String update(User user) {
// ...
}
/* 規定使用DELETE方法進行删除操作 */
@RequestMapping(method = RequestMethod.DELETE)
public String delete() {
// ...
}
}
RESTful
注解
RESTful
SpringMVC
提供了專用的
RESTful
注解代替
@RequestMapping(method = ...)
注解。常用有:
/* @GetMapping與@RequestMapping(method = RequestMethod.GET)等價 */
@GetMapping
/* @PostMapping與@RequestMapping(method = RequestMethod.POST)等價 */
@PostMapping
/* @PutMapping與@RequestMapping(method = RequestMethod.PUT)等價 */
@PutMapping
/* @DeleteMapping與@RequestMapping(method = RequestMethod.DELETE)等價 */
@DeleteMapping
/* @PatchMapping與@RequestMapping(method = RequestMethod.PATCH)等價 */
@PatchMapping
這些注解與
@RequestMapping
的差別在于
method
屬性的取值,其餘特性相同。這些注解與
@RequestMapping
的定義基本相同,以
@GetMapping
為例:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
/* 指定請求方法 */
@RequestMapping(
method = {RequestMethod.GET}
)
public @interface GetMapping {
/* 與@RequestMapping注解中的name屬性作用一緻,為路徑映射起名 */
String name() default "";
/* 與@RequestMapping注解中的value屬性作用一緻,指定通路路徑 */
String[] value() default {};
/* 與@RequestMapping注解中的path屬性作用一緻,指定通路路徑 */
String[] path() default {};
/* 與@RequestMapping注解中的param屬性作用一緻,規定請求參數的格式 */
String[] params() default {};
/* 以下為不常用屬性,與@RequestMapping注解中作用一緻 */
String[] headers() default {};
String[] consumes() default {};
String[] produces() default {};
}
RESTful
注解使用示例:
@RestController(value = "users")
public class UserController {
@GetMapping
public User get(int id) {
// ...
}
@PostMapping
public String add(User user) {
// ...
}
@PutMapping
public String update(User user) {
// ...
}
@DeleteMapping
public String delete(int id) {
// ...
}
}
資源設計規則
RESTful
的核心原則在于,對同一個資源的所有操作均通路同一個
URL
,使用不同的請求方法進行不同的操作。是以,對于資源路徑的設計應當遵循以下規則:
1.每個URL都代表一個資源,例:
http://myoner.com/users
http://myoner.com/employees
http://myoner.com/departments
2.URL中不能出現動詞,隻能是名詞,且所用的名詞應當與資料庫表名對應一緻
動作設計規則
RESTful
為每個請求方法都賦予了新的含義:
GET : 從伺服器查詢一條或多條資料
POST : 在伺服器上添加一條資料
PUT : 在伺服器上更新一條資料(PUT更新整個對象)
PATCH : 在伺服器上更新一條資料(PATCH更新對象的個别屬性)
DELETE : 從伺服器删除資料
HEAD : 擷取一個資源的中繼資料,例如一個資源的最後修改日期
OPTIONS : 擷取資源的API(即用戶端可對該資源進行操作的描述)
路徑傳參
RESTful
中,請求方法需要的參數通過請求路徑獲得,這種方式稱為路徑傳參。路徑傳參是
RESTful
特有的方式,比傳統的請求參數方式(
?name=value&name=value...
)更加簡潔:
@RestController
@RequestMapping(value = "users")
public class UserController {
/* 在URL中使用路徑占位符,用于接收請求URL中的路徑參數并傳遞給處理器,
例如http://.../users/10/Tom */
@GetMapping(value = "{id}/{name}")
/* 使用@PathVariable注解辨別參數,
SpringMVC将URL中占位符的實際内容預設傳遞給處理器的同名參數 */
public User get(@PathVariable int id,@PathVariable String name) {
// ...
}
}
RESTful
路徑傳參時,必須使用
@PathVariable
注解辨別處理器參數,否則
SpringMVC
會将處理器參數了解為請求參數(
URL
中的
?name=value&name=value...
部分)。
@PathVariable
注解的定義為:
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PathVariable {
/* 處理器參數的别名(與name作用相同),預設為"",此時SpringMVC将讀取實際參數名 */
@AliasFor("name")
String value() default "";
/* 處理器參數的别名(與value作用相同),預設為"",此時SpringMVC将讀取實際參數名 */
@AliasFor("value")
String name() default "";
/* 是否要求路徑中必須帶有該參數 */
boolean required() default true;
}
為提高代碼的可讀性,建議使用
@PathVariable
注解時為處理器參數起别名:
@RestController
@RequestMapping(value = "users")
public class UserController {
/* 建議明确指定處理器參數的路徑傳參名,使得路徑占位符與處理器參數之間的對應關系更加清晰 */
@GetMapping(value = "{id}/{name}")
public User get(@PathVariable(value = "id") int id,
@PathVariable(value = "name") String name) {
// ...
}
}
傳回結果規則
RESTful
也為處理器的傳回結果制定了規範,實際開發中可作為參考,不一定必須遵守這些規範:
GET/users : 傳回資源清單(資源對象的數組/集合)
GET/users/Tom : 傳回單個資源對象
POST : 傳回新增的資源對象
PUT : 傳回完整的被修改的資源對象
PATCH : 傳回完整的被修改的資源對象
DELETE : 傳回空文檔
RESTful-API
RESTful-API
RESTful-API
是前後端分離項目的溝通方式。
RESTful-API
并非實際的項目代碼,而是一種描述性質的說明文檔,文檔中羅列了所有
URL
的功能,所需要的參數,傳回結果等詳細資訊。示例:
URL : http://myoner.com/users
method : GET
required : true
params : name(使用者姓名)
request headers :
{
"Accept" : "*/*"
}
response body :
{
id,
name,
gender,
age
}
description : 根據姓名擷取使用者資訊