AOP
Aspect Oriented Programming:面向切面编程
什么时候会出现面向切面编程的需求?
按照软件重构的思想,如果多个类中出现重复的代码,就应该考虑定义一个共同的抽象类,将这些共同的代码提取到抽象类中,比如Teacher,Student都有username,那么就可以把username及相关的get、set方法抽取到SysUser中,这种情况,我们称为纵向抽取。
但是如果,我们的情况是以下情况,又该怎么办?
给所有的类方法添加性能检测,事务控制,该怎么抽取?
PerformanceMonitor
TransactionManager
AOP就是希望将这些分散在各个业务逻辑代码中的相同代码,通过横向切割的方式抽取到一个独立的模块中,让业务逻辑类依然保存最初的单纯。
抽取出来简单,难点就是如何将这些独立的逻辑融合到业务逻辑中,完成跟原来一样的业务逻辑,这就是AOP解决的主要问题。
AOP术语
- 连接点(Joinpoint)
- 程序执行的某个特定位置,如某个方法调用前,调用后,方法抛出异常后,这些代码中的特定点称为连接点。简单来说,就是在哪加入你的逻辑增强
- 三个连接点
- 方法执行之前
- 方法执行之后
- 方法出异常
- 切点(PointCut)
- 每个程序的连接点有多个,如何定位到某个感兴趣的连接点,就需要通过切点来定位。
- 比如,连接点–数据库的记录,切点–查询条件
- 链接点有很多个,要切入点的点就是切点
- 增强(Advice)//通知(前置通知,后置通知,环绕通知)
- 增强是织入到目标类连接点上的一段程序代码。
在Spring中,像BeforeAdvice等还带有方位信息
- 增强是织入到目标类连接点上的一段程序代码。
- 目标对象(Target)
- 需要被加强的业务对象
- 织入(Weaving)
- 织入就是将增强添加到对目标类具体连接点上的过程。
- 代理类(Proxy)
- 一个类被AOP织入增强后,就产生了一个代理类。
- 切面(Aspect)
- 切面由切点和增强组成,它既包括了横切逻辑的定义,也包括了连接点的定义,SpringAOP就是将切面所定义的横切逻辑织入到切面所制定的连接点中。
- 定义一个范围 给哪些类的哪些方法
- 增强:加事务的控制
AOP实现者
- AspectJ
- AspectJ是语言级的AOP实现,2001发布,扩展了Java语言,定义了AOP语法,能够在编译期通过提供横切代码的织入,所以它有一个专门的编译器用来生成遵守Java字节码规范的class文件
- SpringAOP
- SpringAOP使用纯Java实现,在运行期通过代理的方式向目标类织入增强代码。
代理,目标类,增强代码
SpringAOP
SpringAOP代理机制
基于JDK 的动态代理,接口
基于CGlib的动态代理,类
可以通过打印代理类的class看出是利用了什么代理
- 如果包含enhance就是CGlib
- 如果包含sun就是jdk
创建增强(通知)
Spring使用增强类定义横切逻辑,同时由于Spring只支持方法连接点,增强还包括了在方法的哪一点加入横切代码的方位信息,所以增强既包含横切逻辑,还包含部分连接点的信息
增强类型
AOP联盟为增强定义了org.aopalliance.aop.Advice接口,Spring支持5种类型的增强(通知)
- 前置增强:org.springframework.aop.MethodBeforeAdvice代表前置增强,因为Spring只支持方法级的增强,所以MethodBeforeAdvice是目前可用的前置增强,表示在目标方法执行前实施增强,而BeforeAdvice是为了将来版本扩展需要而定
- 后置增强:org.springframework.aop.AfterReturningAdvice代表后增强,表示在目标方法执行后实施增强;
- 环绕增强:org.aopalliance.intercept.MethodInterceptor代表环绕增强,表示在目标方法执行前后实施增强;
- 异常抛出增强:org.springframework.aop.ThrowsAdvice代表抛出异常增强,表示在目标方法抛出异常后实施增强;
基本代码
增强的方法
package com.tamakiakoo.advices;
public class TimeManager {
public void start() {
System.out.println("方法调用之前记录时间");
}
public void end() {
System.out.println("方法调用之后记录时间");
}
}
package com.tamakiakoo.advices;
public class TransactionManager {
public void begin(){
System.out.println("开启事务");
}
public void commit(){
System.out.println("提交事务");
}
}
我们先创建一个接口,IUserService
public interface IUserService {
public void add();
public void update();
}
创建一个接口的实现类
public class UserServiceImpl implements IUserService {
@Override
public void add() {
System.out.println("UserServiceImpl.add()");
}
@Override
public void update() {
System.out.println("UserServiceImpl.update()");
}
}
AOP联盟的使用
导包
创建一个代理类
前置增强:org.springframework.aop. MethodBeforeAdvice
后置增强:org.springframework.aop.AfterReturningAdvice
package com.tamakiakoo.advices;
import java.lang.reflect.Method;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
/**
* 前置增强
* @author dqk
*
*/
public class MyMethodBeforeAdvice implements MethodBeforeAdvice,AfterReturningAdvice {
private TimeManager tm;
private TransactionManager tx;
public MyMethodBeforeAdvice(TransactionManager tx,TimeManager tm){
this.tx = tx;
this.tm = tm;
}
@Override
public void before(Method arg0, Object[] arg1, Object arg2) throws Throwable {
tm.start();
tx.begin();
}
@Override
public void afterReturning(Object arg0, Method arg1, Object[] arg2, Object arg3) throws Throwable {
tx.commit();
tm.end();
}
}
测试类
package com.tamakiakoo.service.aop;
import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import com.tamakiakoo.advices.MyMethodBeforeAdvice;
import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;
import com.tamakiakoo.service.IUserService;
import com.tamakiakoo.service.impl.UserServiceImpl;
public class AOPTest {
@Test
public void test(){
// 1.创建目标对象
IUserService userService = new UserServiceImpl();
// 2.增强
TransactionManager tx = new TransactionManager();
TimeManager tm = new TimeManager();
// 3.创建代理类
ProxyFactory factory = new ProxyFactory();
// 3.1.设置目标对象
factory.setTarget(userService);
// 3.2.添加增强
factory.addAdvice(new MyMethodBeforeAdvice(tx, tm));
//创建代类对象
IUserService proxy = (IUserService)factory.getProxy();
proxy.add();
System.out.println("===========================");
proxy.update();
}
}
控制台
方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
===========================
方法调用之前记录时间
开启事务
UserServiceImpl.update()
提交事务
方法调用之后记录时间
环绕增强:org.aopalliance.intercept.MethodInterceptor
在接口中增加如下方法
public String query(String name);
在UserServiceImpl增加方法
@Override
public String query(String name) {
System.out.println("UserServiceImpl.query()====name:"+name);
return "hello";
}
MyMethodInterceptor
package com.qf.advices;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
/**
* 环绕通知
* @author Windows
*
*/
public class MyMethodInterceptor implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("环绕开始。。。。。");
Object result = methodInvocation.proceed(); // 往下调用
System.out.println("环绕结束");
return result;
}
}
测试类(可以同时添加多个增强)
@Test
public void test2(){
TransactionManager tx = new TransactionManager();
TimeManager tm = new TimeManager();
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.setTarget(new UserServiceImpl());
proxyFactory.addAdvice(new MyMethodBeforeAdvice(tx, tm));
proxyFactory.addAdvice(new MyMethodInterceptor());
UserServiceImpl userServiceImpl = (UserServiceImpl)proxyFactory.getProxy();
userServiceImpl.add();
System.out.println("==============================");
userServiceImpl.update();
System.out.println("=========================");
String query = userServiceImpl.query("zs");
System.out.println(query);
}
控制台
这里的顺序和
proxyFactory.addAdvice(new MyMethodBeforeAdvice(tx, tm));
proxyFactory.addAdvice(new MyMethodInterceptor());
的先后有关
方法调用之前记录时间
开启事务
方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
提交事务
方法调用之后记录时间
==============================
方法调用之前记录时间
开启事务
方法调用之前记录时间
开启事务
UserServiceImpl.update()
提交事务
方法调用之后记录时间
提交事务
方法调用之后记录时间
=========================
方法调用之前记录时间
开启事务
方法调用之前记录时间
开启事务
UserServiceImpl.query()====name:zs
提交事务
方法调用之后记录时间
提交事务
方法调用之后记录时间
hello
异常抛出增强:org.springframework.aop.ThrowsAdvice
package com.tamakiakoo.advices;
import java.lang.reflect.Method;
import org.springframework.aop.ThrowsAdvice;
/**
* 抛出异常增强,一般都用做资源的关闭或者事务回滚
* @author Windows
*
*/
public class MyThrowsAdvice implements ThrowsAdvice {
public void afterThrowing(Method method, Object[] args, Object target, Exception e) throws Throwable {
System.err.println("抛出异常通知");
System.out.println("method=" + method.getName());
System.out.println("抛出异常:" + e.getMessage());
System.out.println("成功回滚事务");
}
}
总结
增强(通知)
- 由AOP联盟提供的常见的增强
- 前置增强
- 后置增强
- 环绕增强
- 抛出异常增强
- 如何实现
- 定义自己的增强
- 通过ProxyFactory创建代理对象
- 设置目标对象
- 设置增强
- 创建代理类
创建切面
我们可能注意到一个问题:增强被织入到目标类的所有方法中,假设我们希望有选择地织入到目标类某些特定的方法中,就需要使用切点进行目标连接点的定位了。描述连接点是进行AOP编程最主要的工作
增强提供了连接点方位信息:如织入到方法前面、后面等,而切点进一步描述织入到哪些类的哪些方法上。
Spring通过org.springframework.aop.Pointcut接口描述切点,Pointcut由ClassFilter和MethodMatcher构成,它通过ClassFilter定位到某些特定类上,通过MethodMatcher定位到某些特定方法上,这样Pointcut就拥有了描述某些类的某些特定方法的能力。
Spring使用org.springframework.aop.Advisor接口表示切面的概念,一个切面同时包含横切代码和连接点信息
从广义上说,增强其实就是一种最简单的切面,它既包括横切代码也包括切点信息,只不过它的切点只是简单的方法相对位置的信息。所以增强一般需要和切点联合才可以表示一个更具实用性的切面。
什么是切面???
包含两个关键信息(切点+增强)
AspectJ(切面)
Spring AOP,它包括基于XML配置的AOP和基于@AspcetJ注解的AOP,这两种方法虽然在配置切面时的表现方式不同,但底层都是采用动态代理技术(JDK代理或CGLib代理)。Spring可以集成AspectJ,但AspectJ本身并不属于Spring AOP的范畴。
Spring在处理@Aspect注解表达式时,需要将Spring的asm模块添加到类路径中。asm是轻量级的字节码处理框架,因为Java的反射机制无法获取入参名,Spring就利用asm处理@AspectJ中所描述的方法入参名。
AspectJ-基础语法
…注解 | 说明 |
---|---|
@Before | 前置增强,相当MethodBeforeAdvice的功能,比如@Around(value=“execution(public * add(…))”)只有匹配了表达式的方法才会增强 |
@AfterReturning | 后置增强,相当于AfterReturningAdvice,目标方法中出现异常不执行 |
@Around | 环绕增强,相当于MethodInterceptor |
@AfterThrowing | 抛出增强,相当于ThrowsAdvice |
@After | Final增强,不管是抛出异常或者是正常退出,该增强都会得到执行,该增强没有对应的增强接口,可以把它看成ThrowsAdvice和AfterReturningAdvice的混合物,一般用于释放资源,相当于try{}finally{} |
@Pointcut | 切入点表达式 |
@Aspect | 说明这是一个切面类 |
定义多个切入点
@Pointcut(value="execution(* com.qf.service.IUserService.add(..))")
public void p1(){
}
@Pointcut(value="execution(* com.qf.service.IUserService.update(..))")
public void p2(){
}
@Before(value="p1() || p2()")
public void begin(){
tm.start();
tx.begin();
}
切点表达式函数
AspectJ 5的切点表达式由关键字和操作参数组成,如execution(* add(..))
的切点表达式,execution为关键字,而“* add(..)”
为操作参数。
常见案例 | 说明 |
---|---|
execution(public * *(..)) | 匹配所有目标类的public方法,第一个代表返回类型;第二个代表方法名;而…代表任意入参的方法; |
execution(* *To(..)) | 匹配目标类所有以To为后缀的方法。第一个代表返回类型;而To代表任意以To为后缀的方法。 |
execution(* com.dream.IUserDao.*(..)) | 匹配IUserDao接口的所有方法,第一个*代表返回任意类型;com.dream.IUserDao.*代表IUserDao接口中的所有方法,public可以不写默认就是public |
execution(*com.dream.*(..)) | 匹配com. dream包下所有类的所有方法; |
表达式:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
有“?”号的部分表示可省略的,
- modifers-pattern表示修饰符如public、protected等,
- ret-type-pattern表示方法返回类型,
- declaring-type-pattern代表特定的类,
- name-pattern代表方法名称,
- param-pattern表示参数,
- throws-pattern表示抛出的异常。
在切入点表达式中,可以使用*来代表任意字符,用…来表示任意个参数
使用方法
首先导入org. org.aspectj下jar包(并不需要配置注解扫描)
使用注解的方式
MyAspectj类
定义一个切面,add,update需要加增强,query不要
package com.tamakiakoo.aspectj;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;
/**
* 定义一个切面,add,update需要加增强,query不要
* @author dqk
*
*/
@Aspect
public class MyAspectj {
private TimeManager tm = new TimeManager();
private TransactionManager tx = new TransactionManager();
@Pointcut(value = "execution(* com.tamakiakoo.service.*.add(..))")
public void p1(){}
@Pointcut(value = "execution(* com.tamakiakoo.service.*.updete(..))")
public void p2(){}
@Before(value = "p1() || p2()")
public void before(){
tm.start();
tx.begin();
}
@After(value = "p1() || p2()")
public void after(){
tx.commit();
tm.end();
}
}
测试类
package com.tamakiakoo.service.aop;
import org.junit.Test;
import org.springframework.aop.aspectj.annotation.AspectJProxyFactory;
import com.tamakiakoo.aspectj.MyAspectj;
import com.tamakiakoo.service.IUserService;
import com.tamakiakoo.service.impl.UserServiceImpl;
public class AspectjTest {
@Test
public void test(){
// 1.创建目标对象
IUserService userService = new UserServiceImpl();
// 3.创建代理类
AspectJProxyFactory factory = new AspectJProxyFactory();
// 3.1 设置目标对象
factory.setTarget(userService);
// 3.2设置切面
factory.addAspect(new MyAspectj());
// 3.3创建代理类
IUserService proxy = (IUserService)factory.getProxy();
proxy.add();
System.out.println("======================");
proxy.update();
System.out.println("======================");
proxy.query("11");
}
}
控制台
方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
======================
UserServiceImpl.update()
======================
UserServiceImpl.query()====name:11
使用xml的方式
增加约束
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd"
在多个表达式之间使用 ||,or表示 或,使用 &&,and表示 与,!表示 非.例如:
<aop:config>
<aop:pointcut id="pointcut" expression="(execution(* com.ccboy.dao..*.find*(..))) or (execution(* com.ccboy.dao..*.query*(..)))"/>
<aop:advisor advice-ref="jdbcInterceptor" pointcut-ref="pointcut" />
</aop:config>
完整的applicationContext.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"
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">
<!-- 1.目标对象 -->
<bean id="userService" class="com.tamakiakoo.service.impl.UserServiceImpl" />
<!-- 2.创建增强 -->
<bean id="tm" class="com.tamakiakoo.advices.TimeManager" />
<bean id="tx" class="com.tamakiakoo.advices.TransactionManager" />
<!-- 3.创建切面 -->
<bean id="myAspectj2" class="com.tamakiakoo.aspectj.MyAspectj2">
<property name="tm" ref="tm"></property>
<property name="tx" ref="tx"></property>
</bean>
<!-- 4.AOP的配置 -->
<aop:config>
<aop:pointcut expression="execution(* com.tamakiakoo.service.*.add(..)) or execution(* com.tamakiakoo.service.*.update(..))" id="p1" />
<aop:aspect ref="myAspectj2"><!-- 引用的是哪个切面 -->
<aop:after method="after" pointcut-ref="p1" /><!-- 切点只能是唯一的一个不能出现pointcut-ref="p1,p2" 并且pointcut-ref和pointcut=""不能同时出现 -->
<aop:before method="before" pointcut-ref="p1"/>
</aop:aspect>
</aop:config>
</beans>
package com.tamakiakoo.service.aop;
import org.junit.Test;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.tamakiakoo.advices.MyMethodBeforeAdvice;
import com.tamakiakoo.advices.MyMethodInterceptor;
import com.tamakiakoo.advices.TimeManager;
import com.tamakiakoo.advices.TransactionManager;
import com.tamakiakoo.service.IUserService;
import com.tamakiakoo.service.impl.UserServiceImpl;
public class AOPXmlTest {
@Test
public void test(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
IUserService userService = (IUserService) applicationContext.getBean("userService");
userService.add();
System.out.println("===================================");
userService.update();
System.out.println("===================================");
userService.query("heheh..");
}
}
方法调用之前记录时间
开启事务
UserServiceImpl.add()
提交事务
方法调用之后记录时间
===================================
方法调用之前记录时间
开启事务
UserServiceImpl.update()
提交事务
方法调用之后记录时间
===================================
UserServiceImpl.query()====name:heheh..
总结
- xml的方式配置aop的话 只有匹配了表达式就会自动生成代理对象并不需要设置目标对象
- aop:config
- aop:pointcut
- expression:表达式
- 在多个表达式之间使用 ||,or表示 或,使用 &&,and表示 与,!表示 非
- 例如:
expression="execution(* com.tamakiakoo.service.*.add(..)) or execution(* com.tamakiakoo.service.*.update(..))"
- id:表达式的唯一表示
- expression:表达式
- <aop:aspect ref=“myAspectj2”> ref引用的是哪个切面
- ref:引向切面
- aop:after
- method:切面中的方法名称
- pointcut-ref:表达式ID
- 切点只能是唯一的一个不能出现pointcut-ref=“p1,p2”
- pointcut-ref和pointcut=""不能同时出现
- aop:pointcut