https://www.jianshu.com/p/a82509c4bb0d

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。下图就是AOP的核心概念和学习路线图,掌握此图是关键:

 
AOP架构图

一、方案举例

想象一下下,写了一个功能代码(比如SayHello()),想要在函数前后都做点什么,最简单的就是去写一段硬编码:

1.写死代码

这是功能的接口

public interface Greeting {
void sayHello(String name);
}

在实现类里边去增加前置方法和后置方法

public class GreetingImpl implements Greeting {
@Override
public void sayHello(String name) {
before();
System.out.println("Hello! " + name);
after();
}
private void before() {
System.out.println("Before");
}
private void after() {
System.out.println("After");
}
}

比如我们要统计每个方法的执行时间,以对性能作出评估,那是不是要在每个方法的一头一尾都做点手脚呢?这样写死的方法会累死码农们,于是来一个加强版。

2.静态代理

单独为 GreetingImpl 这个类写一个代理类,接口还是未变,实现类抽取出来放一边,这样就进行了解耦,后置和前置功能的实现放到这个静态代理类中去绑定结合:

//Greeting接口的实现类
public class GreetingImpl implements Greeting {
@Override
public void sayHello(String name) {
System.out.println("Hello! " + name);
}
} //绑定前置和后置方法的静态代理类
public class GreetingProxy implements Greeting {
private GreetingImpl greetingImpl;
public GreetingProxy(GreetingImpl greetingImpl) {
this.greetingImpl = greetingImpl;
}
@Override
public void sayHello(String name) {
before();
greetingImpl.sayHello(name);
after();
}
private void before() {
System.out.println("Before");
}
private void after() {
System.out.println("After");
}
}

用这个 GreetingProxy 去代理 GreetingImpl,下面看看客户端如何来调用:

public class Client {
public static void main(String[] args) {
Greeting greetingProxy = new GreetingProxy(new GreetingImpl());
greetingProxy.sayHello("Jack");
}
}

这样写没错,但是有个问题,每增强一个功能接口的实现类都要去实现一遍这个实现类的Proxy代理方法,最后的结果就会导致XxxProxy 这样的类会越来越多,如何才能将这些代理类尽可能减少呢?最好只有一个代理类。这时我们就需要使用 JDK 提供的动态代理了。

3.JDK代理

所有的代理类都合并到动态代理类中了,该代理类设置为泛型,可以接收各种实现类,经过代理类生成后可以绑定结合前置、后置方法,进行功能增强。

public class JDKDynamicProxy implements InvocationHandler {

    private Object target;

    public JDKDynamicProxy(Object target) {
this.target = target;
} @SuppressWarnings("unchecked")
public <T> T getProxy() {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
} private void before() {
System.out.println("Before");
} private void after() {
System.out.println("After");
}
}

客户端的调用

public class Client {
public static void main(String[] args) {
Greeting greeting = new JDKDynamicProxy(new GreetingImpl()).getProxy();
greeting.sayHello("Jack");
}
}

所有的代理类都合并到动态代理类中了,但这样做仍然存在一个问题:JDK 给我们提供的动态代理只能代理接口,而不能代理没有接口的类。

4.cglib动态代理

我们使用开源的 CGLib 类库可以代理没有接口的类,这样就弥补了 JDK 的不足。

public class CGLibDynamicProxy implements MethodInterceptor {

    private static CGLibDynamicProxy instance = new CGLibDynamicProxy();

    private CGLibDynamicProxy() {
} public static CGLibDynamicProxy getInstance() {
return instance;
} @SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> cls) {
return (T) Enhancer.create(cls, this);
} @Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before();
Object result = proxy.invokeSuper(target, args);
after();
return result;
} private void before() {
System.out.println("Before");
} private void after() {
System.out.println("After");
}
}

客户端调用也更加轻松了

public class Client {

    public static void main(String[] args) {
Greeting greeting = CGLibDynamicProxy.getInstance().getProxy(GreetingImpl.class);
greeting.sayHello("Jack");
}
}

二、AOP的概念

