咱們常說的 Spring 其實是指 Spring Framework,而 Spring Framework 只是 Spring 家族中的一個分支而已。那麼 Spring 家族都有哪些東西呢?php
Spring 是爲了解決企業級應用開發的複雜性而建立的。在 Spring 以前,有一個重量級的工具叫作 EJB,使用 Spring 可讓 Java Bean 之間進行有效的解耦,而這個操做以前只有 EJB 才能完成,EJB 過於臃腫,使用不多。Spring 不只僅侷限於服務端的開發,在測試性和鬆耦合方面都有很好的表現。java
通常來講,初學者主要掌握 Spring 四個方面的功能:mysql
正常來講,咱們在項目中添加 Maven 依賴就能夠直接使用 Spring 了,若是須要單獨下載 jar,下載地址以下:linux
下載成功後,Spring 中的組件,大體上提供了以下功能:web
Ioc (Inversion of Control),中文叫作控制反轉。這是一個概念,也是一種思想。控制反轉,實際上就是指對一個對象的控制權的反轉。例如,以下代碼:spring
public class Book {
private Integer id;
private String name;
private Double price;
//省略getter/setter
}
public class User {
private Integer id;
private String name;
private Integer age;
public void doSth() {
Book book = new Book();
book.setId(1);
book.setName("故事新編");
book.setPrice((double) 20);
}
}
複製代碼
在這種狀況下,Book 對象的控制權在 User 對象裏邊,這樣,Book 和 User 高度耦合,若是在其餘對象中須要使用 Book 對象,得從新建立,也就是說,對象的建立、初始化、銷燬等操做,通通都要開發者本身來完成。若是可以將這些操做交給容器來管理,開發者就能夠極大的從對象的建立中解脫出來。sql
使用 Spring 以後,咱們能夠將對象的建立、初始化、銷燬等操做交給 Spring 容器來管理。就是說,在項目啓動時,全部的 Bean 都將本身註冊到 Spring 容器中去(若是有必要的話),而後若是其餘 Bean 須要使用到這個 Bean ,則不須要本身去 new,而是直接去 Spring 容器去要。數據庫
經過一個簡單的例子看下這個過程。express
首先建立一個普通的 Maven 項目,而後引入 spring-context 依賴,以下:編程
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
</dependencies>
複製代碼
接下來,在 resources 目錄下建立一個 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">
</beans>
複製代碼
在這個文件中,咱們能夠配置全部須要註冊到 Spring 容器的 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 class="org.javaboy.Book" id="book"/>
</beans>
複製代碼
class 屬性表示須要註冊的 bean 的全路徑,id 則表示 bean 的惟一標記,也開能夠 name 屬性做爲 bean 的標記,在超過 99% 的狀況下,id 和 name 實際上是同樣的,特殊狀況下不同。
接下來,加載這個配置文件:
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
}
}
複製代碼
執行 main 方法,配置文件就會被自動加載,進而在 Spring 中初始化一個 Book 實例。此時,咱們顯式的指定 Book 類的無參構造方法,並在無參構造方法中打印日誌,能夠看到無參構造方法執行了,進而證實對象已經在 Spring 容器中初始化了。
最後,經過 getBean 方法,能夠從容器中去獲取對象:
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Book book = (Book) ctx.getBean("book");
System.out.println(book);
}
}
複製代碼
加載方式,除了ClassPathXmlApplicationContext 以外(去 classpath 下查找配置文件),另外也可使用 FileSystemXmlApplicationContext ,FileSystemXmlApplicationContext 會從操做系統路徑下去尋找配置文件。
public class Main {
public static void main(String[] args) {
FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("F:\\workspace5\\workspace\\spring\\spring-ioc\\src\\main\\resources\\applicationContext.xml");
Book book = (Book) ctx.getBean("book");
System.out.println(book);
}
}
複製代碼
在上一小節中,咱們經過 ctx.getBean 方法來從 Spring 容器中獲取 Bean,傳入的參數是 Bean 的 name 或者 id 屬性。除了這種方式以外,也能夠直接經過 Class 去獲取一個 Bean。
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
Book book = ctx.getBean(Book.class);
System.out.println(book);
}
}
複製代碼
這種方式有一個很大的弊端,若是存在多個實例,這種方式就不可用,例如,xml 文件中存在兩個 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 class="org.javaboy.Book" id="book"/>
<bean class="org.javaboy.Book" id="book2"/>
</beans>
複製代碼
此時,若是經過 Class 去查找 Bean,會報以下錯誤:
因此,通常建議使用 name 或者 id 去獲取 Bean 的實例。
在 XML 配置中,屬性的注入存在多種方式。
經過 Bean 的構造方法給 Bean 的屬性注入值。
1.第一步首先給 Bean 添加對應的構造方法:
public class Book {
private Integer id;
private String name;
private Double price;
public Book() {
System.out.println("-------book init----------");
}
public Book(Integer id, String name, Double price) {
this.id = id;
this.name = name;
this.price = price;
}
}
複製代碼
2.在 xml 文件中注入 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 class="org.javaboy.Book" id="book">
<constructor-arg index="0" value="1"/>
<constructor-arg index="1" value="三國演義"/>
<constructor-arg index="2" value="30"/>
</bean>
</beans>
複製代碼
這裏須要注意的是,constructor-arg 中的 index 和 Book 中的構造方法參數一一對應。寫的順序能夠顛倒,可是 index 的值和 value 要一一對應。
另外一種構造方法中的屬性注入,則是經過直接指定參數名來注入:
<bean class="org.javaboy.Book" id="book2">
<constructor-arg name="id" value="2"/>
<constructor-arg name="name" value="紅樓夢"/>
<constructor-arg name="price" value="40"/>
</bean>
複製代碼
若是有多個構造方法,則會根據給出參數個數以及參數類型,自動匹配到對應的構造方法上,進而初始化一個對象。
除了構造方法以外,咱們也能夠經過 set 方法注入值。
<bean class="org.javaboy.Book" id="book3">
<property name="id" value="3"/>
<property name="name" value="水滸傳"/>
<property name="price" value="30"/>
</bean>
複製代碼
set 方法注入,有一個很重要的問題,就是屬性名。不少人會有一種錯覺,以爲屬性名就是你定義的屬性名,這個是不對的。在全部的框架中,凡是涉及到反射注入值的,屬性名通通都不是 Bean 中定義的屬性名,而是經過 Java 中的內省機制分析出來的屬性名,簡單說,就是根據 get/set 方法分析出來的屬性名。
p 名稱空間注入,使用的比較少,它本質上也是調用了 set 方法。
<bean class="org.javaboy.Book" id="book4" p:id="4" p:bookName="西遊記" p:price="33"></bean>
複製代碼
有時候,咱們使用一些外部 Bean,這些 Bean 可能沒有構造方法,而是經過 Builder 來構造的,這個時候,就沒法使用上面的方式來給它注入值了。
例如在 OkHttp 的網絡請求中,原生的寫法以下:
public class OkHttpMain {
public static void main(String[] args) {
OkHttpClient okHttpClient = new OkHttpClient.Builder()
.build();
Request request = new Request.Builder()
.get()
.url("http://b.hiphotos.baidu.com/image/h%3D300/sign=ad628627aacc7cd9e52d32d909032104/32fa828ba61ea8d3fcd2e9ce9e0a304e241f5803.jpg")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
System.out.println(e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
FileOutputStream out = new FileOutputStream(new File("E:\\123.jpg"));
int len;
byte[] buf = new byte[1024];
InputStream is = response.body().byteStream();
while ((len = is.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.close();
is.close();
}
});
}
}
複製代碼
這個 Bean 有一個特色,OkHttpClient 和 Request 兩個實例都不是直接 new 出來的,在調用 Builder 方法的過程當中,都會給它配置一些默認的參數。這種狀況,咱們可使用 靜態工廠注入或者實例工廠注入來給 OkHttpClient 提供一個實例。
1.靜態工廠注入
首先提供一個 OkHttpClient 的靜態工廠:
public class OkHttpUtils {
private static OkHttpClient OkHttpClient;
public static OkHttpClient getInstance() {
if (OkHttpClient == null) {
OkHttpClient = new OkHttpClient.Builder().build();
}
return OkHttpClient;
}
}
複製代碼
在 xml 文件中,配置該靜態工廠:
<bean class="org.javaboy.OkHttpUtils" factory-method="getInstance" id="okHttpClient"></bean>
複製代碼
這個配置表示 OkHttpUtils 類中的 getInstance 是咱們須要的實例,實例的名字就叫 okHttpClient。而後,在 Java 代碼中,獲取到這個實例,就能夠直接使用了。
public class OkHttpMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
OkHttpClient okHttpClient = ctx.getBean("okHttpClient", OkHttpClient.class);
Request request = new Request.Builder()
.get()
.url("http://b.hiphotos.baidu.com/image/h%3D300/sign=ad628627aacc7cd9e52d32d909032104/32fa828ba61ea8d3fcd2e9ce9e0a304e241f5803.jpg")
.build();
Call call = okHttpClient.newCall(request);
call.enqueue(new Callback() {
@Override
public void onFailure(@NotNull Call call, @NotNull IOException e) {
System.out.println(e.getMessage());
}
@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
FileOutputStream out = new FileOutputStream(new File("E:\\123.jpg"));
int len;
byte[] buf = new byte[1024];
InputStream is = response.body().byteStream();
while ((len = is.read(buf)) != -1) {
out.write(buf, 0, len);
}
out.close();
is.close();
}
});
}
}
複製代碼
2.實例工廠注入
實例工廠就是工廠方法是一個實例方法,這樣,工廠類必須實例化以後才能夠調用工廠方法。
此次的工廠類以下:
public class OkHttpUtils {
private OkHttpClient OkHttpClient;
public OkHttpClient getInstance() {
if (OkHttpClient == null) {
OkHttpClient = new OkHttpClient.Builder().build();
}
return OkHttpClient;
}
}
複製代碼
此時,在 xml 文件中,須要首先提供工廠方法的實例,而後才能夠調用工廠方法:
<bean class="org.javaboy.OkHttpUtils" id="okHttpUtils"/>
<bean class="okhttp3.OkHttpClient" factory-bean="okHttpUtils" factory-method="getInstance" id="okHttpClient"></bean>
複製代碼
本身寫的 Bean 通常不會使用這兩種方式注入,可是,若是須要引入外部 jar,外部 jar 的類的初始化,有可能須要使用這兩種方式。
<bean class="org.javaboy.User" id="user">
<property name="cat" ref="cat"/>
</bean>
<bean class="org.javaboy.Cat" id="cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
</bean>
複製代碼
能夠經過 xml 注入對象,經過 ref 來引用一個對象。
數組注入和集合注入在 xml 中的配置是同樣的。以下:
<bean class="org.javaboy.User" id="user">
<property name="cat" ref="cat"/>
<property name="favorites">
<array>
<value>足球</value>
<value>籃球</value>
<value>乒乓球</value>
</array>
</property>
</bean>
<bean class="org.javaboy.Cat" id="cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
</bean>
複製代碼
注意,array 節點,也能夠被 list 節點代替。
固然,array 或者 list 節點中也能夠是對象。
<bean class="org.javaboy.User" id="user">
<property name="cat" ref="cat"/>
<property name="favorites">
<list>
<value>足球</value>
<value>籃球</value>
<value>乒乓球</value>
</list>
</property>
<property name="cats">
<list>
<ref bean="cat"/>
<ref bean="cat2"/>
<bean class="org.javaboy.Cat" id="cat3">
<property name="name" value="小花"/>
<property name="color" value="花色"/>
</bean>
</list>
</property>
</bean>
<bean class="org.javaboy.Cat" id="cat">
<property name="name" value="小白"/>
<property name="color" value="白色"/>
</bean>
<bean class="org.javaboy.Cat" id="cat2">
<property name="name" value="小黑"/>
<property name="color" value="黑色"/>
</bean>
複製代碼
注意,便可以經過 ref 使用外部定義好的 Bean,也能夠直接在 list 或者 array 節點中定義 bean。
<property name="map">
<map>
<entry key="age" value="99"/>
<entry key="name" value="javaboy"/>
</map>
</property>
複製代碼
<property name="info">
<props>
<prop key="age">99</prop>
<prop key="name">javaboy</prop>
</props>
</property>
複製代碼
以上 Demo,定義的 User 以下:
public class User {
private Integer id;
private String name;
private Integer age;
private Cat cat;
private String[] favorites;
private List<Cat> cats;
private Map<String,Object> map;
private Properties info;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", cat=" + cat +
", favorites=" + Arrays.toString(favorites) +
", cats=" + cats +
", map=" + map +
", info=" + info +
'}';
}
public Properties getInfo() {
return info;
}
public void setInfo(Properties info) {
this.info = info;
}
public Map<String, Object> getMap() {
return map;
}
public void setMap(Map<String, Object> map) {
this.map = map;
}
public List<Cat> getCats() {
return cats;
}
public void setCats(List<Cat> cats) {
this.cats = cats;
}
public String[] getFavorites() {
return favorites;
}
public void setFavorites(String[] favorites) {
this.favorites = favorites;
}
public User() {
}
public User(Integer id, String name, Integer age, Cat cat) {
this.id = id;
this.name = name;
this.age = age;
this.cat = cat;
}
public Cat getCat() {
return cat;
}
public void setCat(Cat cat) {
this.cat = cat;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
複製代碼
在 Spring 中,想要將一個 Bean 註冊到 Spring 容器中,總體上來講,有三種不一樣的方式。
這裏咱們來看 Java 配置。
Java 配置這種方式在 Spring Boot 出現以前,其實不多使用,自從有了 Spring Boot,Java 配置開發被普遍使用,由於在 Spring Boot 中,不使用一行 XML 配置。
例如我有以下一個 Bean:
public class SayHello {
public String sayHello(String name) {
return "hello " + name;
}
}
複製代碼
在 Java 配置中,咱們用一個 Java 配置類去代替以前的 applicationContext.xml 文件。
@Configuration
public class JavaConfig {
@Bean
SayHello sayHello() {
return new SayHello();
}
}
複製代碼
首先在配置類上有一個 @Configuration 註解,這個註解表示這個類不是一個普通類,而是一個配置類,它的做用至關於 applicationContext.xml。 而後,定義方法,方法返回對象,方法上添加 @Bean 註解,表示將這個方法的返回值注入的 Spring 容器中去。也就是說,@Bean 所對應的方法,就至關於 applicationContext.xml 中的 bean 節點。
既然是配置類,咱們須要在項目啓動時加載配置類。
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
SayHello hello = ctx.getBean(SayHello.class);
System.out.println(hello.sayHello("javaboy"));
}
}
複製代碼
注意,配置的加載,是使用 AnnotationConfigApplicationContext 來實現。
關於 Java 配置,這裏有一個須要注意的問題:Bean 的名字是什麼?
Bean 的默認名稱是方法名。以上面的案例爲例,Bean 的名字是 sayHello。 若是開發者想自定義方法名,也是能夠的,直接在 @Bean 註解中進行過配置。以下配置表示修改 Bean 的名字爲 javaboy:
@Configuration
public class JavaConfig {
@Bean("javaboy")
SayHello sayHello() {
return new SayHello();
}
}
複製代碼
在咱們實際開發中,大量的使用自動配置。
自動化配置既能夠經過 Java 配置來實現,也能夠經過 xml 配置來實現。
例如我有一個 UserService,我但願在自動化掃描時,這個類可以自動註冊到 Spring 容器中去,那麼能夠給該類添加一個 @Service,做爲一個標記。
和 @Service 註解功能相似的註解,一共有四個:
這四個中,另外三個都是基於 @Component 作出來的,並且從目前的源碼來看,功能也是一致的,那麼爲何要搞三個呢?主要是爲了在不一樣的類上面添加時方便。
@Service
public class UserService {
public List<String> getAllUser() {
List<String> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add("javaboy:" + i);
}
return users;
}
}
複製代碼
添加完成後,自動化掃描有兩種方式,一種就是經過 Java 代碼配置自動化掃描,另外一種則是經過 xml 文件來配置自動化掃描。
@Configuration
@ComponentScan(basePackages = "org.javaboy.javaconfig.service")
public class JavaConfig {
}
複製代碼
而後,在項目啓動中加載配置類,在配置類中,經過 @ComponentScan 註解指定要掃描的包(若是不指定,默認狀況下掃描的是配置類所在的包下載的 Bean 以及配置類所在的包下的子包下的類),而後就能夠獲取 UserService 的實例了:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
UserService userService = ctx.getBean(UserService.class);
System.out.println(userService.getAllUser());
}
}
複製代碼
這裏有幾個問題須要注意:
1.Bean 的名字叫什麼?
默認狀況下,Bean 的名字是類名首字母小寫。例如上面的 UserService,它的實例名,默認就是 userService。若是開發者想要自定義名字,就直接在 @Service 註解中添加便可。
2.有幾種掃描方式?
上面的配置,咱們是按照包的位置來掃描的。也就是說,Bean 必須放在指定的掃描位置,不然,即便你有 @Service 註解,也掃描不到。
除了按照包的位置來掃描,還有另一種方式,就是根據註解來掃描。例如以下配置:
@Configuration
@ComponentScan(basePackages = "org.javaboy.javaconfig",useDefaultFilters = true,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,classes = Controller.class)})
public class JavaConfig {
}
複製代碼
這個配置表示掃描 org.javaboy.javaconfig 下的全部 Bean,可是除了 Controller。
<context:component-scan base-package="org.javaboy.javaconfig"/>
複製代碼
上面這行配置表示掃描 org.javaboy.javaconfig 下的全部 Bean。固然也能夠按照類來掃描。
XML 配置完成後,在 Java 代碼中加載 XML 配置便可。
public class XMLTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = ctx.getBean(UserService.class);
List<String> list = userService.getAllUser();
System.out.println(list);
}
}
複製代碼
也能夠在 XML 配置中按照註解的類型進行掃描:
<context:component-scan base-package="org.javaboy.javaconfig" use-default-filters="true">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
複製代碼
自動掃描時的對象注入有三種方式:
@Autowired 是根據類型去查找,而後賦值,這就有一個要求,這個類型只能夠有一個對象,不然就會報錯。@Resources 是根據名稱去查找,默認狀況下,定義的變量名,就是查找的名稱,固然開發者也能夠在 @Resources 註解中手動指定。因此,若是一個類存在多個實例,那麼就應該使用 @Resources 去注入,若是很是使用 @Autowired,也是能夠的,此時須要配合另一個註解,@Qualifier,在 @Qualifier 中能夠指定變量名,兩個一塊兒用(@Qualifier 和 @Autowired)就能夠實現經過變量名查找到變量。
@Service
public class UserService {
@Autowired
UserDao userDao;
public String hello() {
return userDao.hello();
}
public List<String> getAllUser() {
List<String> users = new ArrayList<>();
for (int i = 0; i < 10; i++) {
users.add("javaboy:" + i);
}
return users;
}
}
複製代碼
條件註解就是在知足某一個條件的狀況下,生效的配置。
首先在 Windows 中如何獲取操做系統信息?Windows 中查看文件夾目錄的命令是 dir,Linux 中查看文件夾目錄的命令是 ls,我如今但願當系統運行在 Windows 上時,自動打印出 Windows 上的目錄展現命令,Linux 運行時,則自動展現 Linux 上的目錄展現命令。
首先定義一個顯示文件夾目錄的接口:
public interface ShowCmd {
String showCmd();
}
複製代碼
而後,分別實現 Windows 下的實例和 Linux 下的實例:
public class WinShowCmd implements ShowCmd {
@Override
public String showCmd() {
return "dir";
}
}
public class LinuxShowCmd implements ShowCmd {
@Override
public String showCmd() {
return "ls";
}
}
複製代碼
接下來,定義兩個條件,一個是 Windows 下的條件,另外一個是 Linux 下的條件。
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("windows");
}
}
public class LinuxCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return context.getEnvironment().getProperty("os.name").toLowerCase().contains("linux");
}
}
複製代碼
接下來,在定義 Bean 的時候,就能夠去配置條件註解了。
@Configuration
public class JavaConfig {
@Bean("showCmd")
@Conditional(WindowsCondition.class)
ShowCmd winCmd() {
return new WinShowCmd();
}
@Bean("showCmd")
@Conditional(LinuxCondition.class)
ShowCmd linuxCmd() {
return new LinuxShowCmd();
}
}
複製代碼
這裏,必定要給兩個 Bean 取相同的名字,這樣在調用時,才能夠自動匹配。而後,給每個 Bean 加上條件註解,當條件中的 matches 方法返回 true 的時候,這個 Bean 的定義就會生效。
public class JavaMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
ShowCmd showCmd = (ShowCmd) ctx.getBean("showCmd");
System.out.println(showCmd.showCmd());
}
}
複製代碼
條件註解有一個很是典型的使用場景,就是多環境切換。
開發中,如何在 開發/生產/測試 環境之間進行快速切換?Spring 中提供了 Profile 來解決這個問題,Profile 的底層就是條件註解。這個從 @Profile 註解的定義就能夠看出來:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ProfileCondition.class)
public @interface Profile {
/** * The set of profiles for which the annotated component should be registered. */
String[] value();
}
class ProfileCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
if (attrs != null) {
for (Object value : attrs.get("value")) {
if (context.getEnvironment().acceptsProfiles(Profiles.of((String[]) value))) {
return true;
}
}
return false;
}
return true;
}
}
複製代碼
咱們定義一個 DataSource:
public class DataSource {
private String url;
private String username;
private String password;
@Override
public String toString() {
return "DataSource{" +
"url='" + url + '\'' +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
複製代碼
而後,在配置 Bean 時,經過 @Profile 註解指定不一樣的環境:
@Bean("ds")
@Profile("dev")
DataSource devDataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/dev");
dataSource.setUsername("root");
dataSource.setPassword("123");
return dataSource;
}
@Bean("ds")
@Profile("prod")
DataSource prodDataSource() {
DataSource dataSource = new DataSource();
dataSource.setUrl("jdbc:mysql://192.158.222.33:3306/dev");
dataSource.setUsername("jkldasjfkl");
dataSource.setPassword("jfsdjflkajkld");
return dataSource;
}
複製代碼
最後,在加載配置類,注意,須要先設置當前環境,而後再去加載配置類:
public class JavaMain {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setActiveProfiles("dev");
ctx.register(JavaConfig.class);
ctx.refresh();
DataSource ds = (DataSource) ctx.getBean("ds");
System.out.println(ds);
}
}
複製代碼
這個是在 Java 代碼中配置的。環境的切換,也能夠在 XML 文件中配置,以下配置在 XML 文件中,必須放在其餘節點後面。
<beans profile="dev">
<bean class="org.javaboy.DataSource" id="dataSource">
<property name="url" value="jdbc:mysql:///devdb"/>
<property name="password" value="root"/>
<property name="username" value="root"/>
</bean>
</beans>
<beans profile="prod">
<bean class="org.javaboy.DataSource" id="dataSource">
<property name="url" value="jdbc:mysql://111.111.111.111/devdb"/>
<property name="password" value="jsdfaklfj789345fjsd"/>
<property name="username" value="root"/>
</bean>
</beans>
複製代碼
啓動類中設置當前環境並加載配置:
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext();
ctx.getEnvironment().setActiveProfiles("prod");
ctx.setConfigLocation("applicationContext.xml");
ctx.refresh();
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
System.out.println(dataSource);
}
}
複製代碼
在 XML 配置中註冊的 Bean,或者用 Java 配置註冊的 Bean,若是我屢次獲取,獲取到的對象是不是同一個?
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = ctx.getBean("user", User.class);
User user2 = ctx.getBean("user", User.class);
System.out.println(user==user2);
}
}
複製代碼
如上,從 Spring 容器中屢次獲取同一個 Bean,默認狀況下,獲取到的其實是同一個實例。固然咱們能夠本身手動配置。
<bean class="org.javaboy.User" id="user" scope="prototype" />
複製代碼
經過在 XML 節點中,設置 scope 屬性,咱們能夠調整默認的實例個數。scope 的值爲 singleton(默認),表示這個 Bean 在 Spring 容器中,是以單例的形式存在,若是 scope 的值爲 prototype,表示這個 Bean 在 Spring 容器中不是單例,屢次獲取將拿到多個不一樣的實例。
除了 singleton 和 prototype 以外,還有兩個取值,request 和 session。這兩個取值在 web 環境下有效。這是在 XML 中的配置,咱們也能夠在 Java 中配置。
@Configuration
public class JavaConfig {
@Bean
@Scope("prototype")
SayHello sayHello() {
return new SayHello();
}
}
複製代碼
在 Java 代碼中,咱們能夠經過 @Scope 註解指定 Bean 的做用域。
固然,在自動掃描配置中,也能夠指定 Bean 的做用域。
@Repository
@Scope("prototype")
public class UserDao {
public String hello() {
return "userdao";
}
}
複製代碼
在 XML 配置中,咱們能夠看到,便可以經過 id 給 Bean 指定一個惟一標識符,也能夠經過 name 來指定,大部分狀況下這兩個做用是同樣的,有一個小小區別:
name 支持取多個。多個 name 之間,用 , 隔開:
<bean class="org.javaboy.User" name="user,user1,user2,user3" scope="prototype"/>
複製代碼
此時,經過 user、user一、user二、user3 均可以獲取到當前對象:
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = ctx.getBean("user", User.class);
User user2 = ctx.getBean("user2", User.class);
System.out.println(user);
System.out.println(user2);
}
}
複製代碼
而 id 不支持有多個值。若是強行用 , 隔開,它仍是一個值。例如以下配置:
<bean class="org.javaboy.User" id="user,user1,user2,user3" scope="prototype" />
複製代碼
這個配置表示 Bean 的名字爲 user,user1,user2,user3
,具體調用以下:
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = ctx.getBean("user,user1,user2,user3", User.class);
User user2 = ctx.getBean("user,user1,user2,user3", User.class);
System.out.println(user);
System.out.println(user2);
}
}
複製代碼
混合配置就是 Java 配置+XML 配置。混用的話,能夠在 Java 配置中引入 XML 配置。
@Configuration
@ImportResource("classpath:applicationContext.xml")
public class JavaConfig {
}
複製代碼
在 Java 配置中,經過 @ImportResource 註解能夠導入一個 XML 配置。
Aware 接口,從字面上理解就是感知捕獲。單純的一個 Bean 是沒有知覺的。
在 3.6.4 節的場景中,之因此 UserDao 可以注入到 UserService ,有一個前提,就是它兩個都是被 Spring 容器管理的。若是直接 new 一個 UserService,這是沒用的,由於 UserService 沒有被 Spring 容器管理,因此也不會給它裏邊注入 Bean。
在實際開發中,咱們可能會遇到一些類,須要獲取到容器的詳細信息,那就能夠經過 Aware 接口來實現。
Aware 是一個空接口,有不少實現類:
這些實現的接口,有一些公共特性:
每個子接口均提供了一個 set 方法,方法的參數就是當前 Bean 須要感知的內容,所以咱們須要在 Bean 中聲明相關的成員變量來接受這個參數。接收到這個參數後,就能夠經過這個參數獲取到容器的詳細信息了。
@Component
public class SayHello implements ApplicationContextAware {
private ApplicationContext applicationContext;
public String sayHello(String name) {
//判斷容器中是否存在某個 Bean
boolean userDao = applicationContext.containsBean("userDao333");
System.out.println(userDao);
return "hello " + name;
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
複製代碼
Aop(Aspect Oriented Programming),面向切面編程,這是對面向對象思想的一種補充。
面向切面編程,就是在程序運行時,不改變程序源碼的狀況下,動態的加強方法的功能,常見的使用場景很是多:
這些操做中,無一例外,都有不少模板化的代碼,而解決模板化代碼,消除臃腫就是 Aop 的強項。
在 Aop 中,有幾個常見的概念:
概念 | 說明 |
---|---|
切點 | 要添加代碼的地方,稱做切點 |
通知(加強) | 通知就是向切點動態添加的代碼 |
切面 | 切點+通知 |
鏈接點 | 切點的定義 |
在 Aop 實際上集基於 Java 動態代理來實現的。
Java 中的動態代理有兩種實現方式:
基於 JDK 的動態代理。
1.定義一個計算器接口:
public interface MyCalculator {
int add(int a, int b);
}
複製代碼
2.定義計算機接口的實現:
public class MyCalculatorImpl implements MyCalculator {
public int add(int a, int b) {
return a+b;
}
}
複製代碼
3.定義代理類
public class CalculatorProxy {
public static Object getInstance(final MyCalculatorImpl myCalculator) {
return Proxy.newProxyInstance(CalculatorProxy.class.getClassLoader(), myCalculator.getClass().getInterfaces(), new InvocationHandler() {
/** * @param proxy 代理對象 * @param method 代理的方法 * @param args 方法的參數 * @return * @throws Throwable */
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName()+"方法開始執行啦...");
Object invoke = method.invoke(myCalculator, args);
System.out.println(method.getName()+"方法執行結束啦...");
return invoke;
}
});
}
}
複製代碼
Proxy.newProxyInstance 方法接收三個參數,第一個是一個 classloader,第二個是代理多項實現的接口,第三個是代理對象方法的處理器,全部要額外添加的行爲都在 invoke 方法中實現。
Spring 中的 Aop 的通知類型有 5 種:
具體實現,這裏的案例和 5.2 中的同樣,依然是給計算器的方法加強功能。
首先,在項目中,引入 Spring 依賴(此次須要引入 Aop 相關的依賴):
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
</dependencies>
複製代碼
接下來,定義切點,這裏介紹兩種切點的定義方式:
其中,使用自定義註解標記切點,是侵入式的,因此這種方式在實際開發中不推薦,僅做爲了解,另外一種使用規則來定義切點的方式,無侵入,通常推薦使用這種方式。
自定義註解
首先自定義一個註解:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Action {
}
複製代碼
而後在須要攔截的方法上,添加該註解,在 add 方法上添加了 @Action 註解,表示該方法將會被 Aop 攔截,而其餘未添加該註解的方法則不受影響。
@Component
public class MyCalculatorImpl {
@Action
public int add(int a, int b) {
return a + b;
}
public void min(int a, int b) {
System.out.println(a + "-" + b + "=" + (a - b));
}
}
複製代碼
接下來,定義加強(通知、Advice):
@Component
@Aspect//表示這是一個切面
public class LogAspect {
/** * @param joinPoint 包含了目標方法的關鍵信息 * @Before 註解表示這是一個前置通知,即在目標方法執行以前執行,註解中,須要填入切點 */
@Before(value = "@annotation(Action)")
public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法開始執行了...");
}
/** * 後置通知 * @param joinPoint 包含了目標方法的全部關鍵信息 * @After 表示這是一個後置通知,即在目標方法執行以後執行 */
@After("@annotation(Action)")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法執行結束了...");
}
/** * @param joinPoint * @@AfterReturning 表示這是一個返回通知,即有目標方法有返回值的時候纔會觸發,該註解中的 returning 屬性表示目標方法返回值的變量名,這個須要和參數一一對應嗎,注意:目標方法的返回值類型要和這裏方法返回值參數的類型一致,不然攔截不到,若是想攔截全部(包括返回值爲 void),則方法返回值參數能夠爲 Object */
@AfterReturning(value = "@annotation(Action)",returning = "r")
public void returing(JoinPoint joinPoint,Integer r) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法返回:"+r);
}
/** * 異常通知 * @param joinPoint * @param e 目標方法所拋出的異常,注意,這個參數必須是目標方法所拋出的異常或者所拋出的異常的父類,只有這樣,纔會捕獲。若是想攔截全部,參數類型聲明爲 Exception */
@AfterThrowing(value = "@annotation(Action)",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法拋異常了:"+e.getMessage());
}
/** * 環繞通知 * * 環繞通知是集大成者,能夠用環繞通知實現上面的四個通知,這個方法的核心有點相似於在這裏經過反射執行方法 * @param pjp * @return 注意這裏的返回值類型最好是 Object ,和攔截到的方法相匹配 */
@Around("@annotation(Action)")
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//這個至關於 method.invoke 方法,咱們能夠在這個方法的先後分別添加日誌,就至關因而前置/後置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
複製代碼
通知定義完成後,接下來在配置類中,開啓包掃描和自動代理:
@Configuration
@ComponentScan
@EnableAspectJAutoProxy//開啓自動代理
public class JavaConfig {
}
複製代碼
而後,在 Main 方法中,開啓調用:
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JavaConfig.class);
MyCalculatorImpl myCalculator = ctx.getBean(MyCalculatorImpl.class);
myCalculator.add(3, 4);
myCalculator.min(3, 4);
}
}
複製代碼
再來回顧 LogAspect 切面,咱們發現,切點的定義不夠靈活,以前的切點是直接寫在註解裏邊的,這樣,若是要修改切點,每一個方法上都要修改,所以,咱們能夠將切點統必定義,而後統一調用。
@Component
@Aspect//表示這是一個切面
public class LogAspect {
/** * 能夠統必定義切點 */
@Pointcut("@annotation(Action)")
public void pointcut() {
}
/** * @param joinPoint 包含了目標方法的關鍵信息 * @Before 註解表示這是一個前置通知,即在目標方法執行以前執行,註解中,須要填入切點 */
@Before(value = "pointcut()")
public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法開始執行了...");
}
/** * 後置通知 * @param joinPoint 包含了目標方法的全部關鍵信息 * @After 表示這是一個後置通知,即在目標方法執行以後執行 */
@After("pointcut()")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法執行結束了...");
}
/** * @param joinPoint * @@AfterReturning 表示這是一個返回通知,即有目標方法有返回值的時候纔會觸發,該註解中的 returning 屬性表示目標方法返回值的變量名,這個須要和參數一一對應嗎,注意:目標方法的返回值類型要和這裏方法返回值參數的類型一致,不然攔截不到,若是想攔截全部(包括返回值爲 void),則方法返回值參數能夠爲 Object */
@AfterReturning(value = "pointcut()",returning = "r")
public void returing(JoinPoint joinPoint,Integer r) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法返回:"+r);
}
/** * 異常通知 * @param joinPoint * @param e 目標方法所拋出的異常,注意,這個參數必須是目標方法所拋出的異常或者所拋出的異常的父類,只有這樣,纔會捕獲。若是想攔截全部,參數類型聲明爲 Exception */
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法拋異常了:"+e.getMessage());
}
/** * 環繞通知 * * 環繞通知是集大成者,能夠用環繞通知實現上面的四個通知,這個方法的核心有點相似於在這裏經過反射執行方法 * @param pjp * @return 注意這裏的返回值類型最好是 Object ,和攔截到的方法相匹配 */
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//這個至關於 method.invoke 方法,咱們能夠在這個方法的先後分別添加日誌,就至關因而前置/後置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
複製代碼
可是,你們也注意到,使用註解是侵入式的,咱們還能夠繼續優化,改成非侵入式的。從新定義切點,新切點的定義就不在須要 @Action 註解了,要攔截的目標方法上也不用添加 @Action 註解。下面這種方式是更爲通用的攔截方式:
@Component
@Aspect//表示這是一個切面
public class LogAspect {
/** * 能夠統必定義切點 */
@Pointcut("@annotation(Action)")
public void pointcut2() {
}
/** * 能夠統必定義切點 * 第一個 * 表示要攔截的目標方法返回值任意(也能夠明確指定返回值類型 * 第二個 * 表示包中的任意類(也能夠明確指定類 * 第三個 * 表示類中的任意方法 * 最後面的兩個點表示方法參數任意,個數任意,類型任意 */
@Pointcut("execution(* org.javaboy.aop.commons.*.*(..))")
public void pointcut() {
}
/** * @param joinPoint 包含了目標方法的關鍵信息 * @Before 註解表示這是一個前置通知,即在目標方法執行以前執行,註解中,須要填入切點 */
@Before(value = "pointcut()")
public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法開始執行了...");
}
/** * 後置通知 * @param joinPoint 包含了目標方法的全部關鍵信息 * @After 表示這是一個後置通知,即在目標方法執行以後執行 */
@After("pointcut()")
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法執行結束了...");
}
/** * @param joinPoint * @@AfterReturning 表示這是一個返回通知,即有目標方法有返回值的時候纔會觸發,該註解中的 returning 屬性表示目標方法返回值的變量名,這個須要和參數一一對應嗎,注意:目標方法的返回值類型要和這裏方法返回值參數的類型一致,不然攔截不到,若是想攔截全部(包括返回值爲 void),則方法返回值參數能夠爲 Object */
@AfterReturning(value = "pointcut()",returning = "r")
public void returing(JoinPoint joinPoint,Integer r) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法返回:"+r);
}
/** * 異常通知 * @param joinPoint * @param e 目標方法所拋出的異常,注意,這個參數必須是目標方法所拋出的異常或者所拋出的異常的父類,只有這樣,纔會捕獲。若是想攔截全部,參數類型聲明爲 Exception */
@AfterThrowing(value = "pointcut()",throwing = "e")
public void afterThrowing(JoinPoint joinPoint,Exception e) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法拋異常了:"+e.getMessage());
}
/** * 環繞通知 * * 環繞通知是集大成者,能夠用環繞通知實現上面的四個通知,這個方法的核心有點相似於在這裏經過反射執行方法 * @param pjp * @return 注意這裏的返回值類型最好是 Object ,和攔截到的方法相匹配 */
@Around("pointcut()")
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//這個至關於 method.invoke 方法,咱們能夠在這個方法的先後分別添加日誌,就至關因而前置/後置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
複製代碼
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.5</version>
</dependency>
複製代碼
接下來,定義通知/加強,可是單純定義本身的行爲便可,再也不須要註解:
public class LogAspect {
public void before(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法開始執行了...");
}
public void after(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法執行結束了...");
}
public void returing(JoinPoint joinPoint,Integer r) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法返回:"+r);
}
public void afterThrowing(JoinPoint joinPoint,Exception e) {
Signature signature = joinPoint.getSignature();
String name = signature.getName();
System.out.println(name + "方法拋異常了:"+e.getMessage());
}
public Object around(ProceedingJoinPoint pjp) {
Object proceed = null;
try {
//這個至關於 method.invoke 方法,咱們能夠在這個方法的先後分別添加日誌,就至關因而前置/後置通知
proceed = pjp.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
return proceed;
}
}
複製代碼
接下來在 spring 中配置 Aop:
<bean class="org.javaboy.aop.LogAspect" id="logAspect"/>
<aop:config>
<aop:pointcut id="pc1" expression="execution(* org.javaboy.aop.commons.*.*(..))"/>
<aop:aspect ref="logAspect">
<aop:before method="before" pointcut-ref="pc1"/>
<aop:after method="after" pointcut-ref="pc1"/>
<aop:after-returning method="returing" pointcut-ref="pc1" returning="r"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
<aop:around method="around" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
複製代碼
最後,在 Main 方法中加載配置文件:
public class Main {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
MyCalculatorImpl myCalculator = ctx.getBean(MyCalculatorImpl.class);
myCalculator.add(3, 4);
myCalculator.min(5, 6);
}
}
複製代碼
JdbcTemplate 是 Spring 利用 Aop 思想封裝的 JDBC 操做工具。
建立一個新項目,添加以下依賴:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.9.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.17</version>
</dependency>
</dependencies>
複製代碼
準備數據庫:
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test01` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `test01`;
/*Table structure for table `user` */
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`address` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
複製代碼
準備一個實體類:
public class User {
private Integer id;
private String username;
private String address;
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", address='" + address + '\'' +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
複製代碼
提供一個配置類,在配置類中配置 JdbcTemplate:
@Configuration
public class JdbcConfig {
@Bean
DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setUsername("root");
dataSource.setPassword("123");
dataSource.setUrl("jdbc:mysql:///test01");
return dataSource;
}
@Bean
JdbcTemplate jdbcTemplate() {
return new JdbcTemplate(dataSource());
}
}
複製代碼
這裏,提供兩個 Bean,一個是 DataSource 的 Bean,另外一個是 JdbcTemplate 的 Bean,JdbcTemplate 的配置很是容易,只須要 new 一個 Bean 出來,而後配置一下 DataSource 就i能夠。
public class Main {
private JdbcTemplate jdbcTemplate;
@Before
public void before() {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(JdbcConfig.class);
jdbcTemplate = ctx.getBean(JdbcTemplate.class);
}
@Test
public void insert() {
jdbcTemplate.update("insert into user (username,address) values (?,?);", "javaboy", "www.javaboy.org");
}
@Test
public void update() {
jdbcTemplate.update("update user set username=? where id=?", "javaboy123", 1);
}
@Test
public void delete() {
jdbcTemplate.update("delete from user where id=?", 2);
}
@Test
public void select() {
User user = jdbcTemplate.queryForObject("select * from user where id=?", new BeanPropertyRowMapper<User>(User.class), 1);
System.out.println(user);
}
}
複製代碼
在查詢時,若是使用了 BeanPropertyRowMapper,要求查出來的字段必須和 Bean 的屬性名一一對應。若是不同,則不要使用 BeanPropertyRowMapper,此時須要自定義 RowMapper 或者給查詢的字段取別名。
@Test
public void select2() {
User user = jdbcTemplate.queryForObject("select id,username as name,address from user where id=?", new BeanPropertyRowMapper<User>(User.class), 1);
System.out.println(user);
}
複製代碼
2.自定義 RowMapper
@Test
public void select3() {
User user = jdbcTemplate.queryForObject("select * from user where id=?", new RowMapper<User>() {
public User mapRow(ResultSet resultSet, int i) throws SQLException {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String address = resultSet.getString("address");
User u = new User();
u.setId(id);
u.setName(username);
u.setAddress(address);
return u;
}
}, 1);
System.out.println(user);
}
複製代碼
查詢多條記錄,方式以下:
@Test
public void select4() {
List<User> list = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));
System.out.println(list);
}
複製代碼
以上配置,也能夠經過 XML 文件來實現。經過 XML 文件實現只是提供 JdbcTemplate 實例,剩下的代碼仍是 Java 代碼,就是 JdbcConfig 被 XML 文件代替而已。
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="username" value="root"/>
<property name="password" value="123"/>
<property name="url" value="jdbc:mysql:///test01?serverTimezone=Asia/Shanghai"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
複製代碼
配置完成後,加載該配置文件,並啓動:
public class Main {
private JdbcTemplate jdbcTemplate;
@Before
public void before() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
jdbcTemplate = ctx.getBean(JdbcTemplate.class);
}
@Test
public void insert() {
jdbcTemplate.update("insert into user (username,address) values (?,?);", "javaboy", "www.javaboy.org");
}
@Test
public void update() {
jdbcTemplate.update("update user set username=? where id=?", "javaboy123", 1);
}
@Test
public void delete() {
jdbcTemplate.update("delete from user where id=?", 2);
}
@Test
public void select() {
User user = jdbcTemplate.queryForObject("select * from user where id=?", new BeanPropertyRowMapper<User>(User.class), 1);
System.out.println(user);
}
@Test
public void select4() {
List<User> list = jdbcTemplate.query("select * from user", new BeanPropertyRowMapper<>(User.class));
System.out.println(list);
}
@Test
public void select2() {
User user = jdbcTemplate.queryForObject("select id,username as name,address from user where id=?", new BeanPropertyRowMapper<User>(User.class), 1);
System.out.println(user);
}
@Test
public void select3() {
User user = jdbcTemplate.queryForObject("select * from user where id=?", new RowMapper<User>() {
public User mapRow(ResultSet resultSet, int i) throws SQLException {
int id = resultSet.getInt("id");
String username = resultSet.getString("username");
String address = resultSet.getString("address");
User u = new User();
u.setId(id);
u.setName(username);
u.setAddress(address);
return u;
}
}, 1);
System.out.println(user);
}
}
複製代碼
Spring 中的事務主要是利用 Aop 思想,簡化事務的配置,能夠經過 Java 配置也能夠經過 XML 配置。
準備工做:
咱們經過一個轉帳操做來看下 Spring 中的事務配置。
首先準備 SQL:
CREATE DATABASE /*!32312 IF NOT EXISTS*/`test01` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci */ /*!80016 DEFAULT ENCRYPTION='N' */;
USE `test01`;
/*Table structure for table `account` */
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
`money` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
/*Data for the table `account` */
insert into `account`(`id`,`username`,`money`) values (1,'zhangsan',1000),(2,'lisi',1000);
複製代碼
而後配置 JdbcTemplate ,JdbcTemplate 的配置和第 6 小節一致。
而後,提供轉帳操做的方法:
@Repository
public class UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
public void addMoney(String username, Integer money) {
jdbcTemplate.update("update account set money=money+? where username=?", money, username);
}
public void minMoney(String username, Integer money) {
jdbcTemplate.update("update account set money=money-? where username=?", money, username);
}
}
@Service
public class UserService {
@Autowired
UserDao userDao;
public void updateMoney() {
userDao.addMoney("zhangsan", 200);
int i = 1 / 0;
userDao.minMoney("lisi", 200);
}
}
複製代碼
最後,在 XML 文件中,開啓自動化掃描:
<context:component-scan base-package="org.javaboy"/>
<bean class="org.springframework.jdbc.datasource.DriverManagerDataSource" id="dataSource">
<property name="username" value="root"/>
<property name="password" value="123"/>
<property name="url" value="jdbc:mysql:///test01?serverTimezone=Asia/Shanghai"/>
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
</bean>
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
複製代碼
XML 中配置事務一共分爲三個步驟:
1.配置 TransactionManager
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
複製代碼
2.配置事務要處理的方法
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="update*"/>
<tx:method name="insert*"/>
<tx:method name="add*"/>
<tx:method name="delete*"/>
</tx:attributes>
</tx:advice>
複製代碼
注意,一旦配置了方法名稱規則以後,service 中的方法必定要按照這裏的名稱規則來,不然事務配置不會生效
3.配置 Aop
<aop:config>
<aop:pointcut id="pc1" expression="execution(* org.javaboy.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>
</aop:config>
複製代碼
4.測試
@Before
public void before() {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
jdbcTemplate = ctx.getBean(JdbcTemplate.class);
userService = ctx.getBean(UserService.class);
}
@Test
public void test1() {
userService.updateMoney();
}
複製代碼
若是要開啓 Java 註解配置,在 XML 配置中添加以下配置:
<tx:annotation-driven transaction-manager="transactionManager" />
複製代碼
這行配置,能夠代替下面兩個配置:
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="update*"/>
<tx:method name="insert*"/>
<tx:method name="add*"/>
<tx:method name="delete*"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="pc1" expression="execution(* org.javaboy.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc1"/>
</aop:config>
複製代碼
而後,在須要添加事務的方法上,添加 @Transactional 註解,表示該方法開啓事務,固然,這個註解也能夠放在類上,表示這個類中的全部方法都開啓事務。
@Service
public class UserService {
@Autowired
UserDao userDao;
@Transactional
public void updateMoney() {
userDao.addMoney("zhangsan", 200);
int i = 1 / 0;
userDao.minMoney("lisi", 200);
}
}
複製代碼