一. AOP 概念
二. AOP 實現方式
2.1 利用 Proxy 實現 AOP功能
2.2 利用 CGLib 實現 AOP功能
2.3 利用 Spring 註解方式 實現 AOP功能
2.4 利用 Spring XML 文件配置方式實現 AOP功能
一. AOP概念
- 對函數調用進行日誌記錄。
- 監控部分函數,如果拋出異常那麼可以以短信或者郵件通知別人。
- 監控重要函數的運行時間。
二. AOP實現方式
2.1 用Proxy類實現AOP功能
AOP大都可以用在權限系統中,那麼利用Proxy類實現主函數調用Student類的print()方法,打印出”hello
world!”。若主函數創建的Student實例有名字,則可以打印,沒有名字,則不可以打印。
一般來說,我們可以直接在print()方法中加入if 語句來進行判斷,但是這樣一個是代碼量大,一個是不靈活,況且,假如判斷條件需要修改的話,還得找回這個類,不方便。對於採用Proxy類方法,主函數 -->代理-->目標對象的方法。這樣,以後不管是修改判斷條件,還是查找等,可以直接在代理類中進行處理。
對於Proxy類有一個使用前提,就是目標對象必須要實現接口。否則,不能使用這個方法。
過程如下:
1. 創建Student類的接口:
public interface StudentInterface {
public void print();
}
public class StudentBean implements StudentInterface{
private String name;
public StudentBean(){}
public StudentBean(String name){this.name = name;}
public void print(){
System.out.println("Hello World!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class ProxyFactory implements InvocationHandler {
private Object stu;
public Object createStudentProxy(Object stu){
this.stu = stu;
return Proxy.newProxyInstance(stu.getClass().getClassLoader(),
stu.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
StudentBean s = (StudentBean)stu;
Object object = null;
if(s.getName() != null)
object = method.invoke(stu, args);
else
System.out.println("名字爲空,代理類已經攔截!");
return object;
}
}
解釋:
- 首先調用代理工廠的createStudentProxy(Object stu)創建StudentBean類的代理類.
- 在該方法內,調用Proxy.newProxyInstance()方法創建代理對象。第一個參數是目標對象的類加載器,第二個參數是目標對象實現的接口,第三個參數傳入一個InvocationHandler實例,該參數和回調有關係。
- 每當調用目標對象的方法的時候,就會回調該InvocationHandler實例的方法,也就是public Object invoke()方法,我們就可以把限制的條件放在這裏,條件符合的時候,就可以調用method.invoke()方法真正的調用目標對象的方法,否則,則可以在這裏過濾掉不符合條件的調用。
Proxy實現AOP功能總結:
- 目標對象必須實現接口。
- 調用Proxy.newProxyInstance()方法,返回創建的代理對象。
- 由於該方法需要一個實現了InvocationHandler接口的對象,所以我們還要重寫該接口的invoke()方法。
- 我們的限制條件就可以放在這個invoke()方法中,當滿足條件,就調用method.invoke()真正的調用目標對象的方法,否則,不做任何事情,直接過濾。
2.2 利用CGlib實現AOP功能
還是實現主函數調用Student類的print()方法,打印出”hello world!”。若主函數創建的Student實例有名字,則可以打印,沒有名字,則不可以打印。但是這個時候Student類不再實現接口,不像利用 Proxy那樣的實現接口。這時候使用第三方框架CGLib。
具體實現:
1. 引入CGlib的jar包。
2. 創建StudentBean類:
public class StudentBean {
private String name;
public StudentBean(){}
public StudentBean(String name){this.name = name;}
public void print(){
System.out.println(name +" hello world!");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class CGlibProxyFactory implements MethodInterceptor{
private Object object;
public Object createStudent(Object object){
this.object = object;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(object.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodProxy) throws Throwable {
StudentBean stu = (StudentBean)object;
Object result = null;
if(stu.getName() != null)
result = methodProxy.invoke(object, args);
else
System.out.println("方法已經被攔截...");
return result;
}
}
總體來說,使用CGlib的方法和使用Proxy的方法差不多,只是Proxy創建出來的代理對象和目標對象都實現了同一個接口。而CGlib的方法則是直接繼承了目標對象。
4. 創建主類
public class Main {
public static void main(String[] args) {
StudentBean stu1 = (StudentBean)(new CGlibProxyFactory().createStudent(new StudentBean()));
StudentBean stu2 = (StudentBean)(new CGlibProxyFactory().createStudent(new StudentBean("whc")));
stu1.print();
stu2.print();
}
}
前提:介紹spring註解方法的話先介紹一些概念:
在上面的例子中,我們可以在proxy方式中重寫invoke方法,也可以在cglib方式中重寫intercept方法,並且在 裏面調用invoke方法來實際調用目標方法。
那麼我們可以在這個invoke()方法的前前後後加入我們的一些方法,比如在調用實際方法前,調用一個我 們自定義的方法,在調用實際方法後,也調用我們的一個自定義方法,那麼這些就分別叫做前置通知,後置通知,除此之外,還有例外通知,最終通知,整個這個重 寫方法,就叫做環繞通知。整個這個處理類,就叫做切面。(Ps:不太清楚的先搞清楚AOP的一些概念)
1. 引入spring的jar包:
有aspectjweaver-1.6.11.M2.jar,cglib.nodep-2.1.3.jar,spring.jar,aspectjrt.jar,common-annotations.jar,commons-logging-1.1.1.jar包。2. 配置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-3.1.xsd">
<aop:aspectj-autoproxy/>
<bean id = "stuInterceptor" class = "com.springaop.test.StuInterceptor"></bean>
<bean id = "stu" class = "com.springaop.test.Student"></bean>
</beans>
public class Student {
public String print(String name){
System.out.println("print() method:" + name);
return "hello";
}
}
@Aspect
public class StuInterceptor {
/**
* 打印方法AOP
*/
@Pointcut("execution(* com.springaop.test.Student.print(..))")
// @Pointcut("execution(* com.springaop.test.Student.*(..))")
public void printMethod(){}
@Before("printMethod()")
public void printBeforeAdvice(){
System.out.println("printBeforeAdvice()!");
}
@AfterReturning(pointcut="printMethod()",returning="flag")
public void printAfterAdvice(String flag){
System.out.println("printAfterAdvice()! " + flag);
}
@After("printMethod()")
public void finallyAdvice(){
System.out.println("finallyAdvice()!");
}
@Around("printMethod() && args(name)")
public Object printAroundAdvice(ProceedingJoinPoint pjp,String name) throws Throwable{
Object result = null;
if(name.equals("whc"))
pjp.proceed();
else
System.out.println("print()方法以及被攔截...");
return result;
}
}
解釋:
- 聲明該類是切面,在類前使用@Aspect
- 聲明切入點:
@Pointcut("execution(* com.springaop.test.Student.print(..))") // @Pointcut("execution(*com.springaop.test.Student.*(..))")
- 兩種方式都可以。其中第一個*表示返回值可以爲任意類型,com.springaop.test.Student.print表示指定的方法。com.springaop.test.Student.*表示該類中的任意方法。(..)表示任意參數。
- 前置通知:@Before("printMethod()"),裏面的"printMethod()"即之前定義的切入點方法。
- 後置通知:@AfterReturning(pointcut="printMethod()",returning="flag")可以得到方法的返回值,放在flag中,flag要和後置通知的方法參數對應。
- 最終通知:@After("printMethod()")
- 環繞通知:@Around("printMethod() && args(name)"),可以傳入參數。一般我們的權限條件就在這裏寫。如下環繞通知:
public Object printAroundAdvice(ProceedingJoinPoint pjp,String name) throws Throwable{}
必須傳入一個ProceedingJoinPoint類型的參數,符合條件則可以直接調用pjp.proceed();就可以調 用目標對象的方法。如果不符合條件,則不調用pjp.proceed(); - 把切面配置在XMl文件中:如上配置。
- 在主函數中,創建Spring容器,即通過getBean方式得到實例對象,調用其方法即可。
public class Main { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml"); Student stu = (Student)ctx.getBean("stu"); stu.print("whc"); } }
輸出結果:
printBeforeAdvice()!
print() method:whc
printAfterAdvice()! hello
finallyAdvice()!
2.4 利用springXML文件配置方式實現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-3.1.xsd">
<aop:aspectj-autoproxy/>
<bean id = "stu" class = "com.springaop.test.Student"></bean>
<bean id = "interceptor" class = "com.springaop.test.StuInterceptor"></bean>
<aop:config>
<aop:aspect id = "stuInterceptor" ref = "interceptor">
<aop:pointcut id="mycut" expression="execution(* com.springaop.test.Student.print(..))"/>
<aop:before pointcut-ref="mycut" method="printBeforeAdvice" />
<aop:after-returning pointcut-ref="mycut" method="printAfterAdvice"/>
<aop:after pointcut-ref="mycut" method="finallyAdvice"/>
<aop:around pointcut="mycut" method="printAroundAdvice"/>
</aop:aspect>
</aop:config>
</beans>
- 首先可以在切面的類把註釋去掉,因爲我們不再使用註釋的方法。
- 然後再上面的配置文件中,我們定義了<aop:config>,進一步在裏面配置了切面,切入點,前置,後置通知等等一系列的東西。
這裏有一個問題就是如何傳入參數的問題,比如我要爲環繞方法傳入一個name參數。那麼,該如何在這裏配置?一個解決辦法就是在上述配置中不要如下代碼:
<aop:pointcut id="mycut" expression="execution(* com.springaop.test.Student.print(..))"/>
<aop:before pointcut="execution(* com.springaop.test.Student.print(..))" method="printBeforeAdvice" />
<aop:around pointcut="execution(* com.springaop.test.Student.print(..)) and args(name)" method="printAroundAdvice" arg-names(name)/
總結:
- AOP概念:面向切面編程;能夠在方法的前後執行某些代碼,達到控制的目的。
- 實現方法有幾種。1. Proxy 類方式(需要目標對象實現某一接口。目的就是 代理類也實現該接口,使得代理類能夠完全代理該對象) 2. CGlib 方式(此時的代理方式爲代理類繼承代理對象,以此達到代理類和代理對象一致的目的) 3. Spring 註解方式 4. Spring XML 文件配置的方式。