切面(Aspect):其实就是共有功能的实现。如日志切面、权限切面、事务切面等。在实际应用中通常是一个存放共有功能实现的普通Java类,之所以能被AOP容器识别成切面,是在配置中指定的。

增强(Advice):是切面的具体实现。以目标方法为参照点,根据放置的地方不同,可分为前置通知(Before)、后置通知(AfterReturning)、异常通知(AfterThrowing)、最终通知(After)与环绕通知(Around)5种。在实际应用中通常是切面类中的一个方法,具体属于哪类通知,同样是在配置中指定的。

连接点(Joinpoint):就是程序在运行过程中能够插入切面的地点。例如,方法调用、异常抛出或字段修改等,但spring只支持方法级的连接点。

切入点(Pointcut):用于定义通知应该切入到哪些连接点上。不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。

目标对象(Target):就是那些即将切入切面的对象,也就是那些被通知的对象。这些对象中已经只剩下干干净净的核心业务逻辑代码了,所有的共有功能代码等待AOP容器的切入。

代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象的核心业务逻辑功能加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。

织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译期、类装载期及运行期,当然不同的发生点有着不同的前提条件。譬如发生在编译期的话,就要求有一个支持这种AOP实现的特殊编译器;发生在类装载期,就要求有一个支持AOP实现的特殊类装载器;只有发生在运行期,则可直接通过Java语言的反射机制与动态代理机制来动态实现。还有引入(Introduction),用以区分类和方法的拦截。

1.项目aop的实现

项目中aop的实现基于两种模式或者综合,Spring AspectJ+(execution拦截表达式、基于@Annotation的注解拦截)

(1).Spring + AspectJ(基于注解:通过 AspectJ execution 表达式拦截方法)

通过表达式去匹配各个符合条件的切入点,如下就实现了aop.demo.GreetingImpl类下的任意方法拦截匹配,那么匹配后的切入点都会织入代理中。

@Aspect
@Component
public class GreetingAspect { @Around("execution(* aop.demo.GreetingImpl.*(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
before();
Object result = pjp.proceed();
after();
return result;
} private void before() {
System.out.println("Before");
} private void after() {
System.out.println("After");
}
}

类上面标注的 @Aspect 注解,这表明该类是一个 Aspect(其实就是 Advisor)。该类无需实现任何的接口,只需定义一个方法(方法叫什么名字都无所谓),只需在方法上标注 @Around 注解,在注解中使用了 AspectJ 切点表达式。方法的参数中包括一个 ProceedingJoinPoint 对象,它在 AOP 中称为 Joinpoint(连接点),可以通过该对象获取方法的任何信息,例如:方法名、参数等。

虽然有了execution拦截表达式,但是有时候我们只想满足精确的某些方法,靠表达式难免会有缺漏或者是带入了一些不想带入的切入点进来,这可是很考验写拦截表达式的人,我们可以采用点对点的更精确的基于注解的方法。

(2).Spring + AspectJ(基于注解:通过 AspectJ @annotation 表达式拦截方法)

为了拦截指定的注解的方法,我们首先需要来自定义一个注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Tag {
}

以上定义了一个 @Tag 注解,此注解可标注在方法上,在运行时生效。

只需将前面的 Aspect 类的切点表达式稍作改动:

@Aspect
@Component
public class GreetingAspect { @Around("@annotation(aop.demo.Tag)")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
...
}
...
}

这次使用了 @annotation() 表达式,只需在括号内定义需要拦截的注解名称即可。

直接将 @Tag 注解定义在您想要拦截的方法上,就这么简单:

@Component
public class GreetingImpl implements Greeting { @Tag
@Override
public void sayHello(String name) {
System.out.println("Hello! " + name);
}
}

2.项目AOP实现

项目中可能就会同时采用Spring AspectJ的拦截表达式和注解拦截方法,假设要做一个操作日志的功能,需要对某些关键操作进行记录,将要记录操作日志的方法保存为枚举类。比如常见的增删改操作都要记录其操作:

