一、AOP定义:
AOP面向切面编程,主要作用是将业务中无法通过纵向继承来实现的通用功能分离出来或者在不改变原有业务逻辑的情况下添加功能。把分离的功能封装成通用模块,哪里需要就切入哪里。这种实现的方式显而易见就是利用代理模式完成模块的插入。
实现AOP的框架:AspectJ:在编译期提供代码的织入、SpringAOP:在运行期通过代理的方式向目标类织入增强代码。
实现AOP的方式:利用@AspectJ和xml注解的方式。
AOP的应用场景:权限验证、记录跟踪、性能监控、事务。其他的还没有想到。
二、理解AOP一些术语:
1.连接点(joinpoint):spring只提供了在方法级别的操作,连接点就是需要插入的目标方法,Spring可以通过配置通配符的方式定义扫描多个连接点。
2.切点(Pointcut):就是配置的连接点–一个切点可以有多个连接点
3.增强:增强就是切入到连接点的内容代码,spring提供了增强的接口都是带方位的,BeforeAdvice在连接点方法前执行切入的代码。还有几个方法后切入增强,环绕切入增强,异常抛出增强,引介增强。
4.切面:单独分离出来的代码类就行程了切面,切面需要定义切点和实现增强。
5.还有目标对象、代理、织入:AOP的实现操作就是织入。这些概念知道就可以了,没必要去死记硬背。
总结:SpringAOP通过切点指定在哪些类的哪些方法上织入横切逻辑,通过增强描述横切逻辑和方法的具体是织入点(方法前、后、两端等)。此外Spring通过切面将切点和增强逻辑组装起来。
三、两种代理实现
1.JDK代理:目标类必须实现接口。
public interface TargetInterface {
public void removeTopic(int topic);
public void removeForoum(int foroum);
}
public class TargetInterfaceImpl implements TargetInterface {
@Override
public void removeTopic(int topic) {
System.out.println("removeTopic");
}
@Override
public void removeForoum(int foroum) {
System.out.println("foroum");
}
}
public class ProxyTarget implements InvocationHandler {
Object object;
public void setObject(Object object) {
this.object = object;
}
public Object getObject() {
return object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(“---”);
Object obj= method.invoke(object,args);
System.out.println(“----”);
return obj;
}
}
2.Cglib:采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截父类方法的调用。cglib代理无法增强被final修饰的类且必须有无参构造方法。
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer=new Enhancer();
public Object getProxy(Class clazz){
//设置目标类-父类
enhancer.setSuperclass(clazz);
//设置回调--方法拦截器
enhancer.setCallback(this);
//创建代理类--子类
return enhancer.create();
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("===");
//代理增强
Object obj=methodProxy.invokeSuper(o,objects);
return obj;
}
}
public class CglibProxyTest {
@Test
public void test(){
//代理流程--通过代理类去访问目标类--首先创建子类作为代理类--调用经过方法拦截器--目标类
CglibProxy cglibProxy=new CglibProxy();
TargetInterface targetInterface=(TargetInterface)cglibProxy.getProxy(TargetInterfaceImpl.class);
targetInterface.removeTopic(10);
}
}
Cglib在生成代理类过程中会为目标类和方法都生成一个索引,在代理类执行过程中会在invokeSuper(o,objects);方法中通过方法的索引去get查找方法,不是反射。
四、@Aspect和xml配置使用AOP
在不使用@Aspect注解时切面需要实现对应的接口来实现增强如下(只举一个例子)
/**
* @Description
* @Date 2020/3/25 13:35
* @Author cly
**/
public class AopForInterface implements MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice, MethodInterceptor {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
}
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return null;
}
}
@Aspect注解的类定义为切面类,里面将包含切点和增强方法。实例如下
public interface Waiter {
public void greetTo(String clientName);
public void serverTo(String clientName);
}
/**
* @Description
* @Date 2020/3/25 12:07
* @Author cly
**/
public class NativeWaiter implements Waiter {
@Override
public void greetTo(String clientName) {
System.out.println("NativeWarter:greet to:"+clientName+"--");
}
@Override
public void serverTo(String clientName) {
System.out.println("NativeWarter:serving to:"+clientName+"--");
}
}
/**
* @Description
* @Date 2020/3/25 11:56
* @Author cly
**/
@Aspect
public class PreGreetingAspect {
@Before(value = "execution(* greetTo(..))")
public void beforeGreeting(){
System.out.println("hello word");
}
}
/**
* @Description
* @Date 2020/3/25 12:13
* @Author cly
**/
public class AopTest {
@Test
public void test(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Waiter waiter=(Waiter)applicationContext.getBean("nativeWarter");
waiter.greetTo("how are you");
}
}
applicationContext.xml
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 开启注解AOP -->
<aop:aspectj-autoproxy proxy-target-class="false"/>
<bean id="nativeWarter" class="spring.aop.aspectJ.NativeWaiter"></bean>
<bean class="spring.aop.aspectJ.PreGreetingAspect"></bean>
@Aspect切点表达式函数
- 方法切点函数:通过描述目标类方法信息定义连接点 execution()–@annotation()
- 方法入参切点函数:通过描述目标类方法入参的信息定义连接点 args()–@args()
- 目标类切点函数:通过描述目标类类型信息定义连接点 within()–@within()–target()–@target()
- 代理类切点函数:通过描述目标类的代理类的信息定义链接点 @this()
不同的增强类型:
-
@Before 相当于BeforeAdvice的功能,Before 注解类拥有两个成员:
value:该成员用于定义切点
argNames:由于无法通过java反射机制获取方法的入参名,所以如果再java编译时未启用调试信息或者需要在运行期解析切点,就必须通过这个成员指定注解所标注的增强方法的参数名(注意两者名字必须完全相同),多个参数名用逗号分隔。 -
@AfterReturning 后置增强相当于AfterReturningAdvice,该注解有四个成员
value:该成员用于定义切点
pointcut:表示切点的信息,如果显示指定pointcut值,它将覆盖value的设置值
returning:将目标对象方法的返回值绑定给增强方法的输入参数
argNames:同上 -
@Aroud 环绕增强,相当于MethodInterceptor,该注解有两个成员:
value 和argNames:同上 -
@AfterThrowing 抛出增强,相当于ThrowsAdvice,该注解有四个成员:
value–pointcut --argNames:同上
throwing:将抛出的异常绑定到增强方法中。 -
@After Final增强,不管是抛出异常还是正常退出,该增强都会执行,该增强没有对应的增强接口。一般用于释放资源。
-
@DeclareParents 引介增强,相当于IntroductionInterceptor,该增强有两个成员:
value:定义切点,它的不同是这个value表示在哪个目标类上添加引介增强。
defaultImpl 默认的接口实现类。除了引介增强其他都应该容易理解。
引介增强解释一下:引介增强通过defaultImpl可以知道它可以给目标类添加接口实现。
示例:
@Aspect
public class PreGreetingAspect {
@Before(value = "execution(* greetTo(..))")
public void beforeGreeting(){
System.out.println("hello word");
}
//添加引介增强
@DeclareParents(value = "spring.aop.aspectJ.NativeWaiter",defaultImpl = SellerImpl.class)
public Seller seller;
}
public class AopTest {
@Test
public void test(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
Waiter waiter=(Waiter)applicationContext.getBean("nativeWarter");
waiter.greetTo("how are you");
Seller seller=(Seller) waiter;
seller.sell("john","beer");
}
}
在切面中定义一个引介增强,意思就是将Seller接口的实现类SellerImpl引介增强到NativeWaiter目标类中,这样NativeWaiter就实现了Seller接口。在test中可以强转为Seller.
切点函数解释:
这里主要记录一下execution(),这是最常用的一个切点函数。语法格式:execution(<修饰符模式>?<返回类型模式><方法名模式数>(<参数模式>))<异常模式>?),除了返回类型模式、方法名模式、参数模式其他都是可选的。
- 通过方法签名定义切点:execution(public* *(…)):匹配所有目标类中的public方法。
- 通过类定义切点:execution(* spring.aop.aspectJ.*(…)):匹配aspectJ的所有方法,第一代表所有返回类型。第二个代表所有方法。…代表参数。
- 通过类包定义切点:"."表示包下所有的类,而“…”表示包、子包下的所有类。execution(spring.aop.aspectJ.(…))、execution(spring.aop.aspectJ…(…))。
- args(name,num):绑定连接点参数,将name、num绑定在增强方法的参数中名字必须相同,
- 其他args()、 within()、target()、this()用时去查。
Spring AOP–Interceptor
Spring MVC中的拦截器也是根据AOP思想创建的,MVC中拦截器只能基于webController方法级别的拦截,可以获取spring 容器内加载的bean,可以更细粒度的操作请求。拦截器不是 Spring Aop 动态代理实现的,主要采用责任链和适配器的设计模式来实现,直接嵌入到 SpringMVC 入口代码里面。
流程分析:
浏览器请求–>
DispatcherServlet 执行调用 doService(request, response) 作为 Servlet 主要执行者,doService(request, response) 通过调用 doDispatch(request, response) 来真正执行请求处理。
doDispatch(request, response) 中完成拦截器的添加和拦截器拦截处理,
通过 getHandler(HttpServletRequest request) 获取到 HandlerExecutionChain 处理器执行链,将拦截器注入到 HandlerExecutionChain 的属性中。
分别调用 HandlerExecutionChain 的三个方法,applyPreHandle、applyPostHandle、triggerAfterCompletion,
实现前置拦截/请求提交拦截和请求完成后拦截。
使用责任链的设计模式,实际调用的是HandleInterceptor的三个接口,分别对应preHandle、postHandle、afterCompletion
public class HandlerExecutionChain {
private final Object handler;
@Nullable
private HandlerInterceptor[] interceptors;
@Nullable
private List<HandlerInterceptor> interceptorList;
private int interceptorIndex;
/**
按照列表中interceptor的顺序来执行它们的preHandle方法,直到有一个返回false。
true:表示继续流程(如调用下一个拦截器或处理器)
返回false后:表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,
调用triggerAfterCompletion方法,此时this.interceptorIndex指向上一个返回true的interceptor的位置,
所以它会按逆序执行所有返回true的interceptor的afterCompletion方法。
*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
HandlerInterceptor interceptor = interceptors[i];
if (!interceptor.preHandle(request, response, this.handler)) {
this.triggerAfterCompletion(request, response, (Exception)null);
return false;
}
}
}
return true;
}
/**
按照逆序执行所有interceptor的postHandle方法
*/
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for(int i = interceptors.length - 1; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
interceptor.postHandle(request, response, this.handler, mv);
}
}
}
/**
从最后一次preHandle成功的interceptor处逆序执行afterCompletion方法。
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) throws Exception {
HandlerInterceptor[] interceptors = this.getInterceptors();
if (!ObjectUtils.isEmpty(interceptors)) {
for(int i = this.interceptorIndex; i >= 0; --i) {
HandlerInterceptor interceptor = interceptors[i];
try {
interceptor.afterCompletion(request, response, this.handler, ex);
} catch (Throwable var8) {
logger.error("HandlerInterceptor.afterCompletion threw exception", var8);
}
}
}
}
}