spring的两大特其中就存在一个AOP,AOP就是面向切面编程,在开发中,可以把一些公共功能拆分出来,然后做成一个切面,这个每个经过切面的功能就都会进行增强,例如日志收集、权限拦截、spring自身提供的事务等等。
一、动态代理
说到AOP,就不得不说一个重要的概念,就是动态代理,动态代理是指代理类对象在程序运行时由 JVM 根据反射机制动态生成的,可以再不关心和改变原来方法功能的基础上,进行功能的增强。现在各大主流框架中都使用到动态代理。比较流行的两种动态代理的方式分别是:JDK原生动态代理、CGLIB动态代理。
JDK动态代理
在使用jdk动态代理之前需要说明几点:
1、代理类必须实现接口
2、两个比较重要的接口和方法 InvocationHandler、Proxy,InvocationHandler主要运用反射机制调用代理类的方法,同时可以在方法之前和之后进行增强。Proxy方法负责创建代理对象。
3、因为jdk的动态代理是基于接口的,就会出现代理对象不能用子类进行接受(也就是猴子是动物但不是斑马)
下面看代码演示:
接口:
实现类:
InvocationHandler实现类:
PS:需要注意 Proxy.newProxyInstance方法的参数和InvocationHandler中invoke的参数
package com.tes.mgf.proxy.jdk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class AnimalProxy implements InvocationHandler {
private Object target;
public AnimalProxy(Object target){
this.target = target;
}
public <T> T getPorxy(){
/**
* 使用jdk Proxy.newProxyInstance 方法创建动态代理对象
* 参数
* 1、类加载器
* 2、类的接口
* 3、invocationhandler的实现类
*/
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this);
}
/**
* 实现方法
* @param proxy 代理对象
* @param method 调用的方法
* @param args 调用方法的参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
public void before(){
System.out.println("方法执行之前!go");
}
public void after(){
System.out.println("方法执行之后!");
}
}
测试:
Cglib动态代理
在使用Cglib动态代理需要注意一下几点:
1、Cglib动态代理使用的是类代理(原理为继承),所以相对JDK动态代理而言更加灵活,但是需要引入第三方jar包
2、重要的两个类:MethodInterceptor接口和Enhancer类,MethodInterceptor接口相当于InvocationHandler,而Enhancer相当于Proxy用于创建代理对象。
3、Cglib是无法代理final的方法的,也可以理解上去因为使用的继承代理类的方式。
下面看下实例:
代理类:
MethodInterceptor 实现类:
package com.tes.mgf.proxy.cglib;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibProxy implements MethodInterceptor {
private Object target;
public CglibProxy(Object target){
this.target = target;
}
public <T> T getProxy(){
Enhancer enhancer = new Enhancer();
//回调函数 拦截器
enhancer.setCallback(this);
//设置代理对象的父类
enhancer.setSuperclass(this.target.getClass());
return (T) enhancer.create();
}
/**
* @param o 代理对象
* @param method 代理对象的方法
* @param objects 代理对象的参数
* @param methodProxy 代理方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
before();
Object result = methodProxy.invokeSuper(o, objects);
after();
return result;
}
public void before(){
System.out.println("方法执行之前!go");
}
public void after(){
System.out.println("方法执行之后!");
}
}
测试:
FastClass机制:
代理类和被代理类个生成一个class文件,会给方法生成一个index(位置)参数,这样FastClass可以直接调用方法,而不需要进行反射。
两种代理方式的区别:
1、JDK代理是接口代理(也就是被代理的类必须实现接口),Cglib是集成代理
2、JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,JDK效率更高
3、JDK使用反射机制,Cglib使用的是FastClass机制,Cglib相对相率更高
二、AOP
基本概念:传送门
spring aop实现依然有两种方式,第一种是基于配置文件的,第二种是基于注解的,下面我们采用基于注解方式简单演示一下
代码演示:
1、开启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:p="http://www.springframework.org/schema/p"
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">
<context:component-scan base-package="com.tes.mgf"/>
<!-- 开启spring aop注解方式 -->
<aop:aspectj-autoproxy/>
</beans>
2、引入maven坐标:
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.13</version>
</dependency>
3、编写代码:
package com.tes.mgf.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogAspect {
/**
* 切点 - 根据表达式可以进行自定义的拦截
*/
@Pointcut("execution(* com.tes..*.*(..))")
public void pointCut(){}
/**
* 增强方法,在利用代理调用原方法的前后,可以进行增强
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("执行前。。。。。。。。。。。。");
Object result = proceedingJoinPoint.proceed();
System.out.println("执行后。。。。。。。。。。。。");
return result;
}
}
切点的拦截分为以下(其他人总结直接拿过来了):
- execution():用于匹配方法执行的连接点
- args(): 用于匹配当前执行的方法传入的参数为指定类型的执行方法
- this(): 用于匹配当前AOP代理对象类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能包括引入接口也类型匹配;
- target(): 用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就不包括引入接口也类型匹配;
- within(): 用于匹配指定类型内的方法执行;
- @args():于匹配当前执行的方法传入的参数持有指定注解的执行;
- @target():用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解;
- @within():用于匹配所以持有指定注解类型内的方法;
- @annotation:用于匹配当前执行方法持有指定注解的方法;
需要注意的是,execution是一种表达式,可以根据自己的需要,对不通的方法、类、包等进行拦截。
使用注解作为切点:
package com.tes.mgf.aop;
import com.tes.mgf.annotation.LogAnnotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogAspect {
/**
* 切点 - 根据表达式可以进行自定义的拦截
*/
@Pointcut("@annotation(com.tes.mgf.annotation.LogAnnotation)")
public void pointCut(){}
/**
* 增强方法,在利用代理调用原方法的前后,可以进行增强
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("pointCut() && @annotation(logAnnotation)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, LogAnnotation logAnnotation) throws Throwable {
System.out.println("执行前。。。。。。。。。。。。" + logAnnotation.value());
Object result = proceedingJoinPoint.proceed();
System.out.println("执行后。。。。。。。。。。。。");
return result;
}
}
@Around 通知的注解,编写方式也有很多,类似于表达式,上面第一个是切点 && 后面就是切点的注解。
也可以简写,省略切点:
package com.tes.mgf.aop;
import com.tes.mgf.annotation.LogAnnotation;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Component
@Aspect
public class LogAspect {
/**
* 增强方法,在利用代理调用原方法的前后,可以进行增强
* @param proceedingJoinPoint
* @return
* @throws Throwable
*/
@Around("@annotation(com.tes.mgf.annotation.LogAnnotation) && @annotation(logAnnotation)")
public Object around(ProceedingJoinPoint proceedingJoinPoint, LogAnnotation logAnnotation) throws Throwable {
System.out.println("执行前。。。。。。。。。。。。" + logAnnotation.value());
Object result = proceedingJoinPoint.proceed();
System.out.println("执行后。。。。。。。。。。。。");
return result;
}
}
AOP这里面写法还是比较多的,没必要全部都记住,眼熟即可。