一、Spring框架的概述
1、Spring是輕量級的開源的JavaEE框架
2、Spring可以解決企業應用開發的復雜性
3、Spring有兩個核心的部分:IOC(控制反轉)和AOP(面向切面編程)
4、Spring特點
(1)方便解耦,簡化開發
(2)Aop編程支持
(3)方便程序的測試
(4)方便集成各種優秀框架
(5)方便進行事務的操作
(6)降低API的開發難度
二、Spring配置小案例
1.配置Spring的配置文件以及加載
//1.加載配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("myBean.xml");
//2.獲取配置文件創建的對象
User user = context.getBean("user1", User.class);
System.out.println(user);
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user1" class="com.cuit.bean.User">
<property name="name" value="張三"></property>
<property name="sex" value="男"></property>
</bean>
</beans>
三、IOC
1、IOC(概念和原理)
- IOC容器底層就是對象工廠
- 控制反轉,即將對象的創建和對象之間的調用過程交給Spring容器來管理
- 使用IOC的目的是為了解耦,降低耦合度
- IOC的底層是xml解析、工廠模式、反射
2、IOC底層有兩種接口
BeanFactory:加載配置文件的時候不創建對象,調用對象時才創建對象
ApplicationContext:加載配置文件的時候創建好對象,可以為Bean配置lazy-init=true來讓Bean延遲實例化
3、IOC操作Bean管理
Spring創建對象、Spring注入屬性
Spring中有兩種類型的bean
普通bean:配置文件中定義bean類型就是返回類型
工程bean:在配置文件定義bean類型可以和返回類型不一樣
3.1、基於xml方式創建對象
創建對象時,默認使用無參構造方式創建對象
id:對於創建對象的唯一標識
class:類的全路徑(包類路徑),即返回類型
<bean id="user1" class="com.cuit.bean.User">
3.2、基於xml方式設置屬性(DI依賴注入)
DI:依賴注入,就是注入屬性
3.2.1、構造器方式注入
在創建的類中,添加上有參構造器
public class Book {
private String bname;
private String bautohr;
public Book(String bname, String bautohr) {
this.bname = bname;
this.bautohr = bautohr;
}
public Book() {
}
}
xml文件
constructor-arg標簽用於設置構造器參數屬性值
<bean id="book" class="com.cuit.bean.Book" >
<constructor-arg name="bautohr" value="達摩祖師"></constructor-arg>
<constructor-arg name="bname" value="易筋經"></constructor-arg>
</bean>
3.2.2、set方式進行注入
在創建的類中設置屬性,並且帶有set方法
package com.cuit.bean;
public class User {
private String name;
private int age;
private String sex;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setSex(String sex) {
this.sex = sex;
}
}
xml文件
<bean id="user" class="com.cuit.bean.User">
<property name="name" value="張三"></property>
<property name="age" value="22"></property>
<property name="sex" value="男"></property>
</bean>
設置一些特殊值空值
<bean id="user" class="com.cuit.bean.User">
<property name="name" value="張三"></property>
<property name="age" value="22"></property>
<!--設置空值-->
<property name="sex">
<null></null>
</property>
<property name="sex">
<!--設置一些特殊值,比如<<南京>> idea中輸入cd后有提示-->
<value> <![CDATA[
<<南京>>
]]></value>
</property>
</bean>
注入其他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"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="user" class="com.cuit.bean.User">
<property name="name" value="張三"></property>
<property name="age" value="22"></property>
<!--外部bean注入屬性-->
<property name="book" ref="book"></property>
<property name="sex" value="男"></property>
</bean>
<bean id="book" class="com.cuit.bean.Book">
<constructor-arg name="bautohr" value="達摩祖師"></constructor-arg>
<constructor-arg name="bname" value="易筋經"></constructor-arg>
</bean>
<!--直接使用p命名空間注入屬性-->
<bean id="user1" class="com.cuit.bean.User" p:name="李四" p:age="21" p:sex="男">
</bean>
<bean name="emp" class="com.cuit.bean.Emp">
<property name="ename" value="王五"/>
<property name="gender" value="女"/>
<!--內部bean-->
<property name="dept">
<bean id="dept" class="com.cuit.bean.Dept">
<property name="dname" value="研發部"/>
</bean>
</property>
</bean>
<bean name="emp1" class="com.cuit.bean.Emp">
<property name="ename" value="王五"/>
<property name="gender" value="女"/>
<!--級聯賦值-->
<property name="dept" ref="dept1"></property>
<property name="dept.dname" value="技術部"></property>
</bean>
<bean id="dept1" class="com.cuit.bean.Dept">
<property name="dname" value="研發部"/>
</bean>
</beans>
注入集合類型屬性
<?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:p="http://www.springframework.org/schema/p"
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/beans/spring-util.xsd">
<util:list id="list">
<value>易筋經</value>
<value>九陰真經</value>
<value>九陽神功</value>
</util:list>
<bean id="stu" class="com.cuit.bean.Stu">
<property name="course">
<!--數組的注入方式-->>
<array>
<value>數學</value>
<value>英語</value>
<value>語文</value>
</array>
</property>
<property name="list">
<list>
<value>list1</value>
<value>list2</value>
<value>list3</value>
<!--可以使用<ref bean="容器中的bean的id或者name,但對象必須與list申請時對象相對應的"-->
</list>
</property>
<property name="maps">
<map>
<entry key="key1" value="val1"></entry>
<entry key="key2" value="val2"></entry>
<entry key="key2" value="val3"></entry>
<!--可以使用<ref bean="容器中的bean的id或者name,但對象必須與map申請時對象相對應的"-->
</map>
<!--
<props>
<prop key="key1">value1</prop>
</props>
-->
</property>
<property name="sets">
<set>
<value>cf</value>
<value>dnf</value>
<value>lol</value>
<!--可以使用<ref bean="容器中的bean的id或者name,但對象必須與set申請時對象相對應的"-->
</set>
</property>
</bean>
</beans>
set:用戶屬性set的情況,無重復,有個ref標簽,通過引入bean的id,引入對象
list:類似於set但是值時可以重復的,也有個ref標簽,通過引入bean的id,引入對象
map:類死props,鍵和值不一定要用String,專用於map,有個value-ref,對應於bean的id,引入對象
的
3.2.3、其他注入方式
需要引入第三方依賴
(1)、p名稱空間注入,可以簡化基於xml方式注入
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--直接使用p命名空間注入屬性-->
<bean id="user" class="com.cuit.bean.User" p:name="李四" p:age="21" p:sex="男">
</bean>
</beans>
(2)、
3.2.4、bean的作用域
在Spring里面,可以設置創建bean實例是單實例還是多實例,默認單實例對象
通過scope標簽缺省的情況默認為singleton為單實例,prototype為多實例
singleton單實例模式
無論調用多少次getBean()方法,只會在堆空間中生成一個對象
public void testStu(){
ApplicationContext context = new ClassPathXmlApplicationContext("myBean2.xml");
//User user = (User)context.getBean("user");
/*知道輸出類型之后就不需要每一次get之后都去強轉類型了*/
Stu stu1 = context.getBean("stu", Stu.class);
Stu stu2 = context.getBean("stu", Stu.class);
System.out.println(stu1);
System.out.println(stu2);
System.out.println(stu1 == stu2);
}
<util:list id="list">
<value type="java.lang.String">易筋經</value>
<value type="java.lang.String">九陰真經</value>
<value type="java.lang.String">九陽神功</value>
</util:list>
<bean id="stu" class="com.cuit.bean.Stu" scope="singleton">
<property name="lists" ref="list"/>
</bean>
輸出
com.cuit.bean.Stu@38467116
com.cuit.bean.Stu@38467116
true
單實例模式特點
1、把bean放在IOC容器中統一進行管理,只在初始化加載的時候實例化一次,一方面提高了效率,另一方面大大降低了內存開銷(針對創建對象的次數)
2、不同的請求可以同時拿這個實例來用,執行實例中不同的方法時,可以同時進行,但是不可以同時執行該實例的同一個方法。其實就是單例模式的線程安全問題,
3、實例里面不可以有成員變量,因為成員變量共享的話,一個對它修改,另一個再拿來用,用的是修改之后的,會不安全。
4、只有一個共享的實例存在,所有對這個bean的請求都會返回這個唯一的實例。
prototype(多實例模式)
<bean id="stu" class="com.cuit.bean.Stu" scope="prototype">
<property name="lists" ref="list"/>
</bean>
輸出
com.cuit.bean.Stu@5b7a7f33
com.cuit.bean.Stu@790da477
false
request、ssession、application這些只會在web開發中使用
request作用域
對應一個http請求和生命周期,當http請求調用作用域為request的bean的時候,Spring便會創建一個新的bean,在請求處理完成之后便及時銷毀這個bean。
session作用域
Session中所有http請求共享同一個請求的bean實例。Session結束后就銷毀bean。
globalSession作用域
與session大體相同,但僅在portlet應用中使用。
3.2.5、bean生命周期
(一)、不加后置處理器
一共五步
第一步、通過無參構造器創建bean的實例對象
第二步、對bean的set()方法進行屬性賦值以及對其他bean引用
第三步、調用bean的初始化方法(通過init-method標簽配置)
第四步、bean可以使用了
第五步、當容器關閉時,調用bean的銷毀方法(通過destroy-method標簽配置)
(二)、加上后置處理器
需要創建一個類實現BeanPostProcessor接口,並且需要在xml文件中添加一個實現BeanPostProcessor接口的類的bean,相當於給所有的bean添加上了后置處理器
一共七步
第一步、通過無參構造器創建bean的實例對象
第二步、對bean的set()方法進行屬性賦值以及對其他bean引用
第三步、調用后置處理器中的postProcessBeforeInitialization
方法
第四步、調用bean的初始化方法(通過init-method標簽配置)
第五步、調用后置處理器中的postProcessAfterInitialization
方法
第六步、bean可以使用了
第七步、當容器關閉時,調用bean的銷毀方法(通過destroy-method標簽配置)
public class MyBeanPost implements BeanPostProcessor{
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之前執行的方法");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("在初始化之后執行的方法");
return bean;
}
}
<bean id="myBeanPost" class="com.atguigu.spring5.bean.MyBeanPost"></bean>
3.2.6、bean的自動裝配
根據指定裝配規則(屬性名稱或者屬性類型),Spring自動將匹配的屬性值進行注入
bean標簽autowire屬性設置自動裝配,有兩個常用的屬性值
byName:根據屬性名稱注入,要求注入值bean的id和屬性名稱一致
byType:根據屬性類型注入
<bean id="emp" class="com.cuit.autowire.Emp" autowire="byName"></bean>
<!--<bean id="emp" class="com.cuit.autowire.Emp" autowire="byName"></bean>-->
<bean id="dept" class="com.cuit.autowire.Dept"></bean>
3.3、基於注解的方式創建對象
(1)注解是代碼特殊標記,格式:@注解名稱(屬性名稱=屬性值, 屬性名稱=屬性值..) (2)使用注解,注解作用在類上面,方法上面,屬性上面 (3)使用注解目的:簡化 xml 配置
Spring針對bean管理創建對象提供注解
(1)@Component
(2)@Service 一般用於業務邏輯層
(3)@Controller 一般用於web層
(4)@Repository 一般用於dao層
上面四個注解功能是一樣的,都可以用來創建 bean 實例
第一步、引入Aop的依賴包
第二步、使用注解要開啟組件掃描,前提引入context名稱空間
<!--指定要掃描的包,包下面的注解就會生效-->
<context:component-scan base-package="com.cuit.testdemo"/>
第三步、在類上方使用上面任一注解都可以創建對應的bean
@Service("userService")
public class UserService {
public void add() {
System.out.println("Service add.........");
}
}
注解創建對象小結:首先加載,當讀取到xml文件中開啟組件掃描,去對應的包中尋找帶有注解的類,並且創建對應的bean實例
3.4、基於注解的方式屬性注入
@Autowired 根據屬性類型進行自動裝配
@Qualifier 根據屬性的名稱進行注入,配合@Autowired一起使用
@Resource 可以根據名稱和類型進行注入 此注解是jdk自帶的,位於javax.annotation.Resource,jdk11已經被移除了
@Value 針對普通數據類型
第一步 把 service 和 dao 對象創建,在 service 和 dao 類添加創建對象注解
第二步 在 service 注入 dao 對象,在 service 類添加 dao 類型屬性,在屬性上面使用注解
@@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao {
@Override
public void add() {
System.out.println("userDao add ..........");
}
}
@Service
public class UserService {
@Autowired
@Qualifier(value = "userDaoImpl")
private UserDao userDao;
public void add() {
System.out.println("Service add.........");
userDao.add();
}
}
3.5、完全注解開發
(1)創建一個配置類,代替xml配置文件
@Configuration //作為配置類,替代xml配置文件
@ComponentScan(basePackages = {"com.cuit"})
public class SpringConfig {
}
(2) 編寫測試類
和xml配置文件不同的是在加載配置文件時,用的是AnnotationConfigApplicationContext
@Test
public void test2(){
//基於xml配置文件的方式加載配置文件
//ApplicationContext context = new ClassPathXmlApplicationContext("myBean2.xml");
//基於配置類的方式加載配置文件
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
UserService userService = context.getBean("userService", UserService.class);
userService.add();
}
四、AOP
1、AOP(概念)
(1)面向切面編程,利用 AOP 可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
(2)通俗描述:不通過修改源代碼方式,在主干功能里面添加新功能
比如一個登錄流程,需要在登錄功能基礎上添加一個權限判斷,可以利用AOP單獨開一個權限判斷,利用其他方式對原來的登錄流程進行增強
2、動態代理
動態代理有兩種
第一種有接口的情況,使用JDK動態代理
第二種沒有接口的情況,使用CGLIB動態代理
JDK動態代理
JDK動態代理基於有接口的情況
interface Car{
void sell();
}
創建一個被代理類
class Audi implements Car{
@Override
public void sell() {
System.out.println("奧迪賣車出去");
}
}
創建一個代理類
class CarFactory{
public static Object getNewInstance(Object audi){
CarInvocationHandler handler = new CarInvocationHandler(new Audi());
//利用Proxy中的newProxyInstance方法生成一個被代理對象
//因為newProxyInstance方法中第三個參數類型為InvocationHandler,所以創建一個新的類實現InvocationHandler接口
//此方法需要傳三個參數,參數一:實現InvocationHandler接口類的類加載器;參數二:被代理類實現的一些接口;參數三:為此方法的回調接口,用於調用InvocationHandler實例中的invoke方法
return Proxy.newProxyInstance(audi.getClass().getClassLoader(), audi.getClass().getInterfaces(), handler);
}
}
class CarInvocationHandler implements InvocationHandler{
//被代理對象引用,invoke方法里面method需要使用這個被代理對象
private Object car;
public CarInvocationHandler(Object car){
this.car = car;
}
//InvocationHandler接口中有一個invoke方法,當一個代理實例的方法被調用時,代理方法將被編碼並分發到 InvocationHandler接口的invoke方法執行。
/**
* invoke() 方法有下面三個參數:
* proxy :動態生成的代理類
* method : 與代理類對象調用的方法相對應
* args : 當前 method 方法的參數
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("客戶看車");
//調用被代理對象的真實方法
Object invoke = method.invoke(car, args);
System.out.println("客戶走人");
return invoke;
}
}
測試類
public class JDKProxyTest {
public static void main(String[] args) {
Audi audi = new Audi();
Car newInstance = (Car) CarFactory.getNewInstance(audi);
//這里執行sell方法,實際執行的是invoke方法
newInstance.sell();
}
}
輸出
客戶看車
奧迪賣車出去
客戶走人
JDK動態代理使用步驟:
- 定義一個接口及其實現類;
- 自定義
InvocationHandler
並重寫invoke()
方法,在invoke
方法中我們會調用原生方法(被代理類的方法)並自定義一些處理邏輯; - 通過
Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
方法創建代理對象;
invoke()
方法: 當我們的動態代理對象調用原生方法的時候,最終實際上調用到的是 invoke()
方法,然后 invoke()
方法代替我們去調用了被代理對象的原生方法。
Java動態代理優缺點:
優點:
1.Java本身支持,不用擔心依賴問題,隨着版本穩定升級;
2.代碼實現簡單;
缺點:
1.目標類必須實現某個接口,換言之,沒有實現接口的類是不能生成代理對象的;
2.代理的方法必須都聲明在接口中,否則,無法代理;
3.執行速度性能相對cglib較低;
CGLIB動態代理
CGLIB(Code Generation Library)是一個基於ASM的字節碼生成庫,它允許我們在運行時對字節碼進行修改和動態生成。CGLIB通過繼承方式實現代理。
在 CGLIB 動態代理機制中 MethodInterceptor
接口和 Enhancer
類是核心。
需要自定義 MethodInterceptor 並重寫 intercept 方法,intercept 用於攔截增強被代理類的方法。
引入CGLIB的依賴包
<!-- cglib 動態代理依賴 begin -->
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version></dependency>
<!-- cglib 動態代理依賴 stop -->
創建目標類
class Dog{
public void call(){
System.out.println("狗---汪汪");
}
final public void run() {
System.out.println("狗----run");
}
}
代理類需要實現MethodInterceptor接口,方法調用會被轉發到該類的intercept()方法。
intercept方法中有四個參數
- o 代表Cglib 生成的動態代理類 對象本身
- method 代理類中被攔截的接口方法 Method 實例
- objects 接口方法參數
- methodProxy 用於調用父類真正的業務類方法。可以直接調用被代理類接口方法
public class CGLIBProxy implements MethodInterceptor {
//用於生成 Cglib 動態代理類工具方法
//zclass 代表需要 被代理的 委托類的 Class 對象
public Object getInstance(Class zclass){
//創建加強器,用來創建動態代理類
Enhancer enhancer = new Enhancer();
//為代理類指定需要代理的類,也即是父類
enhancer.setSuperclass(zclass);
//設置方法攔截器回調引用,對於代理類上所有方法的調用,都會調用CallBack,而Callback則需要實現intercept()方法進行攔截
enhancer.setCallback(new CGLIBProxy());
// 獲取動態代理類對象並返回
return enhancer.create();
}
/**
* o 代表Cglib 生成的動態代理類對象本身
* method 代理類中被攔截的接口方法Method 實例
* objects 接口方法參數
* methodProxy 用於調用父類真正的業務類方法。可以直接調用被代理類接口方法
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("調用一些增強的方法");
Object invokeSuper = methodProxy.invokeSuper(o, objects);
//方法實質都是調用父類方法
//Object invoke = method.invoke(o, objects);
System.out.println("方法運行完成");
return invokeSuper;
}
}
測試類
public class CGLIBProxyTest{
public static void main(String[] args) {
CGLIBProxy cGLIBProxy = new CGLIBProxy();
Dog instance = (Dog) cGLIBProxy .getInstance();
instance.call();
instance.run();
}
}
輸出
調用一些增強的方法
狗---汪汪
方法運行完成
狗----run
CGLIB動態代理使用步驟
- 定義一個類;
- 自定義
MethodInterceptor
並重寫intercept
方法,intercept
用於攔截增強被代理類的方法,和 JDK 動態代理中的invoke
方法類似; - 通過
Enhancer
類的create()
創建代理類;
CGLIB優缺點:
優點:
1.代理的類無需實現接口;
2.執行速度相對JDK動態代理較高;
缺點:
1.字節碼庫需要進行更新以保證在新版java上能運行;
2.動態創建代理對象的代價相對JDK動態代理較高;
3.代理的對象不能是final關鍵字修飾的
參考博客
https://cloud.tencent.com/developer/article/1461796
https://nilzzzz.github.io/2020/08/JDK+-CGLIB動態代理實戰/
3、AOP術語
連接點:可以被增強的方法
切入點:實際被增強的方法
通知(增強):實際增強的邏輯部分
通知的類型:
- 前置通知
- 后置通知
- 環繞通知
- 異常通知
- 最終通知
切面:把通知應用到切入點的過程
4、Spring中AOP操作
Spring框架一般都是基於AspectJ實現AOP操作
AspectJ不是Spring的組成部分,獨立AOP框架,一般把AspectJ和Spring一起使用,進行AOP操作
基於 AspectJ 實現 AOP 操作有兩種方式 (1)基於 xml 配置文件實現 (2)基於注解方式實現(使用)
基於注解方式實現AOP操作
創建一個配置類
@Configuration //作為配置類,替代xml配置文件
@EnableAspectJAutoProxy //注冊AspectJ 的自動代理默認為false啟用JDK動態代理,為true強制使用CGLIB動態代理
@ComponentScan(basePackages = {"com.cuit.aop"})
public class SpringConfig {
}
創建一個被代理類User
@Component("user")
public class User {
public void add() {
System.out.println("add........");
}
}
創建一個代理類,並且在類上添加@Aspect注解,將當前類指定為一個切面
@Component
@Aspect
public class UserProxy {
//具有相同切入點時,在每一個通知里面的注解里面寫切入點太麻煩,可以用@Pointcut注解統一設置,然后通知注解里面可以傳帶有@Pointcut的方法名
//切入點設置
@Pointcut("execution(* com.cuit.aop.User.add(..))")
public void pointcut(){
}
//@Before前置通知,目標方法前執行
@Before("pointcut")
public void berfore(){
System.out.println("berfore前置輸出");
}
//@AfterReturning后置通知,目標方法執行完執行
@AfterReturning("execution(* com.cuit.aop.User.add(..))")
public void afterReturning(){
System.out.println("afterReturning后置輸出");
}
//@AfterThrowing異常通知,目標方法拋出錯誤時執行
@AfterThrowing("execution(* com.cuit.aop.User.add(..))")
public void afterThrowing(){
System.out.println("afterThrowing拋出錯誤時輸出");
}
//@Around環繞通知,目標方法前后都會執行
@Around("execution(* com.cuit.aop.User.add(..))")
public void around(ProceedingJoinPoint point) throws Throwable {
System.out.println("around環繞前輸出");
point.proceed();
System.out.println("around環繞后輸出");
}
//@After最終通知,目標方法執行完執行
@After("execution(* com.cuit.aop.User.add(..))")
public void after(){
System.out.println("after最終輸出");
}
}
測試類
@Test
public void test3(){
//ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
ApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class);
User user = context.getBean("user", User.class);
user.add();
}
運行結果
around環繞前輸出
berfore前置輸出
add........
afterReturning后置輸出
after最終輸出
around環繞后輸出
基於xml方式實現AOP操作
<!--添加掃描-->
<context:component-scan base-package="com.cuit.aop"/>
<!--創建對象-->
<bean id="book" class="com.cuit.aop.User"></bean>
<bean id="bookProxy" class="com.cuit.aop.UserProxy"></bean>
<!--開啟AspectJ自動代理-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!--配置aop增強-->
<aop:config>
<!--配置切入點-->
<aop:pointcut id="p" expression="execution(* com.cuit.aop.User.add(..))"/>
<!--配置切面-->
<aop:aspect>
<!--增強作用在具體的方法上-->
<aop:before method="before" pointcut-ref="p"></aop:before>
</aop:aspect>
</aop:config>
多個代理類設置優先級
在代理類上加上注解@Order(數值類型)數值從0開始,數值越小,優先級越高
五、Spring事務管理
事務操作(事務概念)
事務是數據庫操作最基本單元,邏輯上一組操作,要么都成功,如果有一個失敗所有操作都失敗
典型場景:銀行轉賬
事務四個特性(ACID)
(1)原子性 (Atomicity)原子性是指事務是一個不可分割的工作單位,事務中的操作要么全部成功,要么全部失敗。
(2)一致性 (Consistency)
官網上事務一致性的概念是:事務必須使數據庫從一個一致性狀態變換到另外一個一致性狀態。
換一種方式理解就是:事務按照預期生效,數據的狀態是預期的狀態。
(3)隔離性 (Isolation)
事務的隔離性是多個用戶並發訪問數據庫時,數據庫為每一個用戶開啟的事務,不能被其他事務的操作數據所干擾,多個並發事務之間要相互隔離。
(4)持久性(Durability)
持久性是指一個事務一旦被提交,它對數據庫中數據的改變就是永久性的,接下來即使數據庫發生故障也不應該對其有任何影響。
在 Spring 進行事務管理操作有兩種方式:編程式事務管理和聲明式事務管理(使用)
注解聲明事務管理
操作步驟如下
1、在spring配置文件中添加事務管理器,也就是下面的transactionManager
2、引入tx名稱空間,開啟事務注解
<context:component-scan base-package="com.cuit"></context:component-scan>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/db" />
<property name="username" value="username" />
<property name="password" value="password" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!-- JdbcTemplate 對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property><!--set方式注入-->
</bean>
<!--創建事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--開啟事務注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
3、在 service 類上面(或者 service 類里面方法上面)添加事務注解@Transactional
如果把這個注解添加類上面,這個類里面所有的方法都添加事務
如果把這個注解添加方法上面,為這個方法添加事務
@Service
@Transactional
public class UserService {
@Autowired
private UserDao userDao;
@Autowired
private JdbcTemplate jdbcTemplate;
public void change(){
userDao.update(1,100);
//int i = 10/0;
userDao.update(2,-100);
}
}
@Repository()
public class UserDaoImpl implements UserDao {
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public void update(int id, int value) {
String sql = "update test set money = money + ? where id = ?";
int update = jdbcTemplate.update(sql, value, id);
}
}
1、在 service 類上面添加注解@Transactional,在這個注解里面可以配置事務相關參數
2、propagation:事務傳播行為
(1)多事務方法直接進行調用,這個過程中事務是如何進行管理的
事務傳播行為用來描述由某一個事務傳播行為修飾的方法被嵌套進另一個方法時事務如何傳播。
事務傳播行為類型 | 說明 |
---|---|
PROPAGATION_REQUIRED | 如果當前沒有事務,就新建一個事務,如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。 |
PROPAGATION_SUPPORTS | 支持當前事務,如果當前沒有事務,就以非事務方式執行。 |
PROPAGATION_MANDATORY | 使用當前的事務,如果當前沒有事務,就拋出異常。 |
PROPAGATION_REQUIRES_NEW | 新建事務,如果當前存在事務,把當前事務掛起。 |
PROPAGATION_NOT_SUPPORTED | 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。 |
PROPAGATION_NEVER | 以非事務方式執行,如果當前存在事務,則拋出異常。 |
PROPAGATION_NESTED | 如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。 |
具體情況可以參考下面鏈接
Spring事務傳播行為詳解 - SegmentFault 思否
3、ioslation:事務隔離級別
(1)事務有隔離性,多事務操作之間不會產生影響。不考慮隔離性產生很多問題
(2)有三個讀問題:臟讀、不可重復讀、虛(幻)讀
臟讀:事務A修改了表中的一行數據,但沒有提交,這時候事務B讀取了被事務A修改后的數據,之后事務A因為某種原因回滾,那么事務B讀取的數據就是臟的
不可重復讀:事務A先讀取了事務B修改前的數據,然后事務B提交了,這時事務A又去讀了一次數據,結果讀到的是事務B修改后的數據,產生了一次事務中讀取數據結果不一致的現象,為不可重復讀
幻讀:事務A先讀取了事務B添加前的數據,然后事務B提交了,這時事務A又去讀了一次數據,結果讀到的是事務B添加后的數據,產生了一次事務中讀取數據條數結果不一致的現象,為幻讀
(3)通過設置事務隔離級別,解決讀問題
不同隔離級別存在不同的問題
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 |
---|---|---|---|
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable read | × | × | √ |
Serializable | × | × | × |
4、timeout:超時時間
(1)事務需要在一定時間內進行提交,如果不提交進行回滾
(2)默認值是 -1 ,設置時間以秒單位進行計算
5、readOnly:是否只讀
(1)讀:查詢操作,寫:添加修改刪除操作
(2)readOnly 默認值 false,表示可以查詢,可以添加修改刪除操作
(3)設置 readOnly 值是 true,設置成 true 之后,只能查詢
6、rollbackFor:回滾
(1)設置出現哪些異常進行事務回滾
7、noRollbackFor:不回滾
(1)設置出現哪些異常不進行事務回滾
xml聲明事務管理
1、在 spring 配置文件中進行配置
第一步 配置事務管理器
第二步 配置通知
第三步 配置切入點和切面,需要引入AOP名稱空間
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/book" />
<property name="username" value="root" />
<property name="password" value="123456" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<!-- JdbcTemplate 對象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--注入 dataSource-->
<property name="dataSource" ref="dataSource"></property><!--set方式注入-->
</bean>
<!--創建事務管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置通知-->
<tx:advice id="interceptor">
<tx:attributes>
<tx:method name="select" isolation="DEFAULT"></tx:method>
<tx:method name="update" isolation="READ_COMMITTED"></tx:method>
</tx:attributes>
</tx:advice>
<!--配置切入點和切面-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.cuit.service.UserService.*(..))"/>
<aop:advisor advice-ref="interceptor" pointcut-ref="pc"></aop:advisor>
</aop:config>
完全注解聲明事務管理
創建配置類,使用配置類替代 xml 配置文件
@Configuration
@ComponentScan("com.cuit") //組件掃描
@EnableTransactionManagement //開啟事務
public class StartConfig {
//創建數據庫連接池
@Bean
public DruidDataSource getDataSource(){
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql:///user_db");
dataSource.setUsername("root");
dataSource.setPassword("root");
return dataSource;
}
//創建 JdbcTemplate 對象
@Bean
public JdbcTemplate getJdbcTemplate(DruidDataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//創建事務管理器
@Bean
public DataSourceTransactionManager getTransactionManager(DruidDataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
六、Spring5新特性
1、整個 Spring5 框架的代碼基於 Java8,運行時兼容 JDK9,許多不建議使用的類和方法在代碼庫中刪除
2、Spring 5.0 框架自帶了通用的日志封裝
(1)Spring5 已經移除 Log4jConfigListener,官方建議使用 Log4j2
(2)Spring5 框架整合 Log4j2
第一步 引入 jar 包
第二步 創建 log4j2.xml 配置文件,這個文件名稱是固定的
<?xml version="1.0" encoding="UTF-8"?>
<!--日志級別以及優先級排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE >
ALL -->
<!--Configuration 后面的 status 用於設置 log4j2 自身內部的信息輸出,可以不設置,
當設置成 trace 時,可以看到 log4j2 內部各種詳細輸出-->
<configuration status="INFO">
<!--先定義所有的 appender-->
<appenders>
<!--輸出日志信息到控制台-->
<console name="Console" target="SYSTEM_OUT">
<!--控制日志輸出的格式-->
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-
5level %logger{36} - %msg%n"/>
</console>
</appenders>
<!--然后定義 logger,只有定義 logger 並引入的 appender,appender 才會生效-->
<!--root:用於指定項目的根日志,如果沒有單獨指定 Logger,則會使用 root 作為
默認的日志輸出-->
<loggers>
<root level="info">
<appender-ref ref="Console"/>
</root>
</loggers>
</configuration>
3、Spring5 框架核心容器支持@Nullable 注解
實質是告訴編譯器,接受空值,並且如果重寫該方法,則還應接受空值。,
1)@Nullable 注解可以使用在方法上面,屬性上面,參數上面,表示方法返回可以為空,屬性值可以
為空,參數值可以為空
2)注解用在方法上面,方法返回值可以為空
3)注解使用在方法參數里面,方法參數可以為空
4)注解使用在屬性上面,屬性值可以為空
4、Spring5 核心容器支持函數式風格 GenericApplicationContext
5、Spring5 支持整合 JUnit5
第一步 引入 JUnit5 的 jar 包
第二步 創建測試類,使用注解完成
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:bean.xml")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}
//也可以使用復合注解代替上面兩個
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class JTest5 {
@Autowired
private UserService userService;
@Test
public void test1() {
userService.accountMoney();
}
}