AOP 即 Aspect Oriented Program 面向切面编程
首先,在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能。
所谓的核心业务,比如登陆,增加数据,删除数据都叫核心业务
所谓的周边功能,比如性能统计,日志,事务管理等等
周边功能在Spring的面向切面编程AOP思想里,即被定义为切面
在面向切面编程AOP的思想里面,核心业务功能和切面功能分别独立进行开发
然后把切面功能和核心业务功能 "编织" 在一起,这就叫AOP
常用术语
join point
连接点(join point)中文翻译大致意思是join point在spring aop中作为一种方法或者是对报错的处理。比如说刚举的例子,切面log是基于f方法的,而这个f方法就是连接点,我个人理解就是切面和主逻辑的连接处。
Advice
也就是增强,为了实现log日志,得编写一些逻辑代码Logic,但此时,我们需要在不同的连接点编写代码,可以说advice决定了在原有方法功能中出现的位置和时机。可分为around before after
Pointcut
可以与任何join point点进行匹配,匹配后的位置就是切面的位置。
target object
顾名思义,目标对象
如上图,f方法里实现的m功能再进行一些修改后(或者说增强)实现了n功能,而target object,也就是基础对象,增强后的方法f,则是被存在代理对象proxy object中。
AOP Proxy
aop代理,也就是为了实现方法增强而编写的对象,编写方式有jdk动态代理等,在我的另一篇文章也有写到。
Weaving
编织,也就是把切面应用到目标对象来创建新的代理对象的过程。也就是把上图的target object通过切面穿件proxy object的过程。
实现方式
先把测试用的类创好
package service;
public class ProductService {
public void doSomeService(){
System.out.println("doSomeService");
}
}
public class TestAOP {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(new String("applicationContext.xml"));
ProductService s = (ProductService) context.getBean("s");
s.doSomeService();
}
}
通过Spring API实现1
创建一个LoggerAspect类
package aspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class LoggerAspect {
public Object log(ProceedingJoinPoint joinPoint) throws Throwable{
System.out.println("start log: "+joinPoint.getSignature().getName());
Object object = joinPoint.proceed();
System.out.println("end log: "+joinPoint.getSignature().getName());
return object;
}
}
在xml文件中添加以下
<bean name="s" class="service.ProductService"></bean>
<bean id="loggerAspect" class="aspect.LoggerAspect"/>
<aop:config>
<aop:pointcut id="loggerCutPoint" expression="execution(* service.ProductService.*(..)) "/>
<aop:aspect id="logAspect" ref="loggerAspect">
<aop:around pointcut-ref="loggerCutPoint" method="log"/>
</aop:aspect>
</aop:config>
解释一下
config中,pointcut是切点,id是标识符(用于下面的pointcut-ref),expression是执行表达式,也就是触发的条件
执行表达式的格式如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
除了返回类型模式(上面代码片断中的ret-type-pattern),名字模式和参数模式以外,所有的部分都是可选的。 返回类型模式决定了方法的返回类型必须依次匹配一个连接点。 你会使用的最频繁的返回类型模式是 * ,它代表了匹配任意的返回类型。 一个全称限定的类型名将只会匹配返回给定类型的方法。名字模式匹配的是方法名。 你可以使用 * 通配符作为所有或者部分命名模式。 参数模式稍微有点复杂:() 匹配了一个不接受任何参数的方法, 而 (..) 匹配了一个接受任意数量参数的方法(零或者更多)。 模式 () 匹配了一个接受一个任何类型的参数的方法。 模式 (,String) 匹配了一个接受两个参数的方法,第一个可以是任意类型,第二个则必须是String类型。
一些常见切入点表达式的例子。execution(public * (..)) 任意公共方法的执行;
execution( set(..)) 任何一个以“set”开始的方法的执行;
execution( com.xyz.service.AccountService.(..)) AccountService接口的任意方法的执行;
execution( com.xyz.service..(..)) 定义在service包里的任意方法的执行;
execution(* com.xyz.service...(..)) 定义在service包或者子包里的任意方法的执行;
aop:aspect标签中注册了相应的切面,子标签around说明了是包围式的
可以看到LoggerAspetc类中
System.out.println("start log: "+joinPoint.getSignature().getName());
Object object = joinPoint.proceed();
System.out.println("end log: "+joinPoint.getSignature().getName());
中间的那句就是将来与某个核心功能编织之后,用于执行核心功能的代码
结果:
通过Spring API实现2
创建Log类和LogAfter类,实现相应的接口
package aspect;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(o.getClass().getName()+"的"+method.getName()+"执行了");
}
}
package aspect;
import org.springframework.aop.AfterReturningAdvice;
import java.lang.reflect.Method;
public class LogAfter implements AfterReturningAdvice {
@Override
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println("执行了"+o1.getClass().getName()+"的"+method.getName()+"结束了");
}
}
修改xml
<bean name="s" class="service.ProductService"></bean>
<bean id="logBefore" class="aspect.Log"/>
<bean id="logAfter" class="aspect.LogAfter"/>
<aop:config>
<aop:pointcut id="loggerCutPoint" expression="execution(* service.ProductService.*(..)) "/>
<aop:advisor advice-ref="logBefore" pointcut-ref="loggerCutPoint"/>
<aop:advisor advice-ref="logAfter" pointcut-ref="loggerCutPoint"/>
</aop:config>
解释一下
和第一个方式不同的是,这里用了aop:advisor,这是通过Spring提供的API实现的。
因为Log类和LogAfter类实现了MethodBeforeAdvice和AfterReturningAdvice方法,所以可以在核心代码执行前后进行输出。
自己实现Aspect
上面这种方法,可能程序复杂以后就会出现记不住接口的情况
记录下第三种方法
做一个MyLog.java
package aspect;
public class MyLog {
public void before(){
System.out.println("-----before-----");
}
public void after(){
System.out.println("-----after------");
}
}
修改xml
<bean name="s" class="service.ProductService"></bean>
<bean id="myLogger" class="aspect.MyLog"/>
<aop:config>
<aop:pointcut id="loggerCutPoint" expression="execution(* service.ProductService.*(..)) "/>
<aop:aspect id="logAspect" ref="myLogger">
<aop:before method="before" pointcut-ref="loggerCutPoint"/>
<aop:after method="after" pointcut-ref="loggerCutPoint"/>
</aop:aspect>
</aop:config>
这里通过aop:before和aop:after分别置顶method来包围核心代码