一、代理
Aop底層使用代理實現
1、代理作用
可以隱藏代理目標類的具體實現
在不修改目標類代碼情況下對目標進行增強
2、分類
分為靜態代理與動態代理
3、靜態代理實現過程
定義:若代理類在目標運行前就已經實現,那麼這種代理方式就是靜態代理,這種情況的代理方法通常是我們在java代碼中定義的。通常情況下,代理類與目標類實現同一介面,或是派生自同一父類
實現步驟:
//主業務介面 public interface SomeService { String doSome(); }
創建目標類實現主業務介面(SerivceImpl)
//目標類 public class SomeServiceImpl implements SomeService {
@Override
public String doSome() {
return "abc";
} }
創建代理類(增強目標類)實現主業務介面(ServicePorxy)
//靜態代理類,要求與目標類實現相同介面
public class ServiceProxy implements SomeService {
SomeService target;
public ServiceProxy() {
super();
}
public ServiceProxy(SomeService target) {
super();
this.target = target;
}
@Override
public String doSome() {
return target.doSome().toUpperCase();
}
}
//測試類
public class SomeTest {
public static void main(String[] args) {
//定義目標對象
SomeService target = new SomeServiceImpl();
//定義目標對象的代理對象
SomeService proxy = new ServiceProxy(target);//增強目標類方法
String result = proxy.doSome();
System.out.println(result);
}
}
4、動態代理
定義:代理類在程序運行時創建的代理方式被稱為動態代理。也就是說,這種情況下,代理類並不是在ava代碼中定義的,而是運行時根據Java代碼「指示」動態生成的。
分類:
JDK動態代理:如果目標對象實現介面,採用jdk動態代理
CGLIB動態代理:如果目標對象沒有實現介面,必須採用jdk動態代理
JDK動態代理實現
//測試類
public class SomeTest {
public static void main(String[] args) {
//定義目標對象
SomeService target = new SomeServiceImpl();
//定義目標對象的代理對象
SomeService proxy = (SomeService) Proxy.newProxyInstance(
target.getClass().getClassLoader(),//目標類的類載入器
target.getClass().getInterfaces(),//目標類實現所有介面
new InvocationHandler() {//調用處理器
//proxy:代理對象
//method:目標方法
//args:目標方法參數
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
String result = (String) method.invoke(target, args);
return result.toUpperCase();
}
});
String result1 = proxy.doSome();
System.out.println(result1);
}
}
CGLIB動態代理實現()
先導包,把以前的包刪掉只導入:這個包
cglib-nodep-3.1.jar
//Cglib動態代理工廠
public class CglibProxyFactory implements MethodInterceptor {
private SomeServiceImpl target;
public CglibProxyFactory() {
super();
// TODO 自動生成的構造函數存根
}
public CglibProxyFactory(SomeServiceImpl target) {
super();
this.target = target;
}
//創建 Cglib動態代理對象的方法
public SomeServiceImpl proxycCreator(){
//創建增強器
Enhancer en = new Enhancer();
//指定父類,指定目標類
en.setSuperclass(SomeServiceImpl.class);
//指定回調對象
en.setCallback(this);
//從當前代理對象
return (SomeServiceImpl) en.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
String result = (String) method.invoke(target, args);
return result.toUpperCase();
}
}
//目標類
public class SomeServiceImpl {
public SomeServiceImpl() {
System.out.println("初始化");
}
public String doSome() {
return "abc";
}
//測試類
public class SomeTest {
public static void main(String[] args) {
//定義目標對象
SomeServiceImpl target = new SomeServiceImpl();
//定義目標對象的代理對象
SomeServiceImpl proxy = new CglibProxyFactory(target).proxycCreator();
String result1 = proxy.doSome();
System.out.println(result1);
}
}
二、Aop
面向切面編程:
就是將交叉業務邏輯封裝成切面 ,利用Aop功能將切面織入 到主業務邏輯中
所謂交叉業務邏輯就是:通用的、與主業務邏輯無關的代碼,如:安全檢查,日誌、事務
2.作用:
若不使用Aop則會造成交叉業務邏輯與主業務邏輯混合在一起,這樣會是的主業務邏輯
變得混雜不清,並且不容易後期維護。
3.基本術語介紹
切面:泛指交叉業務邏輯,如事務處理,日誌處理都可以理解為切面,常用的切面有通知 與顧問 。實際就是對主業務邏輯的一種增強
織入:指將代碼插入到目標對象的過程
連接點:指切面可以織入的位置
切入點:指切面具體織入的位置
通知:通知是切面的一種實現,可以完成簡單的織入功能(織入就是在這裡實現的),通知定義了增強代碼切入目標代碼的時間點,是目標方法執行之前執行,還是之後執行,通知類型不同,切入時間也不同,
顧問:顧問是另一種切面實現,能夠將通知以更為複雜的方式織入到目標對象中,是將通知裝配為更複雜切面的裝配器,不僅可以指定切入時間,還可以具體指定切入點
4.通知的用法步驟 (基於Schema-based方式)
定義目標類
定義通知類
.註冊目標類
註冊通知切面
註冊代理工廠Bean類對象ProxyFactoryBean
客戶端訪問動態代理對象
5.前置通知:
執行的時機點是在目標方法執行之前執行,
1、創建通知類
//切面前置通知
public class MyMethodBeforeAdvice implements MethodBeforeAdvice {
/**
* method 目標方法
* args 目標方法
* target 目標對象
*/
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前置通知的before()方法執行!");
}
}
2、在實現類中添加方法
public class SomeServiceImpl implements SomeService {
public SomeServiceImpl() {
System.out.println("初始化");
}
@Override
public void doSome() {
System.out.println("doSome執行。。。");
}
@Override
public String doOther() {
System.out.println("doOther執行。。。");
return "love";
}
}
3、applicationContext.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"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 註冊目標類 -->
<bean id="someServiceImpl" class="com.sxt.service.impl.SomeServiceImpl"></bean>
<!-- 註冊切面,前置通知 -->
<bean id="myMethodBeforeAdvice" class="com.sxt.aspects.MyMethodBeforeAdvice"></bean>
<!-- 註冊代理 -->
<bean id="proxyFactoryBean" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定目標對象 -->
<property name="target" ref="someServiceImpl"></property>
<!-- 指定目標類實現所有介面 -->
<property name="interfaces" value="com.sxt.service.SomeService"></property>
<!-- 制定切面 -->
<property name="interceptorNames" value="myMethodBeforeAdvice"></property>
</bean>
</beans>
4、測試類
public class SomeTest {
@Test
public void test2(){
//創建容器對象,ApplicationContext容器初始化時,所有容器的bean創建完成
//缺點:佔用內存
ApplicationContext cs =
new ClassPathXmlApplicationContext("applicationContext.xml");
//從容器中獲取bean
SomeService service = cs.getBean("proxyFactoryBean", SomeService.class);
service.doSome();
String result = service.doOther();
System.out.println(result);
}
}
5、結果
後置通知:
執行的時機點是在目標方法執行之後執行,
1、創建通知類
//切面,後置通知
public class MyAfterReturningAdvice implements AfterReturningAdvice {
/**
* returnValue 目標方法返回值
*/
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("後置通知的afterReturning()方法執行!returnValue:"+returnValue);
}
}
2、實現類中方法與前置一樣
3、在applicationContext.xml添加後置通知bean標籤
<!-- 註冊切面,後置通知 -->
<bean id="myAfterReturningAdvice" class="com.sxt.aspects.MyAfterReturningAdvice"></bean>
並且
<!-- 指定切面 -->
<property name="interceptorNames" value="myAfterReturningAdvice"></property>
4、測試類:與前置一樣
5、結果
6、目標返回值可以編譯(變成大寫)
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
System.out.println("後置通知的afterReturning()方法執行!returnValue:"+returnValue);
if(returnValue!=null){
System.out.println("後置通知的afterReturning()方法執行!returnValue:"+((String) returnValue).toUpperCase());
}
}
7、結果
環繞通知:
執行的時機點是在目標方法執行之前之後都有執行
創建通知類
//切面,環繞通知
public class MyMethodInterceptor implements MethodInterceptor {
/**
* invoction方法調用器
*/
@Override
public Object invoke(MethodInvocation invoction) throws Throwable {
System.out.println("環繞通知,目標方法執行之前!");
//調用執行目標方法
Object result = invoction.proceed();
if(result!=null){
result = ((String)result).toUpperCase();
}
System.out.println("環繞通知,目標方法執行之後!");
return result;
}
}
在applicationContext.xml添加環繞通知bean標籤
<!-- 註冊切面,環繞通知 -->
<bean id="myMethodInterceptor" class="com.sxt.aspects.MyMethodInterceptor"></bean>
<!-- 指定切面,放到代理中-->
<property name="interceptorNames" value="myMethodInterceptor"></property>
實現類,創造異常
測試類:與前置一樣
結果
異常通知:
創建通知類
//通知類
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Exception ex){
System.out.println("異常通知執行!");
}
}
在applicationContext.xml添加異常通知bean標籤
<!-- 註冊切面,異常通知 -->
<bean id="myThrowsAdvice" class="com.sxt.aspects.MyThrowsAdvice"></bean>
<!-- 指定切面,放到代理中-->
<property name="interceptorNames" value="myThrowsAdvice"></property>
測試類:與前置一樣
結果
三、基於AspectJ方式
AspectJ對AOP的實現 對於AOP這種編程思想,
很多框架都進行了實現。Spring就是其中之一,可 以完成面向切面編程。然而,AspectJ也實現了AOP的功能,且其實現方式更為簡捷, 使用更為方便,而且還支持註解式開發。所以,Spring又將AspectJ的對於AOP的實 現也引入到了自己的框架中。
在Spring中使用AOP開發時,一般使AspectJ的實現方式。
AspectJ的通知類型 AspectJ中常用的通知有五種類型:
前置通知
後置通知
環繞通知
異常通知
最終通知:無論程序執行是否正常,該通知都會執行。類似於 try..catch中finally代碼塊
AspectJ切入點表達式
execution(
[Modifiers-pattern] 訪問許可權類型
ret-type-pattern 返回值類型
[Declaring-type-pattern] 全限定性類名
name-pattern 方法名(參數名)
[Throws-pattern] 拋出異常類型
)
execution(* *..service.*.*(..)) 第一個*代表返回值類型,第二個*..代表多級包 ,service代表子包,第三個.*代表類名/介面名,第四個.*代表方法名,最後(..)代表方法參數
指定所有包下的serivce子包下所有類(介面)中的所有方法為切入點
搭建AspectJ開發環境
導入價包
AspectJ對Aop實現有兩種方式
註解方式
前置通知:
執行的時機點是在目標方法執行之前執行,
創建通知類
//切面
@Aspect //表明當前類是一個切面
public class MyAspects {
//該註解表明該方法是前置通知
@Before("execution(* *..service.*.doSome(..))")
public void before(){
System.out.println("前置通知方式執行。。。");
}
}
實現類
public class SomeServiceImpl implements SomeService {
public SomeServiceImpl() {
System.out.println("初始化");
}
@Override
public void doSome() {
//System.out.println("doSome執行。。。"+1/0);
System.out.println("doSome執行。。。");
}
@Override
public String doOther() {
System.out.println("doOther執行。。。");
return "love";
}
}
applicationContext.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: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.xsd">
<!-- 註冊目標類 -->
<bean id="someServiceImpl" class="com.sxt.service.impl.SomeServiceImpl"></bean>
<!-- 註冊切面,前置通知 -->
<bean id="myAspects" class="com.sxt.aspects.MyAspects"></bean>
<!-- 註冊自動代理 -->
<aop:aspectj-autoproxy/>
</beans>
測試類
@Test
public void test(){
//創建容器對象,ApplicationContext容器初始化時,所有容器的bean創建完成
//缺點:佔用內存
ApplicationContext cs =
new ClassPathXmlApplicationContext("applicationContext.xml");
//從容器中獲取bean
SomeService service = cs.getBean("someServiceImpl", SomeService.class);
service.doSome();
String result = service.doOther();
System.out.println(result);
}
}
結果
2、後置通知
執行的時機點是在目標方法執行之前執行,
創建通知類
//該註解表明該方法是後置通知
@AfterReturning("execution(* *..service.*.doSome(..))")
public void afterReturning(){
System.out.println("後置通知方式執行。。。");
}
實現類、applicationContext.xml、測試類 不變
結果
3、環繞通知
執行的時機點是在目標方法執行之前與之後都會執行
創建通知類
//該註解表明該方法是環繞通知
@Around("execution(* *..service.*.doOther(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("環繞通知方式之前。。。");
String result = (String)pjp.proceed();
if(result!=null){
result = result.toUpperCase();
}
System.out.println("環繞通知方式之hou。。。");
return result;
}
}
實現類、applicationContext.xml、測試類 不變
結果
4、異常通知
創建通知類
//該註解表明該方法是環繞通知
@AfterThrowing("execution(* *..service.*.doSome(..))")
public void throwing(){
System.out.println("異常方法執行。。。");
}
實現類
public void doSome() {
System.out.println("doSome執行。。。"+1/0);
//System.out.println("doSome執行。。。");
}
applicationContext.xml、測試類 不變
結果
獲取異常信息需要 throwing
//該註解表明該方法是異常通知
@AfterThrowing(value="execution(* *..service.*.doSome(..))",throwing="ex")
public void throwing(Exception ex){
System.out.println("異常方法執行,異常信息為"+ex);
}
}
結果
5、最終通知:
無論程序執行是否正常,該通知都會執行。類似於 try..catch中finally代碼塊
創建通知類
//該註解表明該方法是最終通知
@After("execution(* *..service.*.doSome(..))")
public void after(){
System.out.println("最終方法執行");
}
實現類
public void doSome() {
System.out.println("doSome執行。。。"+1/0);
//System.out.println("doSome執行。。。");
}
applicationContext.xml、測試類 不變
結果
7、配置方式
前置通知
創建通知類
public void before(){
System.out.println("前置通知方式執行。。。");
}
applicationContext.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: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.xsd">
<!-- 註冊目標類 -->
<bean id="someServiceImpl" class="com.sxt.service.impl.SomeServiceImpl"></bean>
<!-- 註冊切面,通知 -->
<bean id="myAspects" class="com.sxt.aspects.MyAspects"></bean>
<!-- Aop配置 -->
<aop:config>
<!-- 定義切入點 -->
<aop:pointcut expression="execution(* *..service.*.doSome(..))" id="doSomePC"/>
<aop:pointcut expression="execution(* *..service.*.doOther(..))" id="doOtherPc"/>
<aop:aspect ref="myAspects">
<aop:before method="before" pointcut-ref="doSomePC"/>
</aop:aspect>
</aop:config>
</beans>
實現類測試類 不變
結果
前置通知(帶參)
創建通知類
public void before(JoinPoint jp){
System.out.println("前置通知方式執行。。。jp="+jp);
}
修改applicationContext.xml
<aop:aspect ref="myAspects">
<!-- <aop:before method="before" pointcut-ref="doSomePC"/> -->
<aop:before method="before(org.aspectj.lang.JoinPoint)" pointcut-ref="doSomePC"/>
</aop:aspect>
結果
後置通知(帶參)
創建通知類
public void afterReturning(Object result){
System.out.println("後置通知方式執行。。。返回值"+result);
}
修改applicationContext.xml
<aop:aspect ref="myAspects">
<aop:after-returning method="afterReturning(java.lang.Object)" pointcut-ref="doOtherPc" returning="result"/>
</aop:aspect>
結果
環繞通知
創建通知類
public Object around(ProceedingJoinPoint pjp) throws Throwable{
System.out.println("環繞通知方式之前。。。");
String result = (String)pjp.proceed();
if(result!=null){
result = result.toUpperCase();
}
System.out.println("環繞通知方式之後。。。");
return result;
修改applicationContext.xml
<aop:aspect ref="myAspects">
<aop:around method="around" pointcut-ref="doOtherPc"/>
</aop:aspect>
結果
異常通知
創建通知類
public void throwing(Exception ex){
System.out.println("異常方法執行,異常信息為"+ex);
}
修改applicationContext.xml
<aop:aspect ref="myAspects">
<aop:after-throwing method="throwing(java.lang.Exception)" pointcut-ref="doSomePC" throwing="ex"/>
</aop:aspect>
結果
最終通知
(有異常)
創建通知類
public void after(){
System.out.println("最終方法執行");
實現類
public SomeServiceImpl() {
System.out.println("初始化");
}
@Override
public void doSome() {
System.out.println("doSome執行。。。"+1/0);
//System.out.println("doSome執行。。。");
}
@Override
public String doOther() {
System.out.println("doOther執行。。。");
return "love";
}
修改applicationContext.xml
<aop:aspect ref="myAspects">
<aop:after method="after" pointcut-ref="doSomePC"/>
</aop:aspect>
結果
(無異常)
實現類
public SomeServiceImpl() {
System.out.println("初始化");
}
@Override
public void doSome() {
//System.out.println("doSome執行。。。"+1/0);
System.out.println("doSome執行。。。");
}
@Override
public String doOther() {
System.out.println("doOther執行。。。");
2.結果
推薦閱讀: