咱們常說的 Spring 其實是指 Spring Framework,而 Spring Framework 只是 Spring 家族中的一個分支而已。那麼 Spring 家族都有哪些東西呢?html
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); } }
原文出處:https://www.cnblogs.com/lenve/p/12052826.html