前言:
上一篇簡單使用反射和jdk的動態代理模擬了AOP工作原理。在這里將講闡述AOP如何把代碼織入到目標對象的方法上。
一:這里介紹一下AOP一些名詞概念
(1)Aspect(切面):通常是一個類,里面可以定義切入點和通知。
(2)JointPoint(連接點):程序執行過程中明確的點,一般是方法的調用。這就是目標對象的方法。
(3)Advice(通知):AOP在特定的切入點上執行的增強處理,有before,after,afterReturning,
afterThrowing,around。這些就是需要織入到連接點中的代碼。
(4)Pointcut(切入點):就是帶有通知的連接點,在程序中主要體現為書寫切入點表達式
(5)AOP代理:AOP框架創建的對象,代理就是目標對象的加強。Spring中的AOP代理可以使JDK動態代理,
也可以是CGLIB代理,前者基於接口,后者基於子類
(6)advisor:增強器,用來篩選類中的哪些方法是我們的連接點(哪些方法需要被攔截).
(7)wave:織入,把切面/切面類和目標類的動態接入。
二:Advice(通知)
AOP的通知有before前置通知。after后置通知,無論是否成功返回都會執行。afterReturning后置通知,只有成功返回后才會執行。
afterThrowing拋出異常通知,顧名思義,只有拋出異常的時候才會執行。around環繞通知,簡單來說就是集合了before和after的
功能。
這里基於xml文件實現把切面類和目標類動態接入。
(1)pojo類:Account.java
package com.cnblogs.aop.pojo; /** * 簡單模擬銀行賬戶 * */
public class Account { // 卡號
private Long id; // 姓名
private String name; //余額
private double balance; public Account() { super(); // TODO Auto-generated constructor stub
} public Account(Long id, String name, double balance) { super(); this.id = id; this.name = name; this.balance = balance; } // get和set方法 }
(2)目標類:這里使用的是JDK的接口代理模式,所以需要准備一個接口,和接口的實現類,即目標類。
package com.cnblogs.aop.service; public interface BankService { /** * 存錢 * @param money */
public Boolean save(double money) throws Exception; /** * 取錢 * @param money * @return
*/
public boolean withdraw(double money) throws Exception; }
package com.cnblogs.aop.service.impl; import com.cnblogs.aop.pojo.Account; import com.cnblogs.aop.service.BankService; public class BankServiceImpl implements BankService { private Account account; public Account getAccount() { return account; } public void setAccount(Account account) { this.account = account; } @Override public Boolean save(double money) throws Exception { System.out.println("存錢: " + money); account.setBalance(account.getBalance() + money); return true; } @Override public boolean withdraw(double money) throws Exception{ System.out.println("取錢: " + money); if(account.getBalance() < money) { throw new Exception("余額不足"); } account.setBalance(account.getBalance() - money); return true; } }
(3):目標類准備好以后,就要准備切面類了。這個切面類直接集合了所有的通知類型。
package com.cnblogs.aop.aspect; import java.lang.reflect.Method; import java.text.SimpleDateFormat; import java.util.Date; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; import org.springframework.aop.AfterReturningAdvice; import org.springframework.aop.MethodBeforeAdvice; import org.springframework.aop.ThrowsAdvice; /** * 切面類 * 實現了前置通知 MethodBeforeAdvice * 成功返回才執行的后置通知 * 環繞通知 * 拋出異常才會執行的通知 * * */
public class MyLogger implements MethodBeforeAdvice,AfterReturningAdvice, MethodInterceptor,ThrowsAdvice{ /** * 前置通知 * @param method 連接點 * @param args 連接點參數 * @param target 目標對象 */ @Override public void before(Method mehtod, Object[] args, Object target) throws Throwable { // 添加日志功能
System.out.println("前置通知: " + new SimpleDateFormat().format(new Date())); } /** * 后置通知會接收到連接點的返回值,連接點返回值必須為引用類型,否則 * 報錯:org.springframework.aop.AopInvocationException: * Null return value from advice does not match primitive return type for * @param returnValue 連接點的返回值【如果有的話】 * @param method 連接點 * @param args 連接點方法參數 * @param target 目標對象【委托對象】 */ @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { // 添加日志功能
System.out.println("成功返回才執行的后置通知: " + new SimpleDateFormat().format(new Date())); } /** * 環繞通知 * 在調用連接點前為前置通知 * 在調用連接點后為后置通知 */ @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("環繞通知之前置: " +
new SimpleDateFormat().format(new Date())); // 調用連接點
invocation.proceed(); System.out.println("環繞通知之后置: " +
new SimpleDateFormat().format(new Date())); return null; } /** * 拋出異常才會執行的通知比較特殊,它的接口類體為空, * 只起一個標記作用。 * 不過會默認調用afterThrowing方法作為通知。 * 該方法重載,有4個參數的和1個參數的 * 當兩個重載方法都存在時優先調用4個參數的 */
public void afterThrowing(Method method, Object[] args, Object target,Exception e) { System.out.println("拋出異常才會執行的通知(4參): " + e.toString()); } public void afterThrowing(Exception e) { System.out.println("拋出異常才會執行的通知(1參): " + e.toString()); } }
(4):織入
advice.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 銀行賬號 -->
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目標對象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面對象 -->
<bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
</bean>
<!-- 生成代理對象 這里使用的是spring的一個代理對象工廠類產生的 -->
<bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 注入目標對象 -->
<property name="target" ref="service"></property>
<!-- 注入目標對象所實現的接口 可以有多個接口,基於JDK的接口代理 -->
<property name="proxyInterfaces">
<list>
<value>com.cnblogs.aop.service.BankService</value>
</list>
</property>
<!-- 注入advice -->
<property name="interceptorNames">
<list>
<value>log</value>
</list>
</property>
</bean>
</beans>
(5):測試
package com.cnblogs.aop.jtest;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.cnblogs.aop.service.BankService;
@SuppressWarnings("resource")
public class AOPTest {
@Test
public void advice(){
try {
// 獲取springIOC容器
String path = "com/cnblogs/aop/aspect/advice.xml";
ApplicationContext container = new ClassPathXmlApplicationContext(path);
// 從容器中獲取代理對象
BankService proxy = (BankService) container.getBean("proxy");
// 執行目標對象中的方法
// proxy.save(1000.0);
// 測試通知會對那些方法起作用
// proxy.getClass(); // 無
// proxy.toString(); // 有
// 測試拋出異常通知
// proxy.withdraw(5000.0);
} catch (Exception e) {
e.printStackTrace();
}
}
}
1.環繞通知的前置優先前置通知輸出,后置通知優先環繞通知的后置輸出。
環繞通知之前置: 19-5-28 下午7:29
前置通知: 19-5-28 下午7:29
存錢: 1000.0
成功返回才執行的后置通知: 19-5-28 下午7:29
環繞通知之后置: 19-5-28 下午7:29
2.通知對那些方法起作用?
單獨調用getClass()方法時沒有通知輸出,toString()方法通知有輸出。去java.lang.Objcet查找兩個方法,
public final Class<?> getClass(),public String toString().簡單對比發現只要是非final類型修飾的,通過代理對象調用
目標對象方法時都會把通知織入。
有時候我們希望目標對象的某些方法被織入,其它方法不想被織入,當時又不想用final修飾那些不想被織入的方法該
怎么辦?增強器可以起到這個效果,篩選需要的方法織入。
三:Advisor增強器
增強器可以篩選目標類中那些方法被織入。
advisor.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 銀行賬號 -->
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目標對象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面對象 -->
<bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
</bean>
<!-- 配置增強器的bean對象 -->
<bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 注入advice -->
<property name="advice" ref="advice"/>
<!-- 注入需要被攔截的目標對象中的方法(連接點) -->
<property name="patterns">
<list>
<value>.*save</value>
<value>.*withdraw</value>
</list>
</property>
</bean>
<!-- 生成代理對象 這里使用的是spring的一個代理對象工廠類產生的 -->
<bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 注入目標對象 -->
<property name="target" ref="service"></property>
<!-- 注入目標對象所實現的接口 可以有多個接口,基於JDK的接口代理 -->
<property name="proxyInterfaces">
<list>
<value>com.cnblogs.aop.service.BankService</value>
</list>
</property>
<!-- 注入advice -->
<property name="interceptorNames">
<list>
<value>advisor</value>
</list>
</property>
</bean>
</beans>
補充:與advice.xml文件對比,發現只是多了一個org.springframework.aop.support.RegexpMethodPointcutAdvisor類的bean對象,該類就是增強器,
里面需要注入切面類的bean對象,以及指定那些方法被織入。
其次在生成代理對象的bean標簽中,注入advice是注入增強器。
測試:
@Test public void advisor(){ try { // 獲取springIOC容器
String path = "com/cnblogs/aop/advisor/advisor.xml"; ApplicationContext container = new ClassPathXmlApplicationContext(path); // 從容器中獲取代理對象
BankService proxy = (BankService) container.getBean("proxy"); // proxy.toString(); // proxy.save(1000);
proxy.getClass(); } catch (Exception e) { e.printStackTrace(); } }
四:AutoProxy,自動代理
如果目標類不止一個,為多個目標類生成代理對象需要配置多個
<bean name="proxy" class="org.springframework.aop.framework.ProxyFactoryBean">...... </bean>,
自動代理可以用很少的xml配置給目標對象生成代理對象。自動代理用到增強器的功能。
autoProxy.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<!-- 銀行賬號 -->
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目標對象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面對象 -->
<bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
</bean>
<!-- 配置增強器的bean對象 -->
<bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 注入advice -->
<property name="advice" ref="log"/>
<!-- 注入需要被攔截的目標對象中的方法(連接點) -->
<property name="patterns">
<list>
<value>.*save</value>
<value>.*withdraw</value>
</list>
</property>
</bean>
<!-- 配置自動代理對象 -->
<bean name="proxy" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
</bean>
</beans>
1.不能通過proxy拿代理對象,通過目標對象的名字拿代理對象。
2.當前xml文件中一定要有一個增強器advisor,配置自動代理對象不需要注入任何東西。
3.不管目標對象是否實現了一個或多接口,自動代理的方式都能夠為它產生代理對象(CGLib的方式)。
4.如果目標對象有增強器中篩選的方法,將來調用方法的時候會動態織入。
測試:
@Test public void autoProxy(){ try { // 獲取springIOC容器
String path = "com/cnblogs/aop/autoProxy/autoProxy.xml"; ApplicationContext container = new ClassPathXmlApplicationContext(path); BankService target = (BankService) container.getBean("service"); target.save(1000.0); } catch (Exception e) { e.printStackTrace(); } }
結果:
環繞通知之前置: 19-5-29 下午4:22
前置通知: 19-5-29 下午4:22
存錢: 1000.0
成功返回才執行的后置通知: 19-5-29 下午4:22
環繞通知之后置: 19-5-29 下午4:22
2.AutoProxyByName 通過名字進行自動代理
雖然自動代理可以很方便的給xml文件中的目標對象設置對應的代理對象,但是並不是xml文件中的所有對象都是我們的目標對象,
我們更想希望可以進一步篩選出某幾個對象為我們的目標對象。
autoProxyByName.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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!-- 銀行賬號 --> <bean name="account" class="com.cnblogs.aop.pojo.Account"> <property name="id" value="6217" /> <property name="name" value="jack" /> <property name="balance" value="1000.00" /> </bean> <!-- 目標對象 --> <bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl"> <property name="account" ref="account" /> </bean> <!-- 切面對象 --> <bean name="log" class="com.cnblogs.aop.aspect.MyLogger"> </bean> <!-- 配置增強器的bean對象 --> <bean name="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor"> <!-- 注入advice --> <property name="advice" ref="log"/> <!-- 注入需要被攔截的目標對象中的方法(連接點) --> <property name="patterns"> <list> <value>.*save</value> <value>.*withdraw</value> </list> </property> </bean> <!-- 配置代理對象 --> <!-- 這里使用自動代理的方式 autoproxybyname --> <bean name="proxy" class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> <!-- 注入需要被代理的對象名字 --> <property name="beanNames"> <list> <!-- value中填寫bean標簽的id或name屬性值 --> <value>service</value> </list> </property> <!-- 注入advice或者advisor --> <property name="interceptorNames"> <list> <value>advisor</value> </list> </property> </bean> </beans>
使用byName自動代理的時候需要注意的方面:
1.當前的配置里面"有沒有"advisor的配置"都沒關系"
2.不管目標對象是否實現了一個或多接口,自動代理的方式都能夠為它產生代理對象.
3.從spring容器中拿代理對象的時候,需要通過目標對象的名字來拿。
五:使用<aop>標簽完成織入功能
execution(modifiers-pattern ret-type-pattern declaring-type-pattern name-pattern(param-pattern) throws-pattern)
除了返回類型模式(ret-type-pattern),名字模式(name-pattern),參數模式(param-pattern)是必須的,
其它模式都是可選的
ret-type-pattern: ' * '代表匹配任意返回類型,java.lang.String匹配String返回類型。
name-pattern: 名字模式匹配的是方法名。 你可以使用 * 通配符作為所有或者部分命名模式。
param-pattern:參數匹配, ' () '匹配無參,' (*) '匹配一個參數,‘(..)’匹配任意參數(0或多個),
' (*,String) ' 匹配了一個接受兩個參數的方法,第一個可以是任意類型,第二個必須是String類型。
下面給出一些常見切入點表達式的例子。
1)任意包下的任意類中的公共方法的執行:
execution(public * *(..))
2)任何一個以“set”開始的方法的執行:
execution(* set*(..))
3)BankService接口的任意方法的執行:
execution(* com.cnblogs.service.BankService.*(..))
4)定義在service包里的任意方法的執行:
execution(* com.cnblogs.service.*.*(..))
5)定義在service包或者子包里的任意方法的執行:
execution(* com.cnblogs.service..*.*(..))
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: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-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd ">
<!-- 銀行賬號 -->
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目標對象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面對象 -->
<bean name="log" class="com.cnblogs.aop.aspect.MyLogger">
</bean>
<!-- 配置aop的代理 也啟用了自動代理的功能-->
<aop:config>
<!-- 定義一個切入點 -->
<aop:pointcut expression="execution(public * com.cnblogs.aop.service.BankService.save(*))" id="save"/>
<aop:pointcut expression="execution(public * com.cnblogs.aop.service.BankService.withdraw(*))" id="withdraw"/>
<!-- 定義哪一個advice在哪一個切入點上面起作用 -->
<aop:advisor advice-ref="log" pointcut-ref="save" />
<aop:advisor advice-ref="log" pointcut-ref="withdraw" />
</aop:config>
</beans>
注意:
1.從spring容器中拿代理對象的時候也是要用目標對象的名字來拿。
2.沒有實現任何接口的目標對象也能產生代理對象。
測試:
@Test public void aop1(){ try { // 獲取springIOC容器
String path = "com/cnblogs/aop/aopConfig/config1.xml"; ApplicationContext container = new ClassPathXmlApplicationContext(path); BankService target = (BankService) container.getBean("service"); target.save(1000.0); target.withdraw(20000.0); } catch (Exception e) { e.printStackTrace(); } }
環繞通知之前置: 19-5-29 下午7:06
前置通知: 19-5-29 下午7:06
存錢: 1000.0
成功返回才執行的后置通知: 19-5-29 下午7:06
環繞通知之后置: 19-5-29 下午7:06
環繞通知之前置: 19-5-29 下午7:06
前置通知: 19-5-29 下午7:06
取錢: 20000.0
拋出異常才會執行的通知(1參): java.lang.Exception: 余額不足
2.第二種方式使用aop
在一個切面類中定個多個方法,根據xml文件的配置每個方法都可以織入到切入點的不同位置,並且advice是在aop的標簽中進行配置,
不需要再寫對應的advice類了。
切面類:
package com.cnblogs.aop.aopConfig; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; /** * 切面類,這里包含了多個切面方法,可以把每個切面方法織入到不同切入點的不同位置 * */
public class XmlHandler { public void beforeTest(JoinPoint p){ System.out.println(p.getSignature().getName()+" before..."); } public void afterTest(JoinPoint p){ System.out.println(p.getSignature().getName()+" after..."); } public void afterReturningTest(JoinPoint p){ System.out.println(p.getSignature().getName()+" afterReturning"); } //在和aroundAdvice結合的時候,這個方法一定要加上這個ProceedingJoinPoint類型的參數
public Object aroundTest(ProceedingJoinPoint pjp)throws Throwable{ //JoinPoint對象不能調用連接點所表示的方法 //ProceedingJoinPoint能調用連接點所表示的方法 pjp.proceed()
System.out.println(pjp.getSignature().getName()+" is start.."); //調用目標對象中的方法
Object obj = pjp.proceed(); System.out.println(pjp.getSignature().getName()+" is end.."); return obj; } public void throwingTest(JoinPoint p,Exception ex){ System.out.println(p.getSignature().getName()+" is throwing..."+ex.getMessage()); } }
<?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-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd ">
<bean name="account" class="com.cnblogs.aop.pojo.Account">
<property name="id" value="6217" />
<property name="name" value="jack" />
<property name="balance" value="1000.00" />
</bean>
<!-- 目標對象 -->
<bean name="service" class="com.cnblogs.aop.service.impl.BankServiceImpl">
<property name="account" ref="account" />
</bean>
<!-- 切面對象 -->
<bean name="log" class="com.cnblogs.aop.aopConfig.XmlHandler">
</bean>
<!-- 配置aop的代理 -->
<aop:config>
<!-- 定義切入點名為myPointCut -->
<aop:pointcut expression="execution(public * com.cnblogs.aop.service.*.*(..))" id="myPointCut"/>
<!-- 定義切面類 以及需要使用的advice -->
<aop:aspect id="aspect" ref="log">
<!-- before表示會把切面類log中的beforeTest方法織入到名字 叫myPointCut的切入點前面 -->
<aop:before method="beforeTest" pointcut-ref="myPointCut"/>
<!-- after表示不管方法是否正常結束都會起作用 -->
<aop:after method="afterTest" pointcut-ref="myPointCut"/>
<!-- after-returning表示方法正常結束才會起作用(拋異常時候不起作用) -->
<aop:after-returning method="afterReturningTest" pointcut-ref="myPointCut"/>
<aop:around method="aroundTest" pointcut-ref="myPointCut"/>
<!-- throwing="ex"表示throwingTest方法中接收異常對象的名字一定要是ex -->
<aop:after-throwing method="throwingTest" pointcut-ref="myPointCut" throwing="ex"/>
</aop:aspect>
</aop:config>
</beans>
@Test public void aop2(){ try { // 獲取springIOC容器
String path = "com/cnblogs/aop/aopConfig/config2.xml"; ApplicationContext container = new ClassPathXmlApplicationContext(path); BankService target = (BankService) container.getBean("service"); target.save(1000.0); // target.withdraw(20000.0);
} catch (Exception e) { e.printStackTrace(); } }
save before...
save is start..
存錢: 1000.0
save is end..
save afterReturning
save after...
六:使用注解配置AOP
1.定義切面類:
package com.cnblogs.aop.annotation; import java.text.SimpleDateFormat; import java.util.Date; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.AfterThrowing; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; @Component @Aspect public class AnnotationHandler { /** * 在一個方法上面加上注解來定義切入點 * 這個切入點的名字就是這個方法的名字 * 這個方法本身不需要有什么作用 * 這個方法的意義就是:給這個 @Pointcut注解一個可以書寫的地方 * 因為注解只能寫在方法、屬性、類的上面,並且方法名作為切入點的名字 */ @Pointcut("execution(public * com.cnblogs.aop.service.impl.*.*(..))") public void myPoint(){} /** * 前置通知 * 如果需要用到Joinpoint,可以在方法參數加上 * @param j */ @Before("myPoint()") public void beforeTest(){ System.out.println("前置通知: " + new SimpleDateFormat().format(new Date())); } /** * 最終通知 */ @After("myPoint()") public void afterTest(){ System.out.println("最終通知: " + new SimpleDateFormat().format(new Date())); } /** * 環繞通知 * @throws Throwable */ @Around("myPoint()") public void aroundTest(ProceedingJoinPoint pjp) throws Throwable{ System.out.println("環繞通知之前置: " +
new SimpleDateFormat().format(new Date())); //調用連接點的方法去執行
Object obj = pjp.proceed(); System.out.println("環繞通知之后置: " +
new SimpleDateFormat().format(new Date())); } /** * 成功返回才會執行的后置通知 */ @AfterReturning("myPoint()") public void afterReturningTest(){ System.out.println("成功返回才執行的后置通知: " + new SimpleDateFormat().format(new Date())); } /** * 拋出異常通知 */ @AfterThrowing(value="myPoint()",throwing="ex") public void throwTest(Exception ex){ System.out.println("拋出異常才會執行的通知(1參): " + ex.toString()); } }
2.在BankServiceImpl上加 @Service("service") 注解,在account成員變量加 @Autowired注解。
在Account上加 @Component @Scope("prototype")。
3.在xml文件中加上
<aop:aspectj-autoproxy/>
<context:component-scan base-package="com.cnblogs.aop.pojo"/>
<context:component-scan base-package="com.cnblogs.aop.service.*"/>
<context:component-scan base-package="com.cnblogs.aop.annotation"/>
<context:component-scan base-package=" " />
表示SpringIOC容器會掃描此包下加了@Component,@Service注解的類,並放入IOC容器。在上上篇隨便springIOC的三種注入方式
有介紹。
4.最后一步就是測試了
@Test public void annotation(){ try { // 獲取springIOC容器
String path = "com/cnblogs/aop/annotation/annotation.xml"; ApplicationContext container = new ClassPathXmlApplicationContext(path); BankService service = (BankService) container.getBean("service"); // service.getAccount().setBalance(10000.0);
service.save(3000.0); } catch (Exception e) { e.printStackTrace(); } }
環繞通知之前置: 19-5-29 下午8:41
前置通知: 19-5-29 下午8:41
存錢: 3000.0
環繞通知之后置: 19-5-29 下午8:41
最終通知: 19-5-29 下午8:41
成功返回才執行的后置通知: 19-5-29 下午8:41