目錄
一.spring的IOC
IOC:控制反轉(inversion of control) ,就是將對象創建的權利交給spring,而程序員無需去關注對象的生命週期。
(程序員手動編寫:Gril gril= new Gril(); ----> spring容器負責對象的創建:Gril gril = spring容器.getBean();)
1.spring的IOC的入門案例
步驟:
①導入spring的依賴包
②編寫實體類
//編寫實體類
public class Hello {
public void test() {
System.out.println("Hello Spring");
}
}
③編寫spring的核心配置文件applicationContext.xml(可以配置使用快捷鍵導入頭文件約束)
<?xml version="1.0" encoding="UTF-8"?>
<!-- 頭文件
xmlns:表示xml的命名空間www.springframework.org表示spring的官方配置beans
xmlns:xsi:表示遵循的xml的規範XMLSchema-instance
xsi:schemaLocation:表示xml的書寫的語法格式
-->
<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-3.2.xsd ">
<!-- bean表示spring-beans-3管理的實體類 -->
<!--
id:表示bean標籤的唯一標識,不能重複。同時,id的格式爲類名首字母小寫
class:表示實體類所在的路徑(包名.類名)
bean標籤中的id不能重複,否則會在配置文件加載時報錯
bean標籤中的class不能重複,否側在加載文件時不報錯,在map取值時報錯,找不到唯一的class
-->
<bean id="hello" class="pojo.Hello"></bean>
<!-- 別名標籤:alias
name屬性:表示引用的bean的id的值
alias:別名
-->
<alias name="hello" alias="spring01"/>
</beans>
④啓動spring容器
⑤獲取Hello的對象,然後進行方法的調用
public class TestSpring {
@Test
public void test01() {
//1、spring容器的創建:加載了配置文件中的信息
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
//2、從spring容器中獲取對象(key:id屬性的值,value:創建出來的對象)
//1,通過id屬性獲取對象
//2,通過class類型獲取對象
Hello hello = (Hello) context.getBean("hello");
//Hello hello = (Hello) context.getBean(Hello.class);
//Hello hello = (Hello) context.getBean("spring01");//別名
//3、調用hello對象中的方法
hello.test();
}
}
2.spring的IOC的原理
當spring容器啓動時,會找到了對應的核心配置文件applicationContext.xml
並加載配置文件,逐行讀取xml配置,然後遇到bean標籤時,進行解析
bean標籤的id屬性,解析爲map中的key,bean標籤的class屬性,解析時通過java的反射機制,創建對象,放入了map的value中
獲取對象的方式 :通過id的值或class類型
3.spring容器創建對象的方式
①.spring默認使用無參構造創建對象:必須要有無參構造
②.使用靜態的工廠類,在工廠類中寫一個getXXX的靜態方法
③.使用非靜態的實例工廠類,在工廠類中寫一個getXXX的非靜態方法
④.使用spring提供的一個接口:FactoryBean<~>
建議:需要創建對象時,而該對象又不能直接被spring容器管理,那麼可以選擇spring提供的工廠模式,FactoryBean
<?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-3.2.xsd ">
<!--spring默認使用無參構造創建對象-->
<bean id="hello" class="pojo.Hello"></bean>
<!-- 靜態工廠 factory-method="" 表示靜態工廠中的靜態方法 -->
<bean id="calendarA" class="pojo.StaticFactory" factory-method="getCalendar"></bean>
<!-- 實例工廠 -->
<bean id="newFactory" class="pojo.InstanceFactory"></bean>
<!-- 獲取calendar日曆對象 -->
<bean id="calendarB" factory-bean="newFactory" factory-method="getCalendar"></bean>
<!-- spring提供的FactoryBean -->
<bean id="calendarC" class="pojo.SpringFactory"></bean>
</beans>
public class Hello {
//有參構造
public Hello(int a){System.out.println("我是有參構造");}
//自定義方法
public void say() {System.out.println("Hello Spring");}
}
//靜態工廠
public class StaticFactory {
//自定義方法:靜態static 修飾
public static Calendar getCalendar() {
return Calendar.getInstance();
}
}
//實例工廠
public class InstanceFactory {
//自定義方法:不使用static
public Calendar getCalendar() {
return Calendar.getInstance();
}
}
public class SpringFactory implements FactoryBean<Calendar>{
//getObject()獲取對象的方法
@Override
public Calendar getObject() throws Exception {
return Calendar.getInstance();
}
//獲取對象的類型
@Override
public Class<?> getObjectType() {
// TODO Auto-generated method stub
return Calendar.class;
}
//對象是單例還是多例
@Override
public boolean isSingleton() {
return false;
}
}
4.spring中的單例和多例
①單例模式:在內存中,有且只有一份對象
- 優點:節省內存的開銷
- 缺點:不能滿足某些項目需求,如:訂單模塊,訂單對象在單例情況下對象的內容會更新
②spring中配置的標籤,對應着一個對象,默認單例模式。
③單例模式下:spring直接創建出對象,負責對象的生命週期
多例模式下:spring不負責對象的生命週期的維護。也就是說,什麼時候調用getBean,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-3.2.xsd"
default-lazy-init="true">
<!-- spring默認是單例 scope="singleton" 單例 scope="prototype" 多例 -->
<!-- <bean id="user" class="pojo.User" scope="prototype"></bean> -->
<!-- spring默認不是懶加載 lazy-init=" "表示懶加載生效,在spring容器啓動時不加載,調用時才加載 -->
<bean id="user" class="pojo.User"></bean>
</beans>
public class TestSpring {
//測試單例還是多例
@Test
public void test01() {
//啓動spring容器
ApplicationContext context = new
ClassPathXmlApplicationContext("applicationContext.xml");
//獲取user對象
User user1 = (User) context.getBean("user");
User user2 = (User) context.getBean("user");
//輸出user對象的地址
System.out.println(user1);
System.out.println(user2);
}
}
二.spring的DI
DI:依賴注入(Dependcy Injection)
- 依賴:誰依賴誰 ? 對象的創建依賴於成員變量
- 注入:誰注入誰?把成員變量的值注入到對象中
1.DI的兩種依賴注入
①set方式注入(必須要有對應的set方法才能正常注入)
②構造方法constructor注入
建議採用set方式注入,正常的項目開發中,一般使用set方式,而spring源碼設計時使用的constructor方式
2.set方式注入
①set方式注入原理:set方式是根據setXXX()方法中的XXX名稱來判斷的,spring進行解析時,找到xxx屬性,取出value值,然後根據對象中的setXXX()方法去匹配,匹配的規則:去掉set,然後XXX變爲小寫xxx,匹配成功則注入。
②注入類型
a.簡單類型屬性值的注入
b.複雜類型屬性值的注入
c.引用類型屬性值的注入
<?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-3.2.xsd">
<!-- set方式的注入 -->
<bean id="user" class="pojo.User">
<!-- property標籤:表示描述類中的各個屬性
name="":表示屬性名
value="":表示屬性值
spring在依賴注入時:自動識別簡單的數據類型
-->
<property name="name" value="張三丰"></property>
<property name="age" value="10"></property>
<!-- 複雜類型 -->
<!-- list在spring中,默認是ArrayList類型的 -->
<property name="list">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
</list>
</property>
<property name="map">
<map>
<entry key="1" value="一"></entry>
<entry key="2" value="二"></entry>
<entry key="3" value="三"></entry>
<entry key="4" value="四"></entry>
</map>
</property>
<property name="set">
<set>
<value>1</value>
<value>1</value>
<value>1</value>
<value>1</value>
</set>
</property>
<!-- 數組 array -->
<property name="arr">
<list>
<value>1</value>
<value>2</value>
<value>3</value>
<value>4</value>
</list>
<!-- 探索:<array>標籤到底是什麼意思? -->
</property>
<!-- properties :userName = root -->
<property name="pro">
<props>
<!-- key:value鍵值對 -->
<prop key="1"> 八 </prop>
<prop key="2"> 二 </prop>
<prop key="3"> 三 </prop>
<prop key="4"> 四 </prop>
</props>
</property>
<!-- 引用類型 ref="":引用了其他的bean標籤,值是bean標籤中的id的值 -->
<property name="dog" ref="dog"></property>
</bean>
<!-- 描述Dog類的對象 把dog對象注入到User的dog屬性中 -->
<bean id="dog" class="pojo.Dog"></bean>
</beans>
public class User {
//簡單類型
private String name;
private int age;
//複雜類型
private List list;
private Map map;
private Set set;
private String[] arr;
private Properties pro;
//引用類型
private Dog dog;
public Dog getDog() {
return dog;
}
public void setDog(Dog dog) {
this.dog = dog;
}
public List getList() {
return list;
}
public Map getMap() {
return map;
}
public void setMap(Map map) {
this.map = map;
}
public Set getSet() {
return set;
}
public void setSet(Set set) {
this.set = set;
}
public String[] getArr() {
return arr;
}
public void setArr(String[] arr) {
this.arr = arr;
}
public Properties getPro() {
return pro;
}
public void setPro(Properties pro) {
this.pro = pro;
}
public void setList(List list) {
this.list = list;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "User [name=" + name + ", age=" + age + ", list=" + list
+ ", map=" + map + ", set=" + set + ", arr="
+ Arrays.toString(arr) + ", pro=" + pro + ", dog=" + dog + "]";
}
}
3.構造方法進行依賴注入
①spring中默認的是無參構造,使用構造方法注入,那麼必須要進行構造方法的重載
②構造方法的注入:不夠靈活,需要重載方法,根據需求會發生變化
三.核心配置文件的簡化
1.自動裝配(對引用類型參數的簡化)
<bean autowire="" ></bean>
①自動裝配方式:autowire=“byName” 和 autowire=“byType”
②主要作用:取代了 property標籤
③全局的自動裝配:default-autowire=“byName”
④原理:必須要有set方法才生效
byName:對bean標籤進行解析時,遇到 autowire="byName"的屬性,spring會自動找到類中成員變量的set方法
舉例:setDog 解析時,會先把set去掉 ,然後得到Dog,再進行首字母的小寫,得到dog。那麼該dog就是bean標籤中的id屬性的值
spring會根據dog,找到配置文件中bean標籤的id屬性(byType是class屬性)之後,進行依賴注入。如果匹配成功,dog對象會注入到user對象中,如果匹配不出成功,會注入null值
2.屬性註解
①屬性註解的形式:@Autowired
②屬性註解取代了:autowire
③使用方式:
1)在類的成員變量上面,添加@Autowired
2)編寫配置文件,添加了頭文件約束和開啓屬性註解的掃描
④屬性註解的執行原理:
spring容器加載時,會解析核心配置文件,當解析到<context:annotation-config /> 標籤時,掃描每個bean標籤對應的類中的屬性註解
遇到成員變量添加了@Autowired,會進行自動裝配過程
先進行自動裝配byName,根據成員變量的名稱得到id,舉例:Dog dog,拿到dog去匹配bean標籤,如果匹配成功,那麼自動進行依賴注入,不需要set方法的支持。如果匹配不成功,那麼採用自動裝配byType,根據成員變量的類型 得到class類型,
3.類的註解
①類的註解的形式:@Component
②類的註解取代了:bean標籤
③使用方式
1)在類的上面添加 @Component
2)在配置文件中,添加開啓包掃描 <context:component-scan base-package=“包的路徑”>
注意:開啓包掃描的同時,開啓了屬性註解掃描
④ID生成策略:
指定id:@Component(value=“自定義的名字”)
自動生成規則:根據類名的第二個字母判斷,如果是小寫,那麼類名生成的id首字母小寫 ,如果是大寫,那麼類名生成的id首字母不變
⑤類的註解的執行原理:
spring加載配置文件,讀取到<context:component-scan base-package=“包的路徑”>標籤
spring首先會根據包掃描,對包中的所有的javabean都進行掃描,遇到@Component註解,會先進行對象的加載,相當於bean標籤創建對象
然後會繼續掃描屬性註解,遇到屬性註解@Autowired,會進行自動裝配過程,完成對javabean對象的依賴注入
4.屬性賦值
①賦值形式:@Value(value=“值”)
②取代了bean標籤中的property標籤
③使用方式:在類的成員變量上添加@Value(value="")
④引入外部的配置文件
1)在配置文件中: <context:property-placeholder location=“文件相對位置”>
2)在實體類中 ,使用 @Value("${配置文件中的key}")
3)可以靈活地去配置對象中的值
⑤複雜類型賦值操作 :@Value("#{@id的值}")
5.spring註解的高級用法
@Repository //表示dao層
@Service //表示service層
@Controller //表示controller層
@Component //表示任意一個類的註解
@Lazy(true) //懶加載生效
@Scope(“prototype”) //多例模式
//類的註解
@Component
public class User {
//屬性註解
@Autowired
private Dog dog;
//簡單類型
@Value(value="張無忌")
private String name;
@Value("${age}")
private int age;
//複雜類型
@Value("#{@list}")
private List list;
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd">
<!-- 開啓包掃描 注意:在開啓包掃描的同時,自動開啓了屬性註解掃描 -->
<context:component-scan base-package="pojo"/>
<!-- 引入外部的配置文件 location="" 表示引用文件的相對路徑 -->
<context:property-placeholder location="classpath:/test.properties" />
</beans>
6.在面向接口編程中的使用
①面向接口編程是一種設計模式。
②作用:能夠使業務邏輯的方法與業務邏輯的具體實現進行分離。
③舉例:UserService 接口有addUser(User user);方法,UserServiceImpl:實現了接口,並進行了方法的重寫
④好處:
----從設計層面來說,架構師會事先把需要的接口定義好,而程序員只需要實現具體的業務邏輯。
----從開發的角度來說,依賴的接口不變,而實現類發生了改變,包括類名發生變化。但是對程序流程來說,沒有任何影響。
⑤使用:
----接口的實現類UserServiceImpl使用了@Service,生成了 id = “UserServiceImpl” class=“service.UserServiceImpl”
----接口依賴時private UserService userService;使用了@Autowired
----spring發現UserService作爲一個接口,無法直接進行依賴注入,根據接口找到了接口的實現類
----匹配成功則完成依賴注入的自動裝配,相當於 UserService userService = new UserServiceImpl();
⑥建議:在項目過程當中,實現接口的類,最好只有一個
四.spring的AOP面向切面編程
spring是在AOP技術的基礎上進行了再次的封裝,讓使用者更加方便的進行調用,AOP的底層是動態代理模式。
1.代理模式
- 代理模式:不影響被代理者的主要業務邏輯,實現額外的操作與主要本職工作之間的解耦
- 好處:在項目當中,可以完成目標方法時進行額外的事件(事務模塊)添加,同時進行解耦。
2.靜態代理模式
- 案例:
①需求: 使用MVC分層思想完成用戶模塊的新增用戶操作的同時完成對新增和修改功能的事務控制
②目的 :實現事務控制層與業務邏輯層之間的解耦
③實現步驟:
----傳統的事務控制方式
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
//傳統的事務控制方式(事務控制和service層產生了強耦合)
@Autowired
private TransactionManager tx;
@Override
public void addUser(User user) {
tx.open();
userDao.addUser(user);
tx.commit();
}
}
----靜態代理模式:
①web層:無需改動
②proxy代理:@Component(“userService”)主要目的是,讓web層依賴代理對象,對於web層來說,沒有任何區別。
③service層:@Component(“target”)主要目的是,讓代理對象依賴真實有效的目標對象 ,然後調用本職操作。
@Component("userService")
public class Proxy implements UserService{
//代理的額外操作
@Autowired
private TransactionManager tx;
//真實有效的目標對象
@Autowired
private UserService target;
//代理的本職操作
@Override
public void addUser(User user) {
tx.open1();
//調用service層的addUser方法
target.addUser(user);
tx.commit();
}
}
@Service("target")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
@Override
public void addUser(User user) {
userDao.addUser(user);
}
}
<!--開啓包掃描 -->
<context:component-scan base-package="pojo,dao,service,web,tx,proxy"/>
-
靜態代理模式的優點:
----①實現了業務層與事務控制層之間的解耦
----②代理類能夠完成本職工作調用(目標對象的方法調用)的同時還能夠完成額外的操作(對業務邏輯方法添加事務控制) -
靜態代理模式的缺點:一個業務模塊,必須要對應着一個代理類,代碼依然會有大量的重複
-
靜態代理模式實現的前提條件:必須要實現與目標對象相同的接口。
-
靜態代理模式與裝飾者模式的區別:主要目的在於解耦
----裝飾者模式:是對目標對象的一種增強實現,相當於對一個人化妝
----靜態代理模式:是對目標對象的一種解耦實現。
3.動態代理模式
1)動態代理介紹
- 動態代理的解決方案:在代碼編譯期間不存在代理類。在代碼運行期間,纔會自動的產生代理類和代理對象。
- 優點:代理類不需要提前編譯好,減少了代碼量,解決了靜態代理模式的缺點。
- 動態代理的實現技術: java的反射機制 以及 回調函數
- 動態代理的實現方式: jdk提供的動態代理 、 spring提供的CGlib動態代理
2)JDK的動態代理實現
- 使用了java提供的Proxy類 和 InvocationHandler接口
- 添加兩個成員變量: 目標對象(本職的操作)、事務控制(額外的操作)
- 添加構造方法,完成成員變量的初始化
- 非靜態的自定義方法getProxy():主要使用java.lang.reflect.Proxy類完成目標對象的代理類和代理對象
- 添加回調函數,主要完成:當客戶端發起請求時,攔截到該方法,進入invoke()方法中,完成額外操作和本職的方法調用
- 優點:①繼承了靜態代理的優點,②解決的代碼重複的問題,③動態的生成代理類和代理對象
- 前提:必須要求目標對象和代理對象實現相同的接口。
//動態代理的解決方案的實現
public class JDKDynamicProxy implements InvocationHandler{
//定義目標對象
private Object target;
//定義額外操作對象
private TransactionManager tx;
//構造方法:完成目標對象與事務對象的初始化賦值
public JDKDynamicProxy(Object target,TransactionManager tx) {
this.target = target;
this.tx = tx;
}
//完成動態代理,生成代理類和代理對象
public Object getProxy() {
/*
* ClassLoader loader:類加載器
* 代理類不存在,所以就沒有相應的類加載器,可以用其他類獲取類加載器
*
* Class<?>[] interfaces: 表示目標對象的類所實現的所有的接口
* 爲了讓代理類看起來和目標對象的類一樣,方法完全一樣,實現目標對象類的所有接口
*
* InvocationHandler h: 表示代理對象的方法重寫
* 方法的重寫主要是爲了完成額外的操作(事務開啓與事務提交)
* 完成本職工作:調用目標對象的方法
*/
Class[] is = target.getClass().getInterfaces();
Object proxy = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
return proxy;
}
//回調函數
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//完成額外的操作
tx.open1();
//完成本職的工作:使用方法對象method.invoker()反射機制
//方法對象是根據客戶端發送的不同方法調用完成的
//result是目標對象的調用的方法返回值
Object result = method.invoke(target, args);
//完成額外的操作
tx.commit();
return result;
}
}
3)Cglib的動態代理
- 使用spring提供的類Enhancer類(增加類,操作二進制文件) 和 MethodIntercepter接口
- 特點:目標對象是作爲代理對象的父類,沒有必要實現接口
public class CGlibDynamicProxy implements MethodInterceptor{
//定義一個目標對象
private Object target;
//定義一個事務模塊的對象
private TransactionManager tx;
//構造方法:完成目標對象與事務對象的初始化賦值
public CGlibDynamicProxy(Object target,TransactionManager tx) {
this.target = target;
this.tx = tx;
}
//cglib動態代理的核心代碼:生成代理類
public Object getProxy() {
Enhancer enhancer = new Enhancer();
//設置目標對象爲代理對象的父類
enhancer.setSuperclass(target.getClass());
//設置一個回調函數
enhancer.setCallback(this);
//創建代理對象
return enhancer.create();
}
//回調函數
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
//完成額外的操作
tx.open1();
//完成本職的工作:使用方法對象method.invoker()反射機制
//方法對象是根據客戶端發送的不同方法調用完成的
//result是目標對象的調用的方法返回值
Object result = method.invoke(target, args);
//完成額外的操作
tx.commit();
return result;
}
}
4.spring的AOP面向切面編程(思想和設計理念)
- 介紹
面向對象編程:使用封裝 、 繼承 、多態,把相應的方法寫在類的內部,而使用者直接進行類的實例化,然後通過實例化對象調用方法,實現方式: 對象.方法名();
面向接口編程:使用接口及接口的實現類,根據需求完成具體功能的設計,然後程序員根據接口,只需要實現具體的方法邏輯即可,
實現方式: 類名 implements 接口 ; 實現接口中的方法體
面向切面編程:首先要理解什麼是切面。
①把與業務邏輯緊密結合的代碼抽離出來,放在一個統一的類中。該類是切面類,主要作用是完成特定的功能模塊(事務模塊)的方法 。 切面實現的模塊主要包括:數據庫中的事務/數據庫連接池/權限控制/日誌分析/安全性的處理(這些模塊與業務邏輯無關)
③切面類是對service層的方法進行額外操作的,那麼就需要定義一個切入點(指向了service層的方法),切入點:一種匹配規則,比如:在service層的UserServiceImpl類上加入切入點,就表示該切面類中的事務控制,對UserServiceImpl中的方法生效了。只有滿足了匹配規則的方法,才能執行通知.
④通知:切面中的方法(主要實現額外的操作)。
⑤在執行完額外通知後,需要進行本職工作的調用。那麼,本職方法的調用是通過連接點完成的。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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-3.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.2.xsd ">
<!--開啓包掃描 -->
<context:component-scan base-package="pojo,dao,service,web,tx,aspect"/>
<!-- 配置aop的切面 -->
<aop:config>
<!-- 設置切面類的引用 -->
<aop:aspect ref="txAspect">
<!-- 配置切入點 -->
<aop:pointcut expression=
"within(service.UserServiceImpl)" id="pc"/>
<!-- 配置切面的通知:完成額外的操作以及本職工作 -->
<!-- 綁定切入點同時找到環繞通知的方法 -->
<aop:around method="around" pointcut-ref="pc" />
</aop:aspect>
</aop:config>
</beans>
@Component
@Aspect //使用註解的形式讓spring知道這是切面類
public class TxAspect {
@Autowired
private TransactionManager tx;
//在結合使用細粒度表達式,返回值類型最好寫上Object
//因爲有些方法的返回值類型不爲void,那麼就需要獲取方法的執行結果
@Around(value="execution(* service..*.*(..)) ")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
tx.open1();
Object result = joinPoint.proceed();
tx.commit();
return result;
}
}
-
切入點表達式
①粗粒度表達式:侷限性太大,只能針對返回值類型爲void的方法,不建議使用
②細粒度表達式
語法格式:Expression=" execution(返回值類型 包名.類名.方法名(包名.參數的類型) ) "
使用註解形式 — 簡化核心配置文件,同時完成細粒度的方法註解(對相應的方法單獨添加註解用於完成精準的控制)
a.核心配置文件:開啓AOP註解的掃描 aop:aspectj-autoproxy/
b.在切面類,添加 @Aspect註解
c.在切面類中,添加 @Around註解 -
spring當中的五大通知
a.環繞通知(Around Advice):表示在目標方法執行的前後添加相應的額外操作。
b.前置通知 (Before Advice):表示在目標方法執行之前進行額外的操作。
前置通知不執行目標方法,所以不能添加ProceedingJoinPoint類作爲形參。
c.後置通知 (AfterRetuning Advice) :是在目標方法執行完畢之後,才執行的。
後置通知一般用於日誌分析與日誌監控,同時在方法執行完畢之後,需要拿到相應的返回值結果做分析。
d.異常通知 (AfterThrowing Advice):在目標方法執行過程中,如果發生了異常,對異常信息進行捕獲和處理
e.最終通知(After (finally) Advice):無論目標方法是否成功執行,都會進行最終通知的執行
6.使用註解的形式,實現五大通知的操作
@Component
@Aspect
public class TxAspect {
@Autowired
private TransactionManager tx;
//自定義的方法
@Pointcut(value="execution(* service..*.*(..))")
public void pointCut() {
//空方法體
}
//通知的方法 :環繞通知
//@Around(value="execution(* service..*.*(..))")
@Around(value="pointCut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
tx.open1();
//本職工作
Object result = joinPoint.proceed();
tx.commit();
return result;
}
//前置通知:主要是用在日誌分析 日誌監控
//前置通知沒有執行目標方法
//JoinPoint主要用在非環繞通知中
@Before(value="pointCut()")
public void before(JoinPoint joinPoint) {
System.out.println("前置通知");
//如何獲取連接點方法的 方法名addUser方法
//1、獲取目標對象的類型
Class targetClass = joinPoint.getTarget().getClass();
//2、獲取該類中的方法簽名對象
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//3、獲取方法名
String methodName = methodSignature.getName();
}
//後置通知
// int number 表示與環繞通知執結果之後的參數類型一致
@AfterReturning(value="pointCut()",returning="number")
public void afterReturning(int number) {
System.out.println("後置通知");
System.out.println(number);
}
//異常通知
//Throwable throwable 所有異常的頂級父類,能夠捕獲所有的異常
@AfterThrowing(value="pointCut()",throwing="throwable")
public void afterThrowing(Throwable throwable) {
System.out.println("異常通知");
System.out.println("異常信息:"+throwable.getMessage());
}
//最終通知
@After(value="pointCut()")
public void after() { System.out.println("最終通知"); }
}
注意:使用 空方法切入點表達式
7.多個切面通知切入,環繞通知的執行邏輯
- springAOP面向切面編程的原理
①說明:底層是動態代理模式,而動態代理模式有兩種:jdk的動態代理和 cglib的動態代理
當業務邏輯層有接口實現時,springAOP底層是JDK的動態代理
當業務邏輯層沒有接口實現時,springAOP底層是CGlib的動態代理
②AOP的調用過程:
1、當客戶端發起請求時,該請求會先執行到 切面類。它是根據切入點的匹配規則找到相應的方法
2、切面類中,使用環繞通知的形式,先執行額外的操作,在執行本職的工作
3、在執行本職工作時,是根據連接點執行的。ProceedingJoinPoint類,該類通過反射機制,找到了連接點方法 addUser()方法。
- 權限控制模塊
需求:使用MVC的思想分層,對業務邏輯層的各個方法,添加相應的權限。當該用戶具有相應的權限時,該方法才能執行
思路:
1、給用戶準備一個權限列表:定義一個權限列表的類
使用成員變量List 集合來模擬權限列表
2、編寫自定義的權限註解
使用PrivilegeAnno註解類,對相應的業務邏輯添加註解
3、編寫權限控制的切面類
使用 around 環繞通知,在方法體中,先判斷該用戶是否有相應的權限
4、編寫核心配置文件
使用包掃描, 把切面類 、註解類 進行添加到包掃描中
5、編寫單元測試
如何通過環繞通知中的連接點 去獲取 目標對象中的目標方法上的註解中的name屬性的值(最終的權限) ????
//環繞通知
@Around(value="execution(* service..*.*(..))")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
Object result = null;
//1、獲取目標對象的類型
Class targetClass = joinPoint.getTarget().getClass();
//2、獲取方法簽名對象
//注意:通過方法簽名對象無法獲取註解
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
//3、獲取方法名 ---> 能夠直接通過方法簽名對象獲取
String methodName = methodSignature.getName();
//4、獲取參數列表
Class[] argsClass = methodSignature.getParameterTypes();
//5、通過java的反射機制完成方法對象的創建
Method method = targetClass.getMethod(methodName, argsClass);
//6、先判斷該方法對象中是否有 註解
if (method.isAnnotationPresent(PrivilegeAnno.class)) {
//具有註解
//7、獲取 方法對象上的註解
PrivilegeAnno priInfo = method.getAnnotation(PrivilegeAnno.class);
//9、獲取 註解對象中的name屬性的值
String priName = priInfo.name();
//10、匹配權限列表,如果匹配成功,執行方法,如果不成功,給出提示
List<String> priList = PrivilegeUtil.getPriList();
if (priList.contains(priName)) {
//表示有相應的權限
System.out.println("恭喜您 擁有權限");
result = joinPoint.proceed();
} else {
//表示沒有相應的權限
System.out.println("對不起 您沒有相應的權限");
}
} else {
//8、沒有註解,執行放行該方法
result = joinPoint.proceed();
}
return result;
}