目录
- 目录
- AOP
- 介绍
- AOP底层原理
- 核心概念
- 基于AspectJ的Spring AOP操作
- AOP操作准备
- XML方式
- 定义切点
- 案例扩展
- 注解方式
AOP
介绍
AOP,即面向切面编程,简单来说其思想指的是在不修改原有代码的情况下扩展新的功能。在传统OOP思想下,我们扩展一个类的功能,可能采取的方式为纵向继承,即定义父类,编写新功能,通过继承的形式在子类中使用父类的方法扩展功能。这种方式下,存在着类与类之间的耦合,多个类需要扩展多个新的功能,都需要新增新的代码到原有类中。
而AOP思想则表示采用“在程序运行期间将某段代码,动态的切入到某个类的指定方法的指定位置”的方式,即我们不需要对原有类的功能进行修改,而是在程序运行过程中,将新增的功能加入都被扩展的类型的指定方法的指定位置。
AOP底层原理
AOP,面向切面编程,底层使用动态代理方式实现。针对实现了接口的类的横向扩展,采用的是JDK动态代理进行实现。在JDK动态代理中,为对原有类(实现了某一接口的类)进行横向扩展,内部创建了实现了同一接口的类代理对象,具有与原有类对象相同的功能,且进行了增强。对于未实现接口的类,想实现横向的扩展,则需要使用cglib动态代理实现,内部创建原有类的子类的代理对象,即在子类中调用父类的方法完成增强。
详细的分析还有待我深入的研究(20180320)!!!
核心概念
- 连接点(Joinpoint):被拦截到的点。在Spring中只支持方法类型的连接点,因此这些点在Spring中指的是方法。简单来说,我们可以认为是可以被增强功能的方法。
- 切入点(Pointcut):对连接点进行拦截的定义。在Spring中通过表达式(后续将进行说明)形式来定义所匹配的类下的方法,以此定义切入点。
- 通知/增强(Advice):所谓通知指的就是指拦截到连接点之后要执行的代码,通知分为前置通知(before 在方法之前执行)、后置(after-returning 在方法之后执行)、异常(after-throwing 在方法出现异常时执行)、最终(after 在后置之后执行,无论方法是否执行成功)、环绕(around 在方法之前和之后执行)通知五类。
- 目标对象(Target):代理的类或是要被增强的类。
- 织入(Weaving):将目标增强的过程,或者是是通知应用到目标的过程。
- 代理(Proxy):一个类进行织入后将产生一个结果类,称为代理类。
- 切面(Aspect):是切入点和通知的结合。
基于AspectJ的Spring AOP操作
AOP操作准备
(1)导入jar包,除包括Spring基础jar包外,还需要导入AOP相关的jar包,此外为了方便测试还需引入junit相关的jar包,包括如下:
Spring基础jar包:
1. spring-beans
2. spring-context
3. spring-core
4. spring-expression
5. commons-logging-1.2.jar
6. log4j-1.2.17.jar
Spring AOP相关的jar包:
1. aopalliance-1.0.jar
2. aspectjweaver-1.8.7.jar
3. spring-aop
4. spring-aspects
junit相关的jar包:
1. junit-4.12.jar
2. hamcrest-core-1.3.jar
(2)创建Spring核心配置文件,并导入AOP的约束。这里我将该核心配置文件命名为aop.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">
</beans>
XML方式
案例:在User类的add方法执行前后加入日志记录功能。
(1)创建需要被增强的类,User.java,如下:
package com.wm103.aop;
/**
* Created by DreamBoy on 2018/3/18.
*/
public class User {
public void add() {
System.out.println("Running User Add Method.");
}
}
(2)创建用于增强其他类的类,这里以日志类为例,即目的是为在User对象add方法调用前后,写入日志。LogHandler.java,如下:
package com.wm103.aop;
/**
* Created by DreamBoy on 2018/3/18.
*/
public class LogHandler {
public void before() {
System.out.println("Start Write Log.");
}
public void after() {
System.out.println("End Write Log.");
}
}
(3)在Spring核心配置文件(这里为aop.xml)中配置,实现类的增强。如下:
<!-- 1. 配置对象 -->
<bean id="user" class="com.wm103.aop.User"></bean>
<bean id="logHandler" class="com.wm103.aop.LogHandler"></bean>
<!-- 2. AOP配置 -->
<aop:config>
<!-- 2.2 配置切入点 -->
<aop:pointcut id="pointcut" expression="execution(* com.wm103.aop.User.*(..))"/>
<!-- 2.1 配置切面 -->
<aop:aspect ref="logHandler">
<!-- 2.3 针对切入点,配置通知/增强 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
(4)创建测试类,TestAop.java,如下:
package com.wm103.aop;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by DreamBoy on 2018/3/18.
*/
public class TestAop {
@Test
public void runUser() {
ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml");
User user = (User) context.getBean("user");
user.add();
}
}
定义切点
使用表达式配置切入点:
在上述例子中为实现AOP操作,对切入点进行定义,定义如下:
<aop:pointcut id="pointcut" expression="execution(* com.wm103.aop.User.*(..))"/>
在expression
属性中,我们通过使用execution
属性对方法进行了定义,即规定哪些方法为切入点。该表达式的语法如下:
execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?) 除了返回类型模式、方法名模式和参数模式外,其它项都是可选的。
举例几个主要使用:
1. execution(public * *(..))
:匹配所有类的public方法;
2. execution(* com.wm103.dao.*(..))
:匹配指定包(这里指com.wm103.dao
)下的所有类的方法,不包含子包;
3. execution(* com.wm103.dao..*(..))
:匹配指定包以及其下子孙包下的所有类的方法,值得注意的是,“..”出现在类名中时,后面必须跟“*”,表示包、子孙包下的所有类;
4. execution(* com.wm103.dao.UserDaoImpl.*(..))
:匹配指定类下的所有方法;
5. execution(* com.wm103.dao.UserDao+.*(..))
:匹配实现指定接口(这里指com.wm103.dao.UserDao
接口)的所有类的方法;
6. execution(* save*(..))
:匹配所有指定前缀(这里指的是save
)开头的方法;
7. execution(* *.*(..))
:匹配所有类的所有方法。
案例扩展
案例:在上述案例中,对User类的add方法进行补充,增加记录方法运行前的时间和运行后的时间以及花费的时间。
(1)创建用于增强其他类的类,这里以时间类为例,即目的是为在User对象add方法调用前后,写入运行时间。TimeHandler.java,如下:
package com.wm103.aop;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Created by DreamBoy on 2018/3/18.
*/
public class TimeHandler {
private long startTime = 0;
private long endTime = 0;
public void before() {
startTime = new Date().getTime();
System.out.println("Start Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTime) + ".");
}
public void after() {
endTime = new Date().getTime();
System.out.println("End Time: " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(endTime) + ".");
long costTime = endTime - startTime;
System.out.println("Total Cost Time: " + (costTime / 1000.0) + "s.");
}
}
(2)对上述Spring核心配置文件,进行进一步补充。如下:
配置对象
<bean id="timeHandler" class="com.wm103.aop.TimeHandler"></bean>
配置切面
<aop:aspect ref="timeHandler">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
(3)运行测试类TestAop,运行结果如下:
Start Write Log.
Start Time: 2018-03-21 00:01:35.
Running User Add Method.
End Time: 2018-03-21 00:01:35.
Total Cost Time: 0.075s.
End Write Log.
增强的顺序默认按照xml中配置的顺序运行。如果需要将TimeHandler
增强先执行的话,那么可以调整LogHandler
和TimeHandler
的顺序。或者在aop:aspect
标签中使用order
属性(按从小到大的顺序执行)。如下:
<!-- 2.1 配置切面 -->
<aop:aspect ref="logHandler" order="2">
<!-- 2.3 针对切入点,配置通知/增强 -->
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
<aop:aspect ref="timeHandler" order="1">
<aop:before method="before" pointcut-ref="pointcut"/>
<aop:after method="after" pointcut-ref="pointcut"/>
</aop:aspect>
运行结果如下:
Start Time: 2018-03-21 00:08:29.
Start Write Log.
Running User Add Method.
End Write Log.
End Time: 2018-03-21 00:08:29.
Total Cost Time: 0.082s.
TestExp Run Method.
(4)环绕通知实现
在User.java中添加del方法,如下:
public void del() {
System.out.println("Running User Del Method.");
}
为实现环绕通知,在LogHandler.java中添加如下方法:
public void around(ProceedingJoinPoint joinPoint) {
try {
before();
joinPoint.proceed();
after();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
其中joinPoint.proceed();
表示调用被增强类的方法。
在Spring核心配置文件中,单独为User类的del方法定义新的切入点,如下:
<aop:pointcut id="pointcut3" expression="execution(* com.wm103.aop.User.del(..))"/>
配置切面,针对切入点实现环绕通知,如下:
<aop:aspect ref="logHandler">
<aop:around method="around" pointcut-ref="pointcut3"/>
</aop:aspect>
以上操作即简单实现了环绕通知。我们可以在被增强的类的方法前后执行所需功能的程序代码,以达到增强类方法的目的。测试结果如下:
Start Write Log.
Running User Del Method.
End Write Log.
注解方式
案例:使用注解方式实现前置通知。如下:
(1)创建User.java,内容如下:
package com.wm103.aopanno;
/**
* Created by DreamBoy on 2018/3/18.
*/
public class User {
public void add() {
System.out.println("Running User Add Method.");
}
}
(2)创建LogHandler.java,这里以日志为例,如下:
package com.wm103.aopanno;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* Created by DreamBoy on 2018/3/18.
*/
public class LogHandler {
public void before() {
System.out.println("Start Write Log.");
}
public void after() {
System.out.println("End Write Log.");
}
}
(3)定义Spring核心配置文件,这里命名为 aopanno.xml,并开启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.xsd">
<!-- 1. 开启AOP注解扫描 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
<!-- 2. 配置对象 -->
<bean id="user" class="com.wm103.aopanno.User"></bean>
<bean id="logHandler" class="com.wm103.aopanno.LogHandler"></bean>
</beans>
(4)开启AOP注解扫描后,我们还需要在增强类上使用注解@Aspect
,在增强方法上使用注解定义切入点,以及设置通知类型。修改LogHandler.java如下:
package com.wm103.aopanno;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* Created by DreamBoy on 2018/3/18.
*/
@Aspect
public class LogHandler {
@Before(value="execution(* com.wm103.aopanno.User.*(..))")
public void before() {
System.out.println("Start Write Log.");
}
@After(value="execution(* com.wm103.aopanno.User.*(..))")
public void after() {
System.out.println("End Write Log.");
}
}
(5)创建测试类TestAop.java进行测试,内容如下:
package com.wm103.aopanno;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* Created by DreamBoy on 2018/3/18.
*/
public class TestAop {
@Test
public void runUser() {
ApplicationContext context = new ClassPathXmlApplicationContext("aopanno.xml");
User user = (User) context.getBean("user");
user.add();
}
}
(6)总结
本案例采用注解的方式简单的实现了类的增强。除了前置通知和最终通知,@AspectJ
同样也提供了对应的其他通知类型的注解形式,如下:
@Before 前置通知
@AfterReturning 后置通知
@Around 环绕通知
@AfterThrowing 异常通知
@After 最终通知(不管是否异常,该通知都会被执行)