AOP(Aspect Oriented Programming):面向切面程式設計
一、AOP的思想
正常的傳統的程式執行流程都是縱向執行流程,AOP(面向切面程式設計)在原有的縱向執行流程中添加橫切面。
二、AOP的優點
1、AOP的使用不需要修改原有程式代碼,
2、具有高擴充性,
3、原有的功能相當于釋放了部分邏輯,讓職責更加明确。
三、面向切面程式設計到底是什麼?
在程式原有縱向執行流程中,針對某個或某一些方法添加通知,形成橫切面過程就叫做面向切面程式設計
四、常用的概念
編号2:切點 需要添加額外功能的方法,
編号3:前置通知 在切點之前執行的功能,before advice,
編号4:後置通知 在切點之後執行的功能,after advice,
異常通知: 如果切點執行過程中出現異常,會觸發異常通知 throws advice,
編号1:切面 所有功能總稱為切面,
織入: 把切面嵌入到原有功能的過程叫做織入。
五、spring 提供了2種AOP實作方式
1、Schema-based
(1)每一個通知都需要實作接口或者類,
(2)配置spring配置檔案時,切面在aop:config标簽下配置。
2、AspectJ
(1)每個通知不需要實作接口或類,
(2)配置spring配置檔案時切面在aop:config的子标簽aop:aspect下配置。
六、實作
方式一:Schema-based方式
1、導包
aopalliance-1.0.jar
aspectjrt-1.8.10.jar
aspectjweaver-1.8.10.jar
commons-logging-1.1.1.jar
spring-aop-4.3.10.RELEASE.jar
spring-beans-4.3.10.RELEASE.jar
spring-context-4.3.10.RELEASE.jar
spring-core-4.3.10.RELEASE.jar
spring-expression-4.3.10.RELEASE.jar
2.建立通知類
2.1 建立前置通知類
2.1.1 arg0 切點方法對象 Method 對象
2.1.2 arg1 切點方法參數
2.1.3 arg2 切點在哪個對象中
public class MyBeforeAdvice implements MethodBeforeAdvice{
@Override
public void before(Method arg0, Object[] arg1, Object arg2){
System.out.println("執行前置通知");
}
}
2.2 建立後置通知類
2.2.1 arg0 切點方法傳回值
2.2.2 arg1 切點方法對象
2.2.3 arg2 切點方法參數
2.2.4 arg3 切點方法所在類的對象
public class MyAfterAdvice implements AfterReturningAdvice{
@Override
public void afterReturning(Object arg0, Method arg1,Object[] arg2, Object
arg3){
System.out.println("執行後置通知");
}
}
3.配置 spring 配置檔案
3.1 引入 aop 命名空間
3.2 配置通知類的
3.3 配置切面
3.4 * 通配符,比對任意方法名,任意類名,任意一級包名
3.5 如果希望比對任意方法參數 (…)
<?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/sc hema/beans
http://www.springframework.org/schema/beans/spring-be ans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop. xsd">
<!-- 配置通知類對象,在切面中引入 -->
<bean id="mybefore" class="com.woniuxy.advice.MyBeforeAdvice"></bean>
<bean id="myafter" class="com.woniuxy.advice.MyAfterAdvice"></bean>
<!-- 配置切面 -->
<aop:config>
<!-- 配置切點 -->
<aop:pointcut expression="execution(* com.woniuxy.test.Demo.demo2())"
id="mypoint" />
<!-- 通知 -->
<aop:advisor advice-ref="mybefore" pointcut-ref="mypoint" />
<aop:advisor advice-ref="myafter" pointcut-ref="mypoint" />
</aop:config>
<!-- 配置 Demo 類,測試使用 -->
<bean id="demo" class="com.woniuxy.test.Demo"></bean>
</beans>
4.編寫測試代碼
public class Test {
public static void main(String[] args){
// Demo demo = new Demo();
// demo.demo1();
// demo.demo2();
// demo.demo3();
ApplicationContext ac =
new ClassPathXmlApplicationContext("applicationContext.xml");
Demo demo = ac.getBean("demo", Demo.class);
demo.demo1();
demo.demo2();
demo.demo3();
}
}
通知類型:
前置通知
後置通知
環繞通知
異常通知
傳回後通知
方式二:Aspectj方式
1、IUserDao
//IUserService接口
public interface IUserService {
void save();
}
2、UserService實作類(實作接口)
UserDao實作類(有實作接口)
@Component // 加入容器
public class UserServiceImpl implements IUserService{
@Override
public void save() {
System.out.println("-----調用DAO核心業務:儲存!!!------");
}
}
3、切面類
public class Aop {
public void begin() {
System.out.println("開始事務/異常");
}
public void after() {
System.out.println("無論程式正常執行還是有異常,隻要程式執行完就會執行這個通知");
}
public void afterReturning() {
System.out.println("程式執行到return,代表程式正常執行完畢,是以隻有程式正常執行完,沒有異常才會執行這個通知");
}
public void afterThrowing() {
System.out.println("afterThrowing()");
}
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("環繞前....");
pjp.proceed(); // 執行目标方法
System.out.println("環繞後....");
}
}
4、spring-context.xml
<!-- dao執行個體 -->
<bean id="userService" class="com.my.g_aop_xml.UserService"></bean>
<bean id="otherService" class="com.my.g_aop_xml.OtherService"></bean>
<!-- 切面類 -->
<bean id="aop" class="com.my.g_aop_xml.Aop"></bean>
<!-- AOP配置 -->
<aop:config>
<!-- 定義一個切入點表達式 -->
<aop:pointcut expression="execution(* com.my.g_aop_xml.*.*(..))" id="pt"/>
<!-- 配置切面 -->
<aop:aspect ref="aop">
<!-- 前置通知 -->
<aop:before method="begin" pointcut-ref="pt"/>
<!-- 後置通知 -->
<aop:after method="after" pointcut-ref="pt"/>
<!-- 環繞通知 -->
<aop:around method="around" pointcut-ref="pt"/>
<!-- 傳回後通知 -->
<aop:after-returning method="afterReturning" pointcut-ref="pt"/>
<!-- 異常通知 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
5、測試類
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/my/f_aop_anno/bean.xml");
//目标對象有實作接口,spring會預設采用JDK代理
IUserService userService = (IUserService) ac.getBean("userService");
System.out.println(userService.getClass());//$Proxy001
userDao.save();
}
注解方式實作Aspectj
1、IUserService接口
public interface IUserService {
void save();
}
2、UserDao實作類(實作接口)
@Component // 加入容器
public class UserServiceImpl implements IUserService{
@Override
public void save() {
System.out.println("-----調用DAO核心業務:儲存!!!------");
}
}
3、AOP切面類
@Component // 加入IOC容器
@Aspect // 指定目前類為切面類
public class Aop {
// 指定切入點表達式:攔截哪些方法(為哪些類生成代理對象)
@Pointcut("execution(* com.my.f_aop_anno.*.*(..))")
public void pointCut_() {
}
//JoinPoint對象封裝了SpringAop中切面方法的資訊,在切面方法中添加JoinPoint參數,就
//可以擷取到封裝了該方法資訊的JoinPoint對象
@Before("pointCut_()")
public void begin(JoinPoint point) {
System.out.println("開始事務/異常");
//方法簽名擷取封裝了署名資訊的對象,在該對象中可以擷取到目标方法名,所屬類的Class
//等資訊
MethodSignature methodSignature =
(MethodSignature)point.getSignature();
System.out.println("調用"+methodSignature.getMethod().getName()+"方法");
}
// 後置/最終通知:在執行目标方法之後執行 【無論是否出現異常最終都會執行】
@After("pointCut_()")
public void after() {
System.out.println("送出事務/關閉");
}
// 傳回後通知: 在調用目标方法結束後執行 【出現異常不執行】
@AfterReturning("pointCut_()")
public void afterReturning() {
System.out.println("afterReturning()");
}
// 異常通知: 當目标方法執行異常時候執行此關注點代碼
@AfterThrowing("pointCut_()")
public void afterThrowing() {
System.out.println("afterThrowing()");
}
// 環繞通知:環繞目标方式執行
//ProceedingJoinPoint對象是JoinPoint的子接口,該對象隻用在@Around的切面方法中
@Around("pointCut_()")
public void around(ProceedingJoinPoint pjp) {
System.out.println("環繞前....");
try{
pjp.proceed(); // 執行目标方法
}catch (Throwable exception){
}finaly{
}
System.out.println("環繞後....");
}
}
4.4、bean.xml配置檔案
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 開啟spring注解掃描 -->
<context:component-scan
base-package="com.my.f_aop_anno"></context:component-scan>
<!-- 開啟aop注解方式 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
4.5、測試類
public static void main(String[] args) {
ApplicationContext ac =
new ClassPathXmlApplicationContext("com/my/f_aop_anno/bean.xml");
//目标對象有實作接口,spring會預設采用JDK代理
IUserService userService =
(IUserService) ac.getBean("userDaoServiceImpl");
System.out.println(userServiceImpl.getClass());//$Proxy001
userServiceImpl.save();
//目标對象沒有實作接口,spring預設采用cglib代理
OtherServiceImpl otherServiceImpl =
(OtherServiceImpl)ac.getBean("otherServiceImpl");
System.out.println(otherServiceImpl.getClass());
otherImpl.save();
}
4.6、OtherService類(未實作接口)
@Component
public class OtherServiceImpl {
public void save() {
System.out.println("執行沒有接口的dao");
}
}
補充:aop是基于代理模式實作的,spring中本身就嵌入了CGLIB,當包含切點的類實作了接口時,aop預設使用JDK代理;當包含切點的類沒有實作接口那麼aop就使用CGLIB代理。