Spring Framework 之AOP
问题
什么是AOP?
AOP的好处是什么?
AOP的实现方式有哪些?
Spring AOP 与 AspectJ AOP的区别?
AOP概述
AOP(Aspect-Oriented Programming)翻译为中文就是“面向切面编程”。从“面向过程”到“面向对象”编程,编程思想的发展永远是朝更自然、更优雅地描述世界的方向发展。“面向切面“的出现也是这一目的,它是对面向对象的补充,使编程语言能够更好地描述世界。
现实编程中无法将重复出现的代码抽取至父类中。例如鉴权模块、监控模块、日志记录模块等,我们无法将公共代码块纵向抽取。那么如何横向抽取重复代码呢?AOP就能够通过横向抽取机制解决无法纵向抽取的问题,将分散在业务代码中的公共代码块抽取至一个独立的模块中。这也体现了设计模式中的“单一职责“的思想。
AOP知识
1、连接点(Joinpoint)
程序执行的某个特定位置。例如类初始化前后、函数调用前后、函数抛异常后等。类或代码块具有边界性质的特定点就成为“连接点”。
2、切点(PointCut)
每个类或函数都可以认为是连接点,我们如何定位我们关注的“连接点“?我们不需要为每个类或函数添加Advice,PointCut就是通过规则为我们关注的joinpoint添加Advice。
3、增强(Advice)
由aspect添加到特定的Join point的代码块。
4、目标对象(Target)
增强逻辑织入的目标类。
5、引介(Introduction)
引介是一种特殊的增强,他为类添加一些属性和方法。
例子:https://blog.csdn.net/u010599762/article/details/80182178
6、织入(Weaving)
织入是将增强添加至目标类的具体连接点上的过程。
7、代理(Proxy)
类被织入增强后就会产生新的结合了原类与增强的代理类。在Spring AOP中有两种代理,分别是JDK动态代理和CGLib动态代理。
8、切面(Aspect)
切面由切点和增强组成,包括增强的横切逻辑和连接点。Spring AOP负责将切面中的增强逻辑织入指定的连接点中。
代理
静态代理
代理模式
代理模式提供对目标对象进行访问方式,即通过代理对象访问目标对象。可以在目标对象的基础上增强,提供个性功能,达到扩招目标对象功能的作用。
接口
public interface Subject {
void request();
}
具体实现
public class RealSubject implements Subject {
public void request() {
//业务逻辑
}
}
代理类
public class Proxy implements Subject {
//要代理的实现类
private Subject subject = null;
public Proxy() {
this.subject = new Proxy();
}
//通过构造函数传递代理者
public Proxy(Object... objects) {
}
//实现接口定义的方法
public void request() {
this.before();
this.subject.request();
this.after();
}
//预处理
public void before() {
}
//后处理
public void after() {
}
}
动态代理
JDK动态代理
JDK动态代理设计到java.lang.relect包中的两个类:Proxy和InvocationHandler,InvocationHandler可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑与业务逻辑编织到一起。Proxy利用InvocationHandler动态创建某一符合该接口的实例,生成目标类的代理对象。
public class Monitor {
public static void begin(){
System.out.println("before");
}
public static void end(){
System.out.println("after");
}
}
public interface CouponService {
void getCoupon();
}
public class CouponServiceImpl implements CouponService {
public void getCoupon() {
//Monitor.begin();
try {
System.out.println("业务代码");
} catch (Exception e) {
throw new RuntimeException();
}
//Monitor.end();
}
}
public class PerformanceHandler implements InvocationHandler {
//被代理对象
private Object target;
public PerformanceHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Monitor.begin();
Object object = method.invoke(target, args);
Monitor.end();
return object;
}
}
public class Client {
public static void main(String[] args) {
//被代理对象
CouponService target = new CouponServiceImpl();
//让PerformanceHandler将监视横切逻辑编织到CouponService中
PerformanceHandler performanceHandler = new PerformanceHandler(target);
//通过Proxy的newProxyInstace()方法,为编织了业务逻辑与监控逻辑的handler创建一个符合CouponService接口的代理实现
CouponService proxy = (CouponService) Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),performanceHandler);
proxy.getCoupon();
}
}
CGLIB动态代理
JDK创建代理只能为接口创建代理,实际开发中我们很难保证每个类都有其对应的接口,对于没有通过接口定义业务方法的类,JDK已经没法对其进行代理,这就出现了Cglib,通过字节码技术,为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并织入横切逻辑。
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("before");
//通过代理类调用父类中的方法
Object result = methodProxy.invokeSuper(o, objects);
System.out.println("after");
return result;
}
}
public class Client {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
//通过冬天生成子类的方式创建代理类
CouponServiceImpl couponService = (CouponServiceImpl) proxy.getProxy(CouponServiceImpl.class);
couponService.getCoupon();
}
}
静态代理与动态代理区别
(1)静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
(2)动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
JDK动态代理 与CGLIB代理区别
Cglib所创建的动态代理,性能要比jdk创建的动态代理高。但对用Cglib创建代理的时间,JDK动态代理显然要快很多。对于无需单例的代理对象或实例池可以使用CGLib来创建代理(无需频繁创建),反之使用JDK动态代理。
@AspectJ
AspectJ是语言级使用Java注解来AOP实现的一种方式,扩招Java语言,定义AOP语法,能够在编译期间提供横切代码织入。
切面定义
Java Configuration方式配置
@Configuration
@EnableAspectJAutoProxy
public class AOPConfig {
}
xml方式配置
<aop:aspectj-autoproxy/>
切面声明
@Component
@Aspect
public class ControllerIntercept {
}
通过@Aspect将ControllerIntercept标识成一个切面,通过@Component将ControllerIntercept标识为一个Bean,Spring框架会收集@Aspect标注的Bean,并将其添加至Spring AOP中。
切点声明
@Pointcut("execution(public * com.ljw.discern_spider.controller.DiscernController.discern(..))")
public void discernWeb() {
}
一个 pointcut 的声明由两部分组成:
- 一个方法签名, 包括方法名和相关参数
- 一个 pointcut 表达式, 用来指定哪些方法执行是我们感兴趣的(即因此可以织入 advice).
切点函数
AspectJ5.0的切点表达式由关键字和操作参数组成,如 "execution( greetTo(..))" 的切点表达式, execution 就是关键字, 而圆括号里的 greetTo(..) 就是操作参数。
函数 | 入参 | 说明 |
---|---|---|
execution() | 方法匹配模式串 | 满足方法匹配模式串所有目标类方法连接点。例如:@Pointcut("execution(public * com.ljw.controller.DiscernController.discern(..))") ,标识目标类中的discern方法 |
@annatation | 方法注解类名 | 表示标注特定注解的目标类方法连接点。例如:@Pointcut("@annotation(com.ljw.ABC)") ,表示标注@ABC注解的目标类连接点 |
within() | 类名匹配串 | 表示特定路径下的所有连接点。例如:within(com.ljw.*) ,表示com.ljw路径下的所有连接点。within(com.ljw.*Service) ,表示com.ljw路径下以Service 结尾的类中的所有连接点 |
args() | 类名 | 匹配参数满足要求的方法。例如@Before(value = "aspectMethod() && args(name)") ,只有一个参数且为name的方法;adgs(com.ljw.ABC),表示所有且仅有一个入参类型与ABC向匹配的方法。 |
target() | 类名 | 目标类按类型匹配指定类。目标类的所有连接点匹配这一切点。例如:target(com.ljw.ABC) |
常见的切点表达式
按方法签名匹配
@Pointcut("execution(public * com.ljw.controller.DiscernController.discern(..))")
按类匹配
@Pointcut("within(com.ljw.*)")
@Pointcut("within(com.ljw..*)")
//匹配实现接口的所有类中的实现的方法
@Pointcut("within(ABCService+)")
按Bean名称匹配
@Pointcut("bean(*Service)")
逻辑运算符运用
可以运用 || 、&& 、!等逻辑运算符
@Pointcut("bean(*Service || *ServiceImpl)")
@Pointcut("bean(*Service) && within(com.ljw.service.*)")
通配符
*:匹配任意字符。只能匹配上下文中的一个元素。
..:匹配任意字符。可以匹配上下文中多个元素。标识类时需要与*结合使用,标识参数时可以单独使用。
+:表示按类型匹配指定类的所有类,必须跟在类名后面。继承或实现指定类的所有类。
增强声明
@Around("discernWeb()")
public Object around(ProceedingJoinPoint joinPoint) {
long start = System.currentTimeMillis();
try {
before(joinPoint);
return joinPoint.proceed(joinPoint.getArgs());
} catch (Throwable t) {
return Result.createFalseRet().withErrMsg("around exception");
}
}
可以从ProceedingJoinPoint中获取参数,对参数进行操作。
@Before:前置增强。
@After:后置增强。
@Around:环绕增强。可以在方法前后进行不同的操作。
参考
[1]《精通Spring4.x企业应用开发实战》