public enum Type {
DEFAULT("",""),
ADD("1","新增"),
UPDATE("2","修改"),
DELETE("3","删除")
;
private String id;
private String operationType;
Type(String id,String operationType){
this.id=id;
this.operationType=operationType;
} public String getId() {return id;} public void setId(String id) {this.id = id;} public String getOperationType() {return operationType;} public void setOperationType(String operationType) {
this.operationType = operationType;}
}

写一个注解类,该注解联合枚举就可以作为一个标记,每一个需要记录加一个对应注解,相应的操作就会留下操作类型信息,方便切面拦截下来调用处理。

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface LogAnno {
//操作类型
public Type operationType() default Type.DEFAULT;
//操作备注
public String remark() default "";
}

切面类的编写,在这里我们采用了Spring AspectJ +两种拦截表达式,拦截特定类(execution(* main.com.*.*(..))下的加了特定注解(@annotation(main.com.LogAnno))的操作,可以进行后置加强方法操作和后置返回方法操作,一个是切下了切入点的输入信息,一个是切下了切入点返回值的信息,具体按照实际情景来各取所需吧。在拦下了输入参数信息的同时,我们可以采用反射来获取输入参数的属性和值,操作日志就相当于留下了现场的快照,我们只需要在写一个操作日志的写入数据库方法就可以保存下这些信息了。

/**
* 切面类,含有多个通知
*/
@Aspect
@Component
public class MyAspect {
//声明公共切入点
@Pointcut("execution(* main.com.UserServiceImpl.*(..))")
private void PointCut1(){} //声明指定包内的注解切入点
@Pointcut("@annotation(main.com.LogAnno) && execution(* main.com.*.*(..)) ")
private void PointCutofAnno(){} //拦截返回值
@AfterReturning(value="PointCut1()" ,returning="ret")
public void myAfterReturning(JoinPoint joinPoint, Object ret){
System.out.println("后置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
} //拦截@注解的方法
@AfterReturning(value="PointCutofAnno()",returning = "ret")
public void myAfterReturningofAnno (JoinPoint joinPoint,Object ret){
//获取参数
Object[] objs=joinPoint.getArgs();
//获取返回值
Object obj=objs[0];
Map<String ,Object> inMap= getParameter(obj);
Map<String ,Object> outMap= getParameter(ret);
System.out.println(inMap);
System.out.println(outMap);
} @Before(value="PointCutofAnno()")
public void myBeforeReturningofAnno (JoinPoint joinPoint){
//获取参数
Object[] objs=joinPoint.getArgs();
//获取返回值
Object obj=objs[0];
Map<String ,Object> inMap= getParameter(obj);
System.out.println(inMap);
} //拓展日志的功能,对拦截的入参进行反射获取信息
private Map<String, Object> getParameter(Object obj) {
try {
//反射对象中的属性
Class clazz=obj.getClass();
Field[] fields= clazz.getDeclaredFields();
Map<String,Object> resultMap=new java.util.HashMap<>();
//遍历并返回
for(Field field:fields){
String fieldName=field.getName();
PropertyDescriptor pd=new PropertyDescriptor(fieldName,clazz);
Method readMethod = pd.getReadMethod();
Object resultObj= readMethod.invoke(obj);
resultMap.put(fieldName,resultObj);
}
return resultMap;
}
catch (Exception e){
e.printStackTrace();
}
return null;
}
}

运行test代码,最后执行结果如下图所示:

 

代理模式-aop的更多相关文章

  1. Spring-构造注入&注解注入&代理模式&AOP

    1.   课程介绍 1.  依赖注入;(掌握) 2.  XML自动注入;(掌握) 3.  全注解配置;(掌握) 4.  代理模式;(掌握) 5.  AOP;(掌握) 依赖注入;(掌握) 2.1.  构 ...

  2. Spring AOP系列(一)— 代理模式

    Spring AOP系列(一)- 代理模式 AOP(Aspect Oriented Programming)并没有创造或使用新的技术,其底层就是基于代理模式实现.因此我们先来学习一下代理模式. 基本概 ...

  3. Spring代理模式及AOP基本术语

    一.代理模式: 静态代理.动态代理 动态代理和静态代理区别?? 解析:静态代理需要手工编写代理类,代理类引用被代理对象. 动态代理是在内存中构建的,不需要手动编写代理类 代理的目的:是为了在原有的方法 ...

  4. Spring 代理模式及AOP基本术语

    一.代理模式: 静态代理.动态代理 动态代理和静态代理区别?? 解析:静态代理需要手工编写代理类,代理类引用被代理对象. 动态代理是在内存中构建的,不需要手动编写代理类 代理的目的:是为了在原有的方法 ...

  5. 设计模式--5.5 代理模式-通用代码及aop

    1.通用代码 (1)Subjects package com.design.代理模式.通用代码; public interface Subject { void request(); } (2)Rea ...

  6. AOP基础—代理模式

    代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息.过滤消息.把消息转发给委托类,以及事后处理消息等.代理类与委托类之间通常会存在关联关系,一个代 ...

  7. Spring AOP /代理模式/事务管理/读写分离/多数据源管理

    参考文章: http://www.cnblogs.com/MOBIN/p/5597215.html http://www.cnblogs.com/fenglie/articles/4097759.ht ...

  8. 代理模式——用AOP测试业务层方法的执行时间

    代理模式 对代理模式的理解,通过http://www.runoob.com/design-pattern/proxy-pattern.html 对AOP的代理模式,参考https://www.cnbl ...

  9. AOP 技术原理——代理模式全面总结

    前言 非常重要的一个设计模式,也很常见,很多框架都有它的影子.定义就不多说了.两点: 1.为其它对象提供一个代理服务,间接控制对这个对象的访问,联想 Spring 事务机制,在合适的方法上加个 tra ...

随机推荐

  1. 【并行计算-CUDA开发】CUDA存储器模型

    CUDA存储器模型 除了执行模型以外,CUDA也规定了存储器模型(如图2所示)和一系列用于主控CPU与GPU间通信的不同地址空间.图中红色的区域表示GPU片内的高速存储器,橙色区域表示DRAM中的的地 ...

  2. 【FFMPEG】谈谈RTP传输中的负载类型和时间戳

    谈谈RTP传输中的负载类型和时间戳 最近被RTP的负载类型和时间戳搞郁闷了,一个问题调试了近一周,终于圆满解决,回头看看,发现其实主要原因还是自己没有真正地搞清楚RTP协议中负载类型和时间戳的含义.虽 ...

  3. IDEA 2019.2.2破解激活方法(激活到2089年8月,亲测有效)

    本来笔者这边是有个正版激活码可以使用的,但是,2019.9月3号的时候,一些小伙伴反映这个注册码已经失效了,于是拿着自己的 IDEA, 赶快测试了一下,果不其然,已然是不能用了. 好在,笔者又找到了新 ...

  4. 手写Indexof

    String.prototype.indexO = function(st){ // console.log(this.length); let str = this; var j = 0; let ...

  5. SQL Pretty Printer for SSMS 很棒的格式化插件

    SQL Pretty Printer for SSMS 很不错的SQL格式化插件   写SQL语句或者脚本时,看到凌乱的格式就头大了,于是决心找一款SQL语句格式化的工具. 功夫不负有心人还真的被我找 ...

  6. POJ2367(拓扑排序裸题

    #include<iostream> #include<vector> #include<queue> using namespace std; typedef l ...

  7. 剑指offer2:C++实现的替换空格(字符中的空格替换为“%20”)

    1. 题目描述 请实现一个函数,将一个字符串中的空格替换成“%20”.例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy. 2. 思路和方法: 2.1 ...

  8. WEB小计

     使用vue的事件绑定时,应当使用.stop来阻止事件的传播   html 有捕获和冒泡两种事件机制

  9. JSON函数表1

    jsoncpp 主要包含三个class:Value.Reader.Writer.注意Json::Value 只能处理 ANSI 类型的字符串,如果 C++ 程序是用 Unicode 编码的,最好加一个 ...

  10. 大数据学习(2)- export、source(附带多个服务器一起启动服务器)

    linux环境中, A=1这种命名方式,变量作用域为当前线程 export命令出的变量作用域是当前进程及其子进程. 可以通过source 脚本,将脚本里面的变量放在当前进程中 附带自己写的tomcat ...