AOP
面向切面编程:Aspect Oriented Programming,可以通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术。
AOP可以说是OOP(面向对象编程)的补充和完善。在OOP设计中有可能导致代码的重复不利于模块的重用性,例如日志功能。日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能关系不大。但是在OOP中这些业务要和核心业务代码在代码这一级集成。还有些如安全性、事务等也是如此。能不能把这些与核心业务无关但系统中需要使用的业务(称为切面)单独编写成一个模块,在主要核心业务代码中不调用,而是在配置文件中做些配置,配置核心业务需要使用到得切面部分,在系统编译时才织入到业务模块中。
切面(Aspect):简单的理解就是把那些与核心业务无关的代码提取出来,进行封装成一个或几个模块用来处理那些附加的功能代码。(如日志,事务,安全验证)我们把这个模块的作用理解为一个切面,其实切面就是我们写一个类,这个类中的代码原来是在业务模块中完成的,现在单独成一个或几个类。在业务模块需要的时候才织入。
连接点(Joinpoint):在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。 在Spring AOP中,一个连接点总是代表一个方法的执行。通过声明一个JoinPoint类型的参数可以使通知(Advice)的主体部分获得连接点信息。
切入点(Pointcut):本质上是一个捕获连接点的结构。在AOP中,可以定义一个pointcut,来捕获相关方法的调用
织入(Weaving):把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象。 这些可以在编译时,类加载时和运行时完成。Spring和其它纯Java AOP框架一样,在运行时完成织入。
通知(Advice):在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。 通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型,并维护一个以连接点为中心的拦截器链。
通知的类型:
- 前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。
- 返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。
- 抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。
- 后置通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。
- 环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。
AOP中的一个重要等式:
切面=切点+通知 (advisor=pointCut+advice)
- 切面: 定义的一个拦截事件(动作)
- 切点: 要拦截哪些(个)类的哪些(个)方法
- 通知: 定义在方法的前面、后面、环绕、出异常 还是 正常返回的时候拦
Spring--四种方式实现AOP
方式一:Spring最底层的方式
纯java实现AOP---了解实现AOP需要哪些Bean
package cn.hncu.aop.hello.v1;
import java.lang.reflect.Method;
import org.aopalliance.aop.Advice;
import org.springframework.aop.Advisor;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.aop.support.JdkRegexpMethodPointcut;
import cn.hncu.aop.hello.v1.domain.Person;
/*AOP中的一个重要等式:
* 切面=切点+通知
* advisor=pointCut+advice
* 切面: 定义的一个拦截事件(动作)
* 切点: 要拦截哪些(个)类的哪些(个)方法
* 通知: 定义在方法的前面、后面、环绕、出异常 还是 正常返回的时候拦
*/
//存java实现 AOP
public class Demo {
public static void main(String[] args) {
//1 被切面的对象(被代理的对象)
Person person = new Person();
//2.2 切点
JdkRegexpMethodPointcut pointcut = new JdkRegexpMethodPointcut();
//拦截的方法的表达式
pointcut.setPattern(".*run.*");
//2.3 通知
Advice advice = new MethodBeforeAdvice() {
//这种是无法进行方法拦截的,只是在方法执行前执行一段代码
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
System.out.println("前面做些事...");
}
};
// 2.1切面入口 --- 切面=切点+通知
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
//创建代理bean工厂
ProxyFactoryBean factory = new ProxyFactoryBean();
//把切面处理方案添加到工厂中
factory.addAdvisor(advisor);
//把需要进行切面的对象放入工厂
factory.setTarget( person );
//拿出代理后的对象
Person proxyPerson = (Person) factory.getObject();
proxyPerson.run(); //执行run
System.out.println( proxyPerson.getClass().getSuperclass() );
System.out.println( proxyPerson.hashCode() );
Person proxyPerson2 = (Person) factory.getObject();
System.out.println( proxyPerson2.hashCode() );
}
}
使用Spring容器实现AOP
有五个版本具体了解见完整代码链接,这里就展示出第四版本的代码。
xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 切面=切点+通知 -->
<!-- 相比于 t2版本 这里的切面bean采用了DefaultPointcutAdvisor的兄弟类,使用时更简便-->
<bean id="advisor" class="org.springframework.aop.support.RegexpMethodPointcutAdvisor">
<!-- 相比于 t2版本 这里的 切点 已经融入切面之中,只需要配置拦截方法的表达式即可-->
<property name="patterns">
<list>
<value>.*run.*</value>
<value>.*eat.*</value>
</list>
</property>
<!-- 通知 -->
<property name="advice">
<bean id="advice" class="cn.hncu.aop.hello.v2.AroundAdviceImpl" />
</property>
</bean>
<bean id="person" class="cn.hncu.aop.hello.v2.domain.Person" />
<bean class="cn.hncu.aop.hello.v2.domain.Student"></bean>
<!-- 自动代理bean -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
</beans>
实现通知的类
package cn.hncu.aop.hello.v2;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/*
* 实现org.aopalliance.intercept.MethodInterceptor接口,
* 即可实现同动态代理那样,既可以前面拦截,后面拦截,同时可以控制放行
*/
public class AroundAdviceImpl implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前面拦拦...");
//控制下面这一句执行就可以控制放行,也就可以实现方法拦截。
Object returnValue = invocation.proceed(); //放行
System.out.println("后面拦拦...");
return returnValue;
}
}
测试代码
/* DefaultAdvisorAutoProxyCreator类 实现了 BeanPostProcessor接口,
* BeanPostProcessor:在每个bean的创建-->初始化过程的前/后-->放入 applicationContext 初始化过程的前/后 起作用的
* 在每个 bean 创建后 --> 初始化过程的前/后 都会触发 BeanPostProcessor中的方法
* DefaultAdvisorAutoProxyCreator类会根据 容器中的 Advisor 的规则把每个符合 的bean进行自动代理
* 然后把代理的bean放入 applicationContext 容器中
*/
@Test
public void t4() {
//使用 t4.xml
ApplicationContext appCtx = new ClassPathXmlApplicationContext("cn/hncu/aop/hello/v2/t4.xml");
Person person = (Person) appCtx.getBean(Person.class);
person.run();
person.eat();//配置拦截
/* 该类中有 切入点 的函数,所以会被 DefaultAdvisorAutoProxyCreator 检测到
* 并且放入applicationContext中是代理后的对象,即获得到的是 代理后的对象
*/
System.out.println( person.getClass() );
Student student = appCtx.getBean(Student.class);
/* 该类中没有 切入点 的函数,所以不会被 DefaultAdvisorAutoProxyCreator 检测到
*/
System.out.println( student.getClass() );
((AbstractApplicationContext) appCtx).close();
}
方式二:Spring---引入AspectJ
aspectj技术主要是改进之前定位切点时不够精确的问题,如aspectj可以使用切点语言定义切点更具体的特征如方法的返回类型、方法的形参等。
aspectj定义切点时采用的是切点语言,助理解:
△设置切点: cut.setExpression("execution( 用切点语言写的切点表达式 )");
△用切点语言写的切点表达式---相关知识点如下:
1) 切点表达式格式: 返回类型 包名.[子包名.]类名.方法名(参数类型列表)
2) "."是包名之间 或 包名与类名 或 类名与方法名 之间的间隔符
3) ".."在包路径位置代表'任意深的目录',在参数类型列表位置代表'任意个数与类型的参数'
4) "*"是操作系统中的通匹符
纯Java方式使用AspectJ实现AOP
package cn.hncu.aop.aspectj.v1;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.junit.Test;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.framework.ProxyFactoryBean;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import cn.hncu.aop.hello.v2.domain.Person;
/*
1. aspectj技术主要是改进之前定位切点时不够精确的问题,
如aspectj可以使用切点语言定义切点更具体的特征如方法的返回类型、方法的形参等
2. aspectj定义切点时采用的是切点语言,助理解:
△设置切点: cut.setExpression("execution( 用切点语言写的切点表达式 )");
△用切点语言写的切点表达式---相关知识点如下:
1) 切点表达式格式: 返回类型 包名.[子包名.]类名.方法名(参数类型列表)
2) "."是包名之间 或 包名与类名 或 类名与方法名 之间的间隔符
3) ".."在包路径位置代表'任意深的目录',在参数类型列表位置代表'任意个数与类型的参数'
4) "*"是操作系统中的通匹符
*/
public class AspectJDemo {
//演示纯java的方式实现AOP
@Test
public void t1() {
//被代理的对象
Person person = new Person();
/////////////AspectJ加强处///////////////////
//切点
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
//拦截 返回值 为 void 并且是cn/hncu/任意目录/Person类中的任意r开头的空参方法
//pointcut.setExpression( "execution( void cn.hncu..Person.r*() )" );
//拦截 返回值 为 void 并且是cn/hncu/任意目录/Person类中的任意r开头的参数为int类型的方法
pointcut.setExpression( "execution( void cn.hncu..Person.r*(int) )" );
//////////////////////////////////////////
//通知
Advice advice = new MethodInterceptor() {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前面拦拦...");
Object returnValue = invocation.proceed(); //放行
System.out.println("后面拦拦...");
return returnValue;
}
};
//切面=切点+通知
Advisor advisor = new DefaultPointcutAdvisor(pointcut, advice);
//创建代理bean工厂
ProxyFactoryBean factory = new ProxyFactoryBean();
//添加切面方案
factory.addAdvisor(advisor);
//放入目标: 被代理对象
factory.setTarget( person );
Person person2 = (Person) factory.getObject();
person2.run();
person2.run(5);
person2.eat();
}
}
使用Spring容器
xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
">
<!-- 使用 AspectJExpressionPointcutAdvisor 切面,该类封装了切点,只要给切点表达式即可-->
<bean class="org.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
<!-- 切点表达式 -->
<property name="expression" value="execution( * cn.hncu..Person.run(..) )" />
<!-- 通知:环绕通知,即前后都拦截 -->
<property name="advice">
<bean class="cn.hncu.aop.aspectj.v2.AroundAdviceImpl"/>
</property>
</bean>
<bean class="cn.hncu.aop.aspectj.v2.domian.Person" />
<!-- 通过切面自动扫描容器中符合切点表达式的bean,并生产代理bean -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
</beans>
实现通知的类
package cn.hncu.aop.aspectj.v2;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/*
* 实现org.aopalliance.intercept.MethodInterceptor接口,
* 即可实现同动态代理那样,既可以前面拦截,后面拦截,同时可以控制放行
*/
public class AroundAdviceImpl implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("前面拦拦...");
//控制下面这一句执行就可以控制放行,也就可以实现方法拦截。
Object returnValue = invocation.proceed(); //放行
System.out.println("后面拦拦...");
return returnValue;
}
}
测试代码
package cn.hncu.aop.aspectj.v2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.hncu.aop.aspectj.v2.domian.Person;
/* 演示使用applicationContext的方式
*/
public class AspectjXmlDemo {
public static void main(String[] args) {
ApplicationContext appCtx = new ClassPathXmlApplicationContext("cn/hncu/aop/aspectj/v2/applicationContext.xml");
Person person = appCtx.getBean(Person.class);
person.run();
person.eat();
((AbstractApplicationContext) appCtx).close();
}
}
最后面有完整代码链接
方式三:AspectJ+注解的方式
xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
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
">
<!-- 1自动代理标签。为了让Spring能够识别基于注释的切面,必须要加这个aop标签 -->
<aop:aspectj-autoproxy />
<!-- Spring2.5,Spring3x用下面这个自动代理bean 可以替换上面的自动代理标签。但是高版本就不行了
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"></bean>
-->
<!-- 2被拦截的Bean -->
<bean class="cn.hncu.aop.annotation.domain.Person" />
<!-- 3切面Bean -->
<bean class="cn.hncu.aop.annotation.v2.MyAdvisor" />
</beans>
切面类
方式一:@Pointcut注解寄托于方法上
package cn.hncu.aop.annotation.v1;
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;
/* 切面=切点+通知:@Aspect = @Pointcut + ( @Before | @After | @Around | @AfterReturning | @AfterThrowing )
*/
@Aspect //被该 注解 注解后的类会被识别为'切面' Advisor
public class MyAdvisor {
//拦截Person类中的所有空参方法
@Pointcut(value="execution( * cn.hncu..Person.*() )")
public void pointCut() { //切点注解的函数没有任何函数功能,该函数只是用来承载 @Pointcut和标识它
}
//可以有多个切点
@Pointcut(value="execution( * cn.hncu..Person.*(*) )")
public void pointCut2() {
}
@Before(value="pointCut()") //通过标识找到对应的切点进行通知
public void before() { //函数名可以随便取,没有实际意义,只不过规范点方便识别
System.out.println("...before...");
}
@Before(value="pointCut2()") //第二个切点
public void before2() {
System.out.println("...before...");
}
@After(value="pointCut()") //通过标识找到对应的切点进行通知
public void after() { //函数名可以随便取,没有实际意义,只不过规范点方便识别
System.out.println("...after...");
}
/* 使用@Around 注解时要注意:
* 被注解的函数一定要有 ProceedingJoinPoint p 作为函数参数, 并且要有一个 Object型 的返回值。
*/
@Around(value="pointCut()")
public Object around(ProceedingJoinPoint p ) throws Throwable {
String methdName = p.getSignature().getName(); //可以获取到被拦截的方法名等信息
System.out.println(methdName+">>前面拦拦...");
Object returnValue = p.proceed(); //放行
System.out.println(methdName+">>后面拦拦...");
return returnValue;
}
/* @AfterReturning 和 @AfterThrowing 是一对'互斥'的注解
* @AfterReturning:函数正常返回后通知
* @AfterThrowing:函数抛出异常(没抓)后通知
*/
@AfterReturning(value="pointCut()")
public void afterReturning() {
System.out.println("这是调用完成,正常返回(没有捕捉到异常)以后");
}
@AfterThrowing(value="pointCut()")
public void afterThrowing() {
System.out.println("这是调用完成,发现有异常");
}
@AfterThrowing(value="pointCut2()") //第二个切点
public void afterThrowing2() {
System.out.println("这是调用完成,发现有异常");
}
}
方式二:不需要@Pointcut注解,直接使用切点表达式
package cn.hncu.aop.annotation.v2;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/* 切面=切点+通知:@Aspect = str + ( @Before | @After | @Around | @AfterReturning | @AfterThrowing )
*/
@Aspect //被该 注解 注解后的类会被识别为'切面' Advisor
public class MyAdvisor {
//切点语言表达式
private final String CUT = "execution( * cn.hncu..Person.*( ) )";
private final String CUT2 = "execution( * cn.hncu..Person.*( * ) )";
@Before(value=CUT)
public void before() {
System.out.println("...before...");
}
@Before(value=CUT2)
public void before2() {
System.out.println("...before...");
}
@Around(value=CUT)
public Object around(ProceedingJoinPoint p ) throws Throwable {
System.out.println("前面拦拦...");
Object returnValue = p.proceed(); //放行
System.out.println("后面拦拦...");
return returnValue;
}
}
测试代码
package cn.hncu.aop.annotation.v1;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import cn.hncu.aop.annotation.domain.Person;
public class Demo {
public static void main(String[] args) {
ApplicationContext appCtx = new ClassPathXmlApplicationContext("cn/hncu/aop/annotation/v1/demo.xml");
Person person = appCtx.getBean( Person.class );
System.out.println("--------------");
person.run();
System.out.println("--------------");
person.run(5);
// person.run(0); //这里演示 @AfterThrowing 注解
System.out.println("--------------");
person.eat();
System.out.println("--------------");
//关流防止内存泄漏
((AbstractApplicationContext) appCtx).close();
}
}
方式四:AspectJ+label的方式
xml文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
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
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
">
<!-- 1自动代理标签,专用于基于标签的切面的 -->
<aop:config>
<aop:aspect ref="hello">
<aop:pointcut expression="execution( * cn.hncu..Person.*( ) )" id="pc1"/>
<aop:after method="hello" pointcut-ref="pc1"/>
<aop:before method="say" pointcut-ref="pc1"/>
<aop:around method="around" pointcut-ref="pc1"/>
</aop:aspect>
</aop:config>
<!-- 2被拦截的Bean -->
<bean class="cn.hncu.aop.annotation.domain.Person" />
<!-- 3切面Bean -->
<bean id="hello" class="cn.hncu.aop.label.HelloWorld" />
</beans>
切面类
package cn.hncu.aop.label;
import org.aspectj.lang.ProceedingJoinPoint;
/* 采用label方式使 java类最多只依赖 org.aspectj.lang.ProceedingJoinPoint
* 如果把该类无视掉的话,该类就是一个纯粹的POJO(普通的java对象)。
* 完全看不出来该类是一个切面。
*/
public class HelloWorld {
public void hello() {
System.out.println("Hello world!");
}
public void say() {
System.out.println("say...");
}
public Object around( ProceedingJoinPoint p ) throws Throwable {
System.out.println("前面拦拦...");
Object returnValue = p.proceed(); //放行
System.out.println("后面拦拦...");
return returnValue;
}
}