Spring5開發教程
簡介
spring特性
-
sping是輕量級的開源的JavaEE框架
-
Spring可以解決企業應用開發的復雜性
-
Sping兩個核心的部分:IOC和AOC
IOC:控制反轉。把創建對象的過程交給sping進行管理,而不需要自己去new
AOP:面向切面。不修改源代碼進行功能增強
-
Sping特點
方便接耦,簡化開發。
AOP編程支持
方便程序的測試
方便集成各種優秀框架
方便進行事務操作
降低API開發難度
簡單使用
如果使用maven構建項目,導入spring核心依賴包就可以了:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
隨便創建一個User類,然后創建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">
<!--配置User對象創建-->
<bean id="user" class="com.atguigu.spring5.User"></bean>
</beans>
class是類路徑
編寫測試代碼
public class TestSpring5 {
@Test
public void testAdd(){
//1 加載spring配置文件
ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/bean1.xml"); //方式1
//BeanFactory context = new FileSystemXmlApplicationContext("/src/main/java/bean1.xml");//方式2
//2 獲取配置創建的對象
User user = context.getBean("user",User.class);
System.out.println(user);
user.add();
}
}
此時,配置文件已經幫我們創建好了實例,我們直接從配置文件中獲取就行了。
Spring IOC容器
IOC底層原理
什么是IOC
Inversion Of Control(控制反轉)。把對象創建和對象之間的調用過程,交給Spring管理;使用IOC的目的是為了降低耦合度。
IOC底層主要用到三個技術:xml解析、工廠模式、反射。
-
工廠模式其實就把把對象的創建交給第三方,降低耦合度(完全沒有耦合度是不能的)
class UserService{ execute(){ UserDao dao = new UserFactory.getDao(); } } class UserDao{ ... } class UserFactory{ public static UserDao getDao(){ return new UserDao(); } }
-
反射就是java代碼可以動態獲取類的字節碼文件,從而可以得到類的屬性和方法等。
IOC過程
-
xml配置文件,配置創建的對象
-
有service類,dao類,創建工廠類
class UserFactory{ public static UserDao getDao(){ String classValue = class屬性值 //1 xml解析 Class class = Class.forName(classValue);//2 通過反射創建對象 return (UserDao) class.newInstance(); } }
IOC接口
-
IOC思想基於IOC容器完成,IOC容器底層就是對象工廠。
-
Spring提供IOC容器實現兩種方式:(兩個接口)
-
BeanFactory:IOC容器最基本實現,是Spring內部的使用接口,不提供開發人員進行使用
*加載配置文件的時候不會創建對象,在獲取對象(getBean)才去擦黃建對象
-
ApplicationContext:BeanFactory接口的子接口,提供更多更強大的功能,一般由開發人員使用
*加載配置文件配置文件的時候就會把配置文件對象進行創建
一般把耗時耗資源的過程交給服務器啟動的時候去完成,所以ApplicationContext更合適。
-
IOC操作Bean管理——XML方式
Bean管理是Spring的兩個操作:Spring創建對象;Spring注入屬性(不需要寫set方法,這里的屬性可以是基本類型,也可以是類對象)。
Bean管理操作有兩種方式:基於xml配置文件方式實現;基於注解方式實現。
基於xml方式
-
創建對象
<bean id="user" class="com.atguigu.spring5.User"></bean> <!-- ---------- bean標簽常用的屬性 *id:唯一標識 *class:類全路徑(包類路徑) *name:作用和id一樣,可以加特殊符號,目前已棄用 ---------- 創建對象時候,默認也是執行無參構造方法 ---------- -->
-
注入屬性—基本數據類型
DI:依賴注入,就是注入屬性。
<bean id="user" class="com.atguigu.spring5.User"> <!-- set方法注入屬性:使用property完成屬性注入 --> <property name="userName" value="xing"></property> <!-- 有參構造方法注入屬性 <constructor-arg name="userName" value="xing"></constructor-arg> --> </bean>
使用p名稱空間注入,可簡化基於xml配置方式
<!--xmlns:p="http://www.springframework.org/schema/p"--> <bean id="user" class="com.atguigu.spring5.User" p:userName="xing"></bean>
xml注入其他屬性
<!-- 設置空值 --> <property name="userName"> <null></null> </property> <!-- 特殊符號如:<<>> --> <property name="userName"> <value><![CDATA[<<xing>>]]></value> </property>
-
注入屬性—外部bean
創建一個service和dao對象,其中service里面有一個dao作為屬性
public class UserService { //創建UserDao類型屬性,生成set方法 private UserDao userDao; public void setUserDao(UserDao userDao) { this.userDao = userDao; } public void add(){ userDao.update(); } }
配置文件
<!-- service和dao對象的創建--> <bean id="userService" class="com.atguigu.spring5.service.UserService"> <!-- 注入userDao對象 name屬性值:類里面屬性名稱 ref屬性:創建userDao對象bean標簽id值 --> <!-- set 方法注入屬性--> <property name="userDao" ref="userDaoImpl"></property> </bean> <!-- userDao的創建class要指向實現類--> <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"></bean>
-
注入屬性—內部bean
內部bean可以理解在一個bean里面嵌套定義另外一個bean,實際應用中,更常用外部bean,因為更加清晰。
<bean id="userService" class="com.atguigu.spring5.service.UserService"> <property name="userDao"> <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"> </property> </bean>
-
注入屬性—級聯賦值
級聯賦值就是注入外部bean的時候,在外部bean里面賦值,比如:
<bean id="userService" class="com.atguigu.spring5.service.UserService"> <property name="userDao" ref="userDaoImpl"></property> </bean> <bean id="userDaoImpl" class="com.atguigu.spring5.dao.UserDaoImpl"> <property name="userDaoProperty" value="value"></property> </bean>
還有一種方式:這種方式要在userService中設置userDaoImpl的set方法,因為userDaoImpl.userDaoProperty要通過set方法來獲取
<bean id="userService" class="com.atguigu.spring5.service.UserService"> <property name="userDao" ref="userDaoImpl"></property> <property name="userDaoImpl.userDaoProperty" value="value2"></property> </bean>
-
注入屬性—集合屬性
public class Stu { private String[] courses; private List<String> list; private Map<String,String> map; //對象list private List<Course> courseList; public void setCourses(String[] courses) { this.courses = courses; } public void setList(List<String> list) { this.list = list; } public void setMap(Map<String, String> map) { this.map = map; } public void setCourseList(List<Course> courseList) { this.courseList = courseList; } }
<bean id="stu" class="com.atguigu.spring5.collection.Stu"> <!-- 數組屬性的注入 --> <property name="courses"> <array> <value>java課程</value> <value>數據庫標簽</value> </array> </property> <!-- list類型屬性注入 --> <property name="list"> <list> <value>張三</value> <value>李四</value> </list> </property> <!-- map類型屬性注入 --> <property name="map"> <map> <entry key="Java" value="java"></entry> </map> </property> <!-- 注入對象list類型 --> <property name="courseList"> <list> <ref bean="course1"></ref> <ref bean="course2"></ref> </list> </property> </bean> <bean id="course1" class="com.atguigu.spring5.collection.Course"> <property name="cname" value="Spring5框架"></property> </bean> <bean id="course2" class="com.atguigu.spring5.collection.Course"> <property name="cname" value="Mybatis框架"></property> </bean>
還可以把集合注入部分提取出來,方法是在spring配置文件中引入新的名稱空間比如util,模版如下:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/utils/spring-utils.xsd"> <!-- 提取list集合類型屬性注入 --> <util:list id="bookList"> <value>java</value> <value>c++</value> </util:list> <bean id="book" class="com.atguigu.spring5.collection.Book"> <property name="list" ref="bookList"></property> </bean> </beans>
FactoryBean
Spring有兩種類型的bean,一種普通bean,另一種工廠bean(FactoryBean)。
普通bean在Spring配置文件中定義什么類型,返回就是什么類型;而工廠bean定義的類型和返回的類型可以不一致。
做法:
-
創建類,讓這個類作為工廠bean,實現接口FactoryBean
-
實現接口里面的方法,在實現的方法中定義返回的bean類型
public class MyBean implements FactoryBean<Course> { //定義返回bean public Course getObject() throws Exception { Course course = new Course(); course.setCname("java"); return course; } public Class<?> getObjectType() { return null; } }
<bean id="myBean" class="com.atguigu.spring5.factorybean.MyBean"></bean>
public class TestSpring5 { ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/bean1.xml"); Course course = context.getBean("myBean",Course.class); }
Bean的作用域
bean的作用域是指:在Spring里面,設置創建bean實例是單實例還是多實例(每次getBean都獲取新對象),默認情況下是單實例對象。
在bean標簽里面scope
屬性用來設置單實例還是多實例,默認值singleton
,prototype
表示多實例。
兩個值的區別:singleton時候,加載spring配置文件時候就會創建單實例對象,而prototype在調用getBean方法時候才會去創建。
Bean的生命周期
- 通過構造器創建bean實例(無參數構造)
- 為bean的屬性設置值和對其他bean引用(調用set方法)
- *把bean實例傳遞bean后置處理器(實現BeanPostProcessor的類對象)的方法
- 調用bean的初始化方法(需要配置初始化的方法
bean標簽的init-method屬性
) - *把bean實例傳遞bean后置處理器的方法
- bean可以使用了(對象獲取到了)
- 當容器關閉的時候(context.close()),調用bean的銷毀方法(需要配置銷毀的方法
bean標簽的destroy-method屬性
)
xml自動裝配
根據指定裝配規則(屬性名稱或者屬性類型),Spring自動將匹配的屬性值進行注入
<!--
bean標簽屬性autowire,配置自動裝配
autowire屬性常用兩個值:
byName:根據屬性名稱注入,注入bean的id值和類屬性名稱一樣
byType:根據屬性類型注入,相同類型的bean不能有多個
-->
<bean id="emp" class="com.atguigu.spring5.autowire.Emp" autowire="byName"></bean>
<bean id="dept" class="com.atguigu.spring5.autowire.Dept"></bean>
基於xml的自動裝配很少用到,基本都是使用注解
引入外部屬性文件
以配置數據庫連接池為例:
-
創建外部屬性文件.properties格式文件,寫數據庫信息
prop.driverClass=com.mysql.jdbc.Driver prop.url=jdbc:mysql://localhost:3306/userDb prop.userName=root prop.passWord=root #等號左邊可以隨便寫,但建議不要寫某一個單詞,容易沖突
-
把外部properties屬性文件引入到spring配置文件中
首先引入context名稱空間,先后在spring配置文件中使用標簽引入外部屬性文件
<!-- 引入外部屬性文件--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置連接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${prop.driverClass}"></property> ... </bean>
IOC操作Bean管理——注解方式
注解可以作用在類、屬性、方法上面,可以簡化我們的xml配置。
Spring針對Bean管理中創建對象提供的注解四種,他們都作用在類上面:
- @Component
- @Service
- @Controller
- @Repository
*** 這四個注解功能是一樣的,都可以用來創建bean實例,只不過在開發中為了清晰的表明這個bean用在哪一個層上面**
使用注解創建對象
-
引入AOP依賴
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.2.8.RELEASE</version> </dependency>
-
開啟組件掃描
指定要掃描的包,這個包中的類有注解就回去創建對象。
首先引入context名稱空間
<!--開啟組件掃描 1 如果掃描多個包,多個包使用逗號隔開 2 也可以掃描包的上層目錄(推薦) --> <context:component-scan base-package="com.atguigu.spring5"></context:component-scan>
-
創建類,在類上面添加創建對象注解即可
//value屬性可以省略不寫,默認值是類名稱首字母小寫 @Component(value = "userService") //<bean id="userService" class="..."></bean> public class UserService{ //... }
開啟組件掃描細節配置
這個表示只掃描包里面帶Controller注解的類
<!--示例1
use-default-filters="false" 表示現在不使用默認的filter(所有類都掃描),自己配置filter
context:include-filter,設置掃描哪些內容
-->
<context:component-scan base-package="com.atguigu.spring5" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--示例2
context:exclude-filters:設置哪些內容不進行掃描
-->
<context:component-scan base-package="com.atguigu.spring5">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
基於注解方式實現屬性注入
-
@AutoWired:根據屬性類型進行自動裝配(byType),不需要添加set方法,如果接口有多個實現類的話,@AutoWired就不知道找哪個了,這個時候就要配合@Qualifier根據名稱進行自動裝配了
-
@Qualifier:根據屬性名稱進行自動裝配(byName),
要和@AutoWired一起使用
@AutoWired @Qualifier(value = "userDaoImpl") private UserDao userDao;
-
@Resource:可以根據類型注入,也可以根據名稱注入。這個注解在javax.annotation.Resource包中,不是Spring本身提供的,因此官方建議使用前兩者。
-
@Value:注入普通類型屬性
@Value(value="abc") private String name;
完全注解開發
-
創建配置類,替代xml配置文件
@Configuration //Spring才能識別為配置類,替代xml文件 @ComponentScan(basePackages = {"com.atguigu.spring5"}) public class SpringConfig { }
-
加載配置類
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
Spring AOP
概念
AOP:Aspect Oriented Programming,面向切面編程。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
通俗點說就是:在不修改源代碼的情況下增強類的功能。
底層原理
AOP底層使用動態代理。有兩種情況動態代理:
- 有接口情況:使用JDK動態代理,創建接口實現類代理對象來增強接口功能
- 和沒有接口情況:使用CGLIB動態代理,創建當前類子類的代理對象來增強類功能
使用JDK動態代理:
-
使用java.lang.reflect.Proxy類里面的newProxyInstance方法創建代理對象,首先我們創建接口以及實現類
static Object newProxyInstance(ClassLoader loader,class<?>[] interfaces,InvocationHandler h); //返回指定接口的代理類實現,該接口將方法調用分派給指定的調用處理程序 //第一個參數:類加載器 //第二個參數:增強類所實現的接口,可以有多個 //第三個參數:實現InvocationHandler接口的一個對象,這個對象里面通過實現invoke()方法來寫增強的邏輯
public interface UserDao { public int add(int a,int b); public String update(String id); } //------- public class UserDaoImpl implements UserDao{ public int add(int a, int b) { System.out.println("add方法執行了。。。"); return a+b; } public String update(String id) { System.out.println("update方法執行了。。。"); return id; } }
-
使用Proxy類創建接口實現類代理對象
public class JDKProxy { public static void main(String[] args) { //創建接口實現類代理對象 Class[] interfaces = {UserDao.class}; // Proxy.newProxyInstance(JDKProxy.class.getClassLoader(), interfaces, new InvocationHandler() { // public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // return null; // } // }); UserDaoImpl userDao = new UserDaoImpl(); UserDao dao = (UserDao)Proxy.newProxyInstance(JDKProxy.class.getClassLoader(),interfaces,new UserDaoProxy(userDao)); int result = dao.add(1,2); System.out.println("result:"+result); } } class UserDaoProxy implements InvocationHandler{ //1 創建的是誰的代理對象,就把誰傳進來 //有參構造傳遞 private Object obj; public UserDaoProxy(Object obj){ this.obj = obj; } //寫增強邏輯 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //方法之前 System.out.println("方法之前執行。。。"+method.getName()+"傳遞的參數:"+ Arrays.toString(args)); //被增強的方法執行 //這里也可以判斷執行的方法去做不同的處理 Object res = method.invoke(obj,args); //方法執行之后 System.out.println("方法之后執行..."+obj); return null; } }
AOP術語
- 連接點:在一個類中,哪些方法可以被增強,這些方法稱為連接點
- 切入點 :實際被真正增強的方法
- 通知(增強):實際增強的邏輯部分。通知有多種類型:前置通知、后置通知、環繞通知、異常通知、最終通知
- 切面:把通知應用到切入點的過程,指的是一個動作
Spring框架中一般都是基於AspectJ實現AOP操作,需要引入AOP相關依賴
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!--AspectJ 開始-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjtools</artifactId>
<version>1.9.5</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.0</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
<!--AspectJ 結束-->
AspectJ不是Spring的組成部分,獨立AOP框架,一般把AspectJ和Spring框架一起使用,進行AOP操作
AOP操作
切入點表達式作用:知道對哪個類里面的哪個方法進行增強
語法結構
execution([權限修飾符][返回類型][類全路徑][方法名稱][參數列表])
-
對com.atguigu.dao.BookDao類里面的add方法進行增強
execution(* com.atuguigu.dao.BookDao.add(..))
*表示所有修飾符,返回類型可以省略
-
對com.atguigu.dao.BookDao類里面的所有方法增強
execution(* com.atuguigu.dao.BookDao.*(..))
-
對com.atguigu.dao包里面所有類,類里面的所有方法進行增強
execution(* com.atuguigu.dao.*.*(..))
AspectJ注解
-
創建類,在類里面定義方法
//被增強類 @Component public class User { public void add(){ System.out.println("add...."); } }
-
創建增強類(編寫增強邏輯)
在增強類里面創建方法,讓不同的的方法代表不同的通知類型
//增強類 @Component @Aspect //生成代理對象 public class UserProxy { //Before注解表示作為前置通知 @Before(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void before(){ System.out.println("before...."); } @After(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void after(){ System.out.println("after..."); } //方法返回后執行,比after早,有異常不執行 @AfterReturning(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterReturning(){ System.out.println("afterReturning..."); } //有異常執行 @AfterThrowing(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void afterThrowing(){ System.out.println("afterThrowing..."); } //環繞通知:在方法之前和之后都通知 @Around(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("環繞之前..."); //被增強的方法執行 proceedingJoinPoint.proceed(); System.out.println("環繞之后..."); } }
-
進行通知配置
在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" xmlns:context="http://www.springframework.org/schema/context" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--引入context/aop名稱空間--> <!--開啟注解掃描--> <context:component-scan base-package="com.atguigu.spring5"></context:component-scan> <!--開啟AspectJ生成代理對象--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
-
配置不同類型的通知
在增強類里面,在作為通知方法上面添加通知類型注解,使用切入點表達式配置。
-
測試
@Test public void testAopAnno(){ ApplicationContext context = new FileSystemXmlApplicationContext("/src/main/java/bean1.xml"); User user = context.getBean("user", User.class); user.add(); } /* output: 環繞之前... before.... add.... afterReturning... after... 環繞之后... */
-
相同的切入點抽取
//相同切入點抽取 @Pointcut(value = "execution(* com.atguigu.spring5.aopanno.User.add(..))") public void pointdemo(){ } //Before注解表示作為前置通知 @Before(value = "pointdemo()") public void before(){ System.out.println("before...."); }
-
有多個增強類對同一個方法進行增強,設置增強類的優先級。
在增強類上面添加注解@Order(數字)
,數字值越小優先級越高
AspectJ配置文件
AspectJ也可以通過配置文件使用
JdbcTemplate
概念
Spring框架對JDBC進行封裝,使用JdbcTemplate可以很方便的實現對數據庫的操作。
准備工作
-
引入依賴
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.23</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.2.6.RELEASE</version> </dependency>
-
在spring配置文件配置數據庫連接池
-
配置JdbcTemplate對象,注入DataSource
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <!-- 注入dataSource --> <property name="dataSource" ref="dataSource"></property> </bean>
-
創建dao類,注入jdbcTemplate對象;創建service類,注入dao對象
CRUD操作
-
增刪改操作使用的是
update(String sql, Object... args)
函數@Repository public class BookDaoImpl implements BookDao{ //注入jdbcTemplate private JdbcTemplate jdbcTemplate; //添加方法 @Override public void add(Book book){ //1 創建sql語句 String sql = "insert into t_book values(?,?,?)"; //2 調用方法實現 Object[] args = {book.getUserId(),book.getUsername(),book.getUstatus}; int update = jdbcTemplate.update(sql,args); } }
-
查詢返回某一個值
查詢操作使用的是
queryForObject(String sql, Class<T> requiredType)
方法,第二個參數表示查詢操作的返回類型。String sql = "select count(*) from t_book"; Integer count = jdbcTemplate.queryForObject(sql,Integer.class);
-
查詢返回對象
queryForObject(String sql,RowMapper<T> rowMapper,Object... args)
query(String sql,RowMapper<T> rowMapper,Object... args)
:返回對象列表第二個參數rowMapper是接口,針對返回不同類型數據,使用這個接口里面實現類完成數據封裝
String sql = "select * from t_book where user_id=?"; //調用方法 Book book = jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Book>(Book.class),id);
-
批量操作
batchUpdate(String sql,List<Object[]> batchArgs)
public void batchAdd(List<Object[]> batchArgs){ String sql = "insert into t_book values(?,?,?)"; int[] ints = jdbcTemplate.batchUpdate(sql,batchArgs); }
Spring聲明式事務
Sping事務管理介紹
-
事務添加到JavaEE三層結構里面Service層(業務邏輯層)
-
在Spring進行事務管理操作有兩種方式:編程式和聲明式
編程式
public void accountMoney(){ try{ //1 開啟事務 //2 進行業務操作 userDao.reduceMoney(); userDao.addMoney(); //3 沒有發生異常,提交事務 }catch(Exception e){ //4 出現異常 事務回滾 } }
-
聲明式事務管理有基於注解方式,也有基於xml配置文件方式
-
在Spring進行聲明式事務管理,底層使用AOP原理
-
Spring事務管理API提供一個接口,代表事務管理器,這個接口針對不同的框架提供不同的實現類
注解方式
-
在配置文件中配置事務管理器,並開啟事務注解
<!-- 創建事務管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 注入數據源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 開啟事務注解 引入名稱空間tx--> <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
在service類上面添加事務注解@Transactional
這個注解可以添加到類上面,也可以添加上方法上面,如果添加上類上面表示這個類里面所有的方法都添加事務。
-
這個注解里面可以配置事務相關的參數
propagation:事務傳播行為。多事務方法(對數據庫表中的數據發生變化的操作)進行互相調用,這個過程中事務是如何進行管理的。Spring定義了7種傳播行為
isolation:事務的隔離級別,默認是可重復讀
timeout:超時時間。事務需要在一定時間內進行提交,如果不提交進行回滾。默認值-1(沒有超時),單位秒
readOnly:是否只讀。默認false,true表示只能做查詢操作
rolllbackFor:回滾。設置出現哪些異常進行回滾
noRollbackFor:不回滾。設置出現哪些異常不進行回滾
xml方式
- 配置事務管理器
- 配置通知
- 配置切入點和切面
<!--配置通知-->
<tx:advice id="txadvice">
<!--配置事務參數-->
<tx:attributes>
<!--指定哪種規則的方法上面添加事務-->
<tx:method name="accountMoney" propagation="REQUIRED"></tx:method>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 配置切入-->
<aop:pointcut id="pt" expression="execution(* com.atguigu.spring5.service.UserService.*(..))"/>
<!-- 配置切面-->
<aop:advisor advice-ref="txadvice" pointcut-ref="pt"/>
</aop:config>
完全注解開發
-
創建配置類替代xml配置文件
@Configuration @ComponentScan(basePackages="com.atguigu") @EnableTransactionManagement //開啟事務 public class TxConfig{ //創建數據庫連接池 @Bean public DruidDataSource getDruidDataSource(){ DruidDataSource dataSource = new DruidDataSource(); dataSource.SetDriverClassName(""); dataSource.SetUrl(""); dataSource.SetUsername(""); dataSource.SetPassword(""); return dataSource; } //創建JdbcTemplate對象 @Bean public JdbcTemplate getJdbcTemplate(DataSource dataSource){ //到ioc容器中根據類型找到dataSource JdbcTemplate jdbcTemplate = new JdbcTemplate(); //注入dataSource jdbcTemplate.setDataSource(dataSource); return jdbcemplate; } //創建事務管理器 @Bean public DataSourceTransactionManager getDataSourceTransationManager(DataSource dataSource){ DataSourceTransactionManager transactionManager = new DataSourceTransationManager(); transactionManager.setDataSource(dataSource); return transactionManager; } }
Spring5新功能
整個Spring5框架的代碼基於Java8,運行時兼容JDK9,許多不建議使用的類和方法在代碼庫中刪除。
整合日志框架
Spring5自帶類通用的日志封裝,已經移除了Log4jConfigListener,官方建議使用Log4j2。
Nullable注解和函數式注冊對象
@Nullable注解可以使用在方法上面,屬性上面,參數上面,表示方法返回可以為空,屬性值可以為空,參數值可以為空。
Spring5核心容器支持函數時風格GenericApplicationContext,通過lambda表達式注冊對象
public void test(){
//1 創建GenericApplicationContext對象
GenericApplicationConntext context = new GenericApplicationContext();
//2 調用context的方法對象注冊
context.refresh();
context.registerBean(User.class, ()-> new User());
//context.registerBean("user1",User.class,()->new User());
//3 獲取在spring注冊的對象
User user = context.getBean("com.atguigu.spring5.test.User");
//User user = context.getBean("user1");
}
支持整合JUnit5
SpringWebFlux
簡介
WebFlux是Spring5添加新的模塊,用於web開發,功能和SpringMVC類似,WebFlux使用當前一種比較流行的響應式出現的框架。
傳統的web框架比如SpringMVC,這些基於Servlet容器,WebFlux是一種異步非阻塞的框架,異步非阻塞的框架在Servlet3.1以后才支持,核心是基於Reactor的相關API實現。
同步異步針對調用者:調用者發送請求,如果等着對方回應之后才去做其他事情就是同步,如果發送請求之后不等着對方回應就去做其他事情就是異步
阻塞和非阻塞針對被調用者:被調用者收到請求之后,做完請求任務之后才給出反饋就是阻塞,收到請求之后馬上給出反饋然后再去做其他事情就是非阻塞
WebFlux特點:
- 非阻塞:在有限資源下,提高系統的吞吐量和伸縮性,以Reactor為基礎實現響應式編程
- 函數式編程:Spring5框架基於java8,WebFlux使用java8函數式編程方式實現路由請求
WebFlux VS SpringMVC
- 都可以使用注解方式,都運行在Tomcat等容器中
- SpringMVC采用命令式編程,WebFlux采用異步響應式編程
響應式編程
響應式編程是一種面向數據流和變化傳播的編程范式,這意味着可以在編程語言中很方便的表達靜態或動態的數據流,而相關的計算模型會自動將變化的值通過數據流進行傳播。
響應式編程使用觀察者模式,在java8及之前的版本提供的觀察者模式兩個類Observer和Observerable。
在java8之后被Flow類取代
Reactor實現
響應式編程操作中,Reactor是滿足Reactive規范框架,其有兩個核心類Mono和Flux,這兩個類實現接口Publisher,提供豐富操作符。Flux對象實現發布者,返回N個元素;Mono實現發布者返回0或1個元素。
Flux和Mono都是數據流的發布者,使用Flux和Mono都可以發出三種數據信號:元素值、錯誤信號、完成信號。后兩個代表終止信號,用於告訴訂閱者數據流結束了,錯誤信號在終止數據流的同時把錯誤信息傳遞給訂閱者。
-
引入依賴
<dependency> <groupId>io.projectreactor</groupId> <artifactId>reactor-core</artifactId> <version>3.3.5.RELEASE</version> </dependency>
-
public static void main(String[] args) { //just方法直接聲明 Flux.just(1,2,3,4); Mono.just(1); //其他方法 Integer[] array = {1,2,3,4}; Flux.fromArray(array); List<Integer> list = Arrays.asList(array); Flux.fromIterable(list); }
-
錯誤信號和完成信號都是終止,不能共存;如果沒有發送任何元素值,而是直接發送錯誤或完成信號表示空數據流;如果沒有錯誤信號,沒有完成信號,表示無限數據流。
-
調用just或者其他方法只是聲明數據流,數據流並沒有發出,只有進行訂閱之后才會觸發數據流。
-
對數據流進行一道道操作,成為操作符,比如工廠流水線
map:元素映射為新元素
flatMap:把每個元素轉換成流,把轉換之后的多個流合並成大的流
WebFlux執行流程和核心API
WebFlux基於Reactor,默認容器是Netty,Netty是高性能的NIO框架,異步非阻塞的框架。
SpringWebFlux執行過程和SpringMVC相似:
- SpringWebFlux核心控制器DispatchHandler,負責請求的處理,實現WebHandler接口
- HandlerMapping:請求查詢到處理的方法
- HandlerAdapter:真正負責請求處理
- HandlerResultHandler:響應結果處理
SpringWebFlux實現函數式編程的兩個接口:RouterFunction(路由處理)和HandlerFunction(處理函數)
基於注解編程模型
使用注解編程模型方式,和之前SpringMVC使用相似,只需要把相關依賴配置到項目中,SpringBoot自動配置相關運行容器,默認情況下使用Netty服務器。
-
引入webflux相關依賴,創建一個SpringBoot工程
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-webflux</artifactId> <version>2.3.5.RELEASE</version> </dependency>
-
創建實體類User,以及service類
@Service public class UserServiceImpl implements UserService { //創建map集合存儲數據 private final Map<Integer,User> users = new HashMap<Integer, User>(); public UserServiceImpl(){ this.users.put(1,new User("lucy","boy",20)); this.users.put(2,new User("mary","girl",20)); this.users.put(3,new User("jack","boy",20)); } public Mono<User> getUserById(int id) { return Mono.justOrEmpty(this.users.get(id)); } public Flux<User> getAllUser() { return Flux.fromIterable(this.users.values()); } public Mono<Void> saveUserInfo(Mono<User> userMono) { return userMono.doOnNext(person ->{ //向map集合里面放值 int id = users.size(); users.put(id,person); }).thenEmpty(Mono.empty());//Mone.empty()表示數據流結束 } }
-
創建controller
@RestController public class UserController { @Autowired private UserService userService; @GetMapping("/user/{id}") public Mono<User> getUserById(@PathVariable int id){ return userService.getUserById(id); } @GetMapping("/user") public Flux<User> getUsers(){ return userService.getAllUser(); } @PostMapping("/saveuser") public Mono<Void> saveUser(@RequestBody User user){ Mono<User> userMono = Mono.just(user); return userService.saveUserInfo(userMono); } }
雖然形式上和SpringMVC方式差不多,但是底層不一樣了。前者基於SpringMVC+Servelet+Tomcat,后者基於SpringWebFlux+Reactor+Netty。
基於函數式編程模型
在使用函數式編程模型操作時,需要自己初始化服務器。
基於函數式編程模型的時候,有兩個核心接口:RouterFunction(實現路由功能,請求轉發給對應的Handler)和HandlerFunction(處理請求生成響應的函數)。核心任務定義兩個函數式接口的實現並且啟動需要的服務器。
SpringWebFlux請求和響應不再是ServletRequest和ServeltResponse,而是ServerRequest和ServerRespone。
-
創建Handler
public class UserHandler { private final UserService userService; public UserHandler(UserService userService){ this.userService = userService; } public Mono<ServerResponse> getUserById(ServerRequest serverRequest){ int userID = Integer.valueOf(serverRequest.pathVariable("id")); //空值處理 Mono<ServerResponse> notFount = ServerResponse.notFound().build(); Mono<User> userMono = this.userService.getUserById(userID); //把userMono進行轉換返回 //使用Reactor操作符flatMap return userMono.flatMap(person -> ServerResponse.ok().contentType(MediaType.APPLICATION_JSON) .body(fromObject(person))) .switchIfEmpty(notFount); } public Mono<ServerResponse> getAllUsers(ServerRequest serverRequest){ //調用service得到結果 Flux<User> users = this.userService.getAllUser(); return ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(users,User.class); } public Mono<ServerResponse> saveUser(ServerRequest serverRequest){ //得到user對象 Mono<User> userMono = serverRequest.bodyToMono(User.class); return ServerResponse.ok().build(this.userService.saveUserInfo(userMono)); } }
-
初始化服務器,編寫Router和adapter
public class Server { public static void main(String[] args) throws Exception { Server server = new Server(); server.createReactorService(); System.out.println("enter to exit"); System.in.read(); } //1 創建Router路由 public RouterFunction<ServerResponse> routingFunction(){ UserService userService = new UserServiceImpl(); UserHandler userHandler = new UserHandler(userService); return RouterFunctions.route( GET("/users/{id}").and(accept(MediaType.APPLICATION_JSON)),userHandler::getUserById ).andRoute( GET("/users").and(accept(MediaType.APPLICATION_JSON)),userHandler::getAllUsers ); } // 2 創建服務器完成適配 public void createReactorService(){ //路由和Handler適配 RouterFunction<ServerResponse> route = routingFunction(); HttpHandler httpHandler = toHttpHandler(route); ReactorHttpHandlerAdapter adapter = new ReactorHttpHandlerAdapter(httpHandler); //創建服務器 HttpServer httpServer = HttpServer.create(); httpServer.handle(adapter).bindNow(); } }