如果轉(zhuǎn)載文章請注明出處, 謝謝 !
本系列文章是學習完 Spring4.3.8 后的詳細整理, 如果有錯誤請向我指明, 我會及時更正~??
Spring4.3.8
Spring4.3.8學習[一]
Spring4.3.8學習[二]
4. 面向切面編程
這里有一張網(wǎng)上看到的圖我覺得也蠻適合理解的, 可以看完下面的代理之后返回來仔細看看這張圖能夠更容易理解:
4.1 代理模式
我們在 Hibernate 中處理增刪改功能都要添加事務, 代碼如下:
public class UserDaoImpl implements UserDao {
@Override
public void insertUser() {
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
System.out.println("插入用戶");
transaction.commit();
session.close();
}
@Override
public void delete(Integer uid) {
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
System.out.println("刪除用戶");
transaction.commit();
session.close();
}
}
事務和邏輯的處理混雜在一起, 如果再增添方法還需要繼續(xù)重復有關(guān)事務的重復代碼.
或者需求發(fā)生了變化爬凑,要求項目中所有的類在執(zhí)行方法時輸出執(zhí)行耗時萎馅。最直接的辦法是修改源代碼
缺點:
1讹开、工作量特別大痊乾,如果項目中有多個類,多個方法,則要修改多次。
2您旁、違背了一些設(shè)計原則:
開閉原則(OCP)[對擴展開放,對修改關(guān)閉轴捎,而為了增加功能把每個方法都修改了鹤盒,也不便于維護。]
單一職責(SRP)[每個方法除了要完成自己本身的功能侦副,還要計算耗時侦锯、延時;每一個方法引起它變化的原因就有多種跃洛。]
4.1.1 靜態(tài)代理
1率触、定義UserDao接口, 抽象主題
public interface UserDao{
void insertUser();
void delete(Integer uid);
}
2、實現(xiàn)UserDao接口, 被代理的目標對象, 真實主題
public class UserDaoImpl implements UserDao {
@Override
public void insertUser() {
System.out.println("插入用戶");
}
@Override
public void delete(Integer uid) {
System.out.println("刪除用戶");
}
}
3汇竭、代理類, 靜態(tài)代理類
public class UserDaoProxy implements UserDao{
//被代理的對象
private UserDao userDao;
private Transaction transaction;
public UserDaoProxy(UserDao userDao, Transaction transaction) {
this.userDao = userDao;
this.transaction = transaction;
}
@Override
public void insertUser() {
transaction.beginTransaction();
userDao.insertUser();
transaction.commitTransaction();
}
@Override
public void delete(Integer uid) {
transaction.beginTransaction();
userDao.insertUser();
transaction.commitTransaction();
}
}
4葱蝗、測試
public class ProxyTest {
@Test
public void testProxy(){
UserDao dao = new UserDaoImpl();
Transaction transaction = new Transaction();
UserDaoProxy proxy = new UserDaoProxy(dao, transaction);
proxy.insertUser();
}
}
通過靜態(tài)代理可以解決這些設(shè)計原則問題, 但是不能解決:
如果項目中有多個類,則需要編寫多個代理類细燎,工作量大两曼,不好修改,不好維護玻驻,不能應對變化.
如果要解決上面的問題悼凑,可以使用動態(tài)代理
4.1.2 JDK動態(tài)代理
JDK的動態(tài)代理主要涉及 java.lang.reflect 包中的兩個類:Proxy和InvocationHandler偿枕。其中,InvocationHandler是一個接口户辫,可以通過實現(xiàn)該接口定義橫切邏輯渐夸,并通過反射機制調(diào)用目標類的代碼,動態(tài)地將橫切邏輯和業(yè)務邏輯編織在一起渔欢。而Proxy利用InvocationHandler動態(tài)創(chuàng)建一個符合某一接口的實例墓塌,生成目標類的代理對象。
只需要一個代理類奥额,而不是針對每個類編寫代理類苫幢。
1、在上一個示例中修改代理類 UserDaoProxy 如下:
JDK動態(tài)代理需要實現(xiàn) InvocationHandler 接口, 這個類其實應該是攔截器類
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 攔截器:
* 1. 目標類導入
* 2. 事務導入
* 3. invoke完成:
* 1.開啟事務
* 2.調(diào)用目標對象方法
* 3.事務提交
*/
public class UserDaoProxy implements InvocationHandler {
private Object target;
private Transaction transaction;
public UserDaoProxy(Object target, Transaction transaction) {
this.target = target;
this.transaction = transaction;
}
/**
* 當用戶調(diào)用對象中的每個方法時都通過下面的方法執(zhí)行垫挨,方法必須在接口
* proxy 被代理后的對象
* method 將要被執(zhí)行的方法信息(反射)
* args 執(zhí)行方法時需要的參數(shù)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String methodName = method.getName();
if (!methodName.equals("getUser")){
this.transaction.beginTransaction();
method.invoke(target, args); // 目標對象調(diào)用目標方法
this.transaction.commitTransaction();
} else {
method.invoke(target, args);
}
return null;
}
/**
* 獲得被代理后的對象
* @param object 被代理的對象
* @return 代理后的對象
* loader:一個ClassLoader對象韩肝,定義了由哪個ClassLoader對象來生成代理對象進行加載
* interfaces:一個Interface對象的數(shù)組,表示的是我將要給我需要代理的對象提供一組什么接口九榔,如果我提供了一組接口給它哀峻,那么這個代理對象就宣稱實現(xiàn)了該接口(多態(tài)),這樣我就能調(diào)用這組接口中的方法了
* h:一個InvocationHandler對象哲泊,表示的是當我這個動態(tài)代理對象在調(diào)用方法的時候谜诫,會關(guān)聯(lián)到哪一個InvocationHandler對象上,間接通過invoke來執(zhí)行
*/
public Object getProxyObject(Object object){
this.target = object;
return Proxy.newProxyInstance(
target.getClass().getClassLoader(), //類加載器
target.getClass().getInterfaces(),//獲得被代理對象的所有接口
this); //攔截器: InvocationHandler對象
}
}
2攻旦、測試一下:
public class JDKProxyTest {
@Test
public void testProxy(){
/*
1. 創(chuàng)建一個目標類
2. 創(chuàng)建一個事務
3. 創(chuàng)建一個攔截器
4. 動態(tài)產(chǎn)生代理對象
*/
Object target = new UserDaoImpl();
Transaction transaction = new Transaction();
List<Interceptor> interceptorList = new ArrayList<>();
interceptorList.add(transaction);
UserDaoProxy interceptor = new UserDaoProxy(target, transaction);
UserDao dao = (UserDao) interceptor.getProxyObject(target);
dao.delete("10");
}
}
使用內(nèi)置的Proxy實現(xiàn)動態(tài)代理有一個問題:被代理的類必須實現(xiàn)接口,未實現(xiàn)接口則沒辦法完成動態(tài)代理生逸。
如果項目中有些類沒有實現(xiàn)接口牢屋,則不應該為了實現(xiàn)動態(tài)代理而刻意去抽出一些沒有實例意義的接口,通過cglib可以解決該問題槽袄。
4.1.3 CGLIB動態(tài)代理
CGLIB(Code Generation Library)是一個開源項目,是一個強大的烙无,高性能,高質(zhì)量的Code生成類庫遍尺,它可以在運行期擴展Java類與實現(xiàn)Java接口截酷,通俗說cglib可以在運行時動態(tài)生成字節(jié)碼。
1乾戏、引用 cglib jar 包迂苛,通過maven或者直接下載 jar 包添加
下載地址: http://download.csdn.net/download/javawebxy/6849703
maven:<dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>2.2.2</version> </dependency>
2、修改 攔截器類
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/*
* 動態(tài)代理類
* 實現(xiàn)了一個方法攔截器接口
*/
public class MyInterceptor implements MethodInterceptor {
// 被代理對象
private Object target;
private Transaction transaction;
public MyInterceptor(Transaction transaction) {
this.transaction = transaction;
}
//動態(tài)生成一個新的類鼓择,使用父類的無參構(gòu)造方法創(chuàng)建一個指定了特定回調(diào)的代理實例
public Object getProxyObject(Object object) {
this.target = object;
// 代碼增強類
Enhancer enhancer = new Enhancer();
// 回調(diào)方法
enhancer.setCallback(this); // 參數(shù)就是攔截器
//設(shè)置生成類的父類類型
enhancer.setSuperclass(target.getClass());// 生成的代理類父類是目標類
//動態(tài)生成字節(jié)碼并返回代理對象
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
this.transaction.beginTransaction();
method.invoke(target, objects);
this.transaction.commitTransaction();
return null;
}
}
3三幻、測試
/**
* 通過 CGLIB 產(chǎn)生的代理類是目標類的子類
*/
public class CGLIBProxyTest {
@Test
public void testCGLIB(){
Object target = new UserDaoImpl();
Transaction transaction = new Transaction();
MyInterceptor interceptor = new MyInterceptor(transaction);
UserDaoImpl dao = (UserDaoImpl) interceptor.getProxyObject(target);
dao.insertUser();
//另一個被代理的對象,不再需要重新編輯代理代碼
PersonDaoImpl personDao = (PersonDaoImpl) interceptor.getProxyObject(new PersonDaoImpl());
personDao.delete();
}
}
4.2 AOP編程
4.2.1概念
使用 JDK 動態(tài)代理的代碼進行對應說明
1. Aspect(切面)
比如說事務、權(quán)限等呐能,與業(yè)務邏輯沒有關(guān)系的部分
2. joinPoint(連接點)
目標類的目標方法念搬。(由客戶端在調(diào)用的時候決定)
3. pointCut(切入點)
所謂切入點是指我們要對那些攔截的方法的定義.
被納入spring aop中的目標類的方法。
4. Advice(通知)
所謂通知是指攔截到j(luò)oinpoint之后所要做的事情就是通知.通知分為前置通知,后置通知,異常通知,最終通知,環(huán)繞通知(切面要完成的功能)
5. Target(目標對象)
代理的目標對象
6. Weaving(織入)
是指把切面應用到目標對象來創(chuàng)建新的代理對象的過程.切面在指定的連接點織入到目標對象
對比表格查看
JDKProxy代理 | SpringAop |
---|---|
目標對象 | 目標對象 Target |
攔截器類 | 切面 Aspect |
攔截器類中的方法 | 通知 Advice |
被攔截到的目標類中方法的集合 | 切入點 pointCut |
在客戶端調(diào)用的方法(目標類目標方法) | 連接點 joinPoint |
代理類 | AOP代理 AOP Proxy |
代理類的代理方法生成的過程 | 織入 Weaving |
4.2.2 Spring AOP實現(xiàn)的兩種模式
4.2.2.1 xml形式
還以事務為例, 準備 Transaction 切面類, 目標接口類 UserDao, 目標類 UserDaoImpl
/**
* 切面
*/
public class Transaction {
public void beginTransaction(){
System.out.println("Begin Transaction");
}
public void commit(){
System.out.println("Commit Transaction");
}
}
- - - - - - - -
public interface UserDao {
void saveUser();
}
- - - - - - - -
public class UserDaoImpl implements UserDao {
@Override
public void saveUser() {
System.out.println("save Person");
}
}
剩余的我們需要配置 appliactionContext.xml
1、引入 aop 命名空間
<?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: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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd" >
2朗徊、配置 兩個 bean
<bean id="userDao" class="com.lanou.spring.aop.transaction.UserDaoImpl"/>
<bean id="transaction" class="com.lanou.spring.aop.transaction.Transaction"/>
3首妖、aop 的配置
<!-- aop 配置-->
<aop:config>
<!-- 切入點表達式, 確認目標類 -->
<aop:pointcut id="pointcut" expression="execution(* com.lanou.spring.aop.transaction.UserDaoImpl.*(..))"/>
<!-- ref 指向的對象就是切面 -->
<aop:aspect ref="transaction">
<!-- 前置通知-->
<aop:before method="beginTransaction" pointcut-ref="pointcut" />
<!-- 正常返回通知-->
<aop:after-returning method="commit" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
4、測試
public class TransactionTest {
@Test
public void testTransaction(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) context.getBean("userDao");
userDao.saveUser();
}
}
5爷恳、報錯, Spring缺少 aspectjweaver.jar 包, 添加 jar 包重新運行
Caused by: java.lang.ClassNotFoundException: org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException
at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
... 70 more
Spring AOP 原理:
- 當spring容器啟動的時候有缆,加載兩個bean,對兩個bean進行實例化
- 當spring容器對配置文件解析到
<aop:config>
的時候 - 把切入點表達式解析出來,按照切入點表達式匹配spring容器內(nèi)容的bean
- 如果匹配成功舌仍,則為該bean創(chuàng)建代理對象
- 當客戶端利用context.getBean獲取一個對象時妒貌,如果該對象有代理對象,則返回代理對象; 如果沒有代理對象铸豁,則返回對象本身
- 切入點表達式如與springbean 沒有一個匹配就會報錯
4.2.2.2 注解形式
- 首先需要導入兩個 jar 包 : aspectjrt & aspectjweaver . 需要注意如果你的 jdk 是1.7 就找這兩個 jar 包的1.7.+版本; 我是jdk1.8, 使用了aspectjrt1.8.5 & aspectjweaver1.8.5
- 在 applicationContext.xml 中添加context, aop 的命名空間.掃描, 添加自動創(chuàng)建代理設(shè)置
<?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/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd" >
<!-- 掃描包 -->
<context:component-scan base-package="com.lanou.transaction"/>
<!-- 自動創(chuàng)建代理 -->
<aop:aspectj-autoproxy/>
</beans>
- 添加 UserDaoImpl, Transaction 的注解
@Repository("userDao")
public class UserDaoImpl implements UserDao {...}
--------
@Component
public class Transaction {...}
- 在 Transaction 切面類中添加注解
@Component
@Aspect
public class Transaction {
@Pointcut("execution(* com.lanou.transaction.UserDaoImpl.*(..))")
private void method(){} //方法簽名
@Before("method()")
public void beginTransaction(JoinPoint joinpoint){
System.out.println("Begin Transaction");
}
@AfterReturning(value = "method()", returning = "returnVal")
public void commit(JoinPoint joinpoint, Object returnVal)
{
System.out.println("返回值: " + returnVal);
System.out.println("Commit Transaction");
}
@After("method()")
public void finallyMethod(JoinPoint joinpoint){
System.out.println("Finally Method");
}
@AfterThrowing(value = "method()", throwing = "throwable")
public void throwingMethod(JoinPoint joinpoint, Throwable throwable){
System.out.println("異常: " + throwable.getMessage());
}
}
4.2.3 切入點表達式
execution(public * *(..)) : 任意公共方法的執(zhí)行
execution(* set*(..)) : 任何一個名字以 set 開始的方法的執(zhí)行
execution(* com.lanou.spring.aop.transaction.service.*.*(..)) : 在 service 包中定義的任意方法的執(zhí)行
execution(* com.lanou.spring.transaction.service..*.*(..)) : 在 service 包或其子包中定義的任意方法的執(zhí)行
execution(* com.lanou.spring.aop..service..*.*(..)) : 在 aop 包及子包一直到 service 包,再子包下的所有類所有方法
4.2.4 通知
4.2.4.1 通知種類
名稱 | 解釋 | 使用 |
---|---|---|
前置通知 [Before advice] |
在連接點前面執(zhí)行灌曙,前置通知不會影響連接點的執(zhí)行, 除非此處拋出異常 |
<aop:before method="before" pointcut-ref="pointcut"/> |
正常返回通知 [After returning advice] |
在連接點正常執(zhí)行完成后執(zhí)行节芥, 如果連接點拋出異常在刺,則不會執(zhí)行 |
<aop:after-returning method="afterReturning" pointcut-ref="pointcut"/> |
異常返回通知 [After throwing advice] |
在連接點拋出異常后執(zhí)行 | <aop:after-throwing method="afterThrowing" pointcut-ref="pointcut"/> |
返回通知 [After (finally) advice] |
在連接點執(zhí)行完成后執(zhí)行, 不管是正常執(zhí)行完成头镊,還是拋出異常蚣驼,都會執(zhí)行返回通知中的內(nèi)容 |
<aop:after method="after" pointcut-ref="pointcut"/> |
環(huán)繞通知 [Around advice] |
環(huán)繞通知圍繞在連接點前后,比如一個方法調(diào)用的前后相艇。 這是最強大的通知類型颖杏,能在方法調(diào)用前后自定義一些操作。 環(huán)繞通知還需要負責決定目標方法的執(zhí)行 |
<aop:around method="around" pointcut-ref="pointcut"/> |
4.2.4.2 通知使用細節(jié)
- 前置通知: 每種通知都能夠添加連接點參數(shù), 可以獲取連接點信息
/*
前置通知
1.在目標方法執(zhí)行之前執(zhí)行
2.獲取不到目標方法返回值
*/
public void beginTransaction(JoinPoint joinpoint){
System.out.println("連接點名稱: "+joinpoint.getSignature().getName());
System.out.println("目標類" + joinpoint.getTarget().getClass());
System.out.println("Begin Transaction");
}
- 后置通知可以獲取返回值類型, 但當目標方法產(chǎn)生異常, 后置通知將不再執(zhí)行
public class UserDaoImpl implements UserDao {
@Override
public String saveUser() {
/* 制造異常
int a = 1/0;
*/
System.out.println("save User");
return "11111111";
}
}
<!-- 正常返回通知
1. 可以獲取目標方法的返回值
2. 當目標方法產(chǎn)生異常, 后置通知將不再執(zhí)行
-->
<aop:after-returning method="commit" pointcut-ref="pointcut" returning="returnVal"/>
</aop:aspect>
/*
后置通知, 在目標方法執(zhí)行之后執(zhí)行, 返回值參數(shù)的名稱與 xml 中保護一致
*/
public void commit(JoinPoint joinpoint, Object returnVal){
System.out.println("目標方法返回值: " + returnVal);
System.out.println("Commit Transaction");
}
- 最終通知
/*
最終通知
無論目標方法是否發(fā)出異常都將執(zhí)行
*/
public void finallyMethod(JoinPoint joinpoint){
System.out.println("Finally Method");
}
<!-- 最終通知 -->
<aop:after method="finallyMethod" pointcut-ref="pointcut"/>
- 異常通知
/*
異常通知
接受目標方法拋出的異常
*/
public void throwingMethod(JoinPoint joinPoint, Throwable throwable){
System.out.println("異常: " + throwable.getMessage());
}
<!--異常通知-->
<aop:after-throwing method="throwingMethod" pointcut-ref="pointcut" throwing="throwable"/>
- 環(huán)繞通知
/*
環(huán)繞通知
ProceedingJoinPoint: 子接口
控制目標方法的執(zhí)行
前置通知和后置通知也能在目標方法的前后添加內(nèi)容,但是不能控制目標方法的執(zhí)行
*/
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("before..before..before");
joinPoint.proceed(); // 調(diào)用目標方法
System.out.println("after..after.after");
}
<!-- 環(huán)繞通知 -->
<aop:around method="around" pointcut-ref="pointcut"/>
</aop:aspect>
4.2.5 SpringAOP 細節(jié)
- 如果目標類實現(xiàn)了接口, 則采用 JDKProxy; 如果沒有實現(xiàn)接口, 采用 CGLIBProxy [Spring 內(nèi)部做的]
- 目標類實現(xiàn)了接口, 但還想要采用 CGLIBProxy, 作如下更改:
Spring4.3.8學習之 與 Struts2 整合[四]
Spring4.3.8學習之與Hibernate4 整合[五]
Spring4.3.8學習之S2SH 整合[六]