SpringBoot实践——AOP实现
借鉴:wwwblogs/xrq730/p/4919025.html
      blog.csdn/zhaokejin521/article/details/50144753
    www.importnew/24305.html
AOP介绍
⼀、AOP
  AOP(Aspect Oriented Programming),即⾯向切⾯编程,可以说是OOP(Object Oriented Programming,⾯向对象编程)的补充和完善。OOP引⼊封装、继承、多态等概念来建⽴⼀种对象层次结构,⽤于模拟公共⾏为的⼀个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如⽇志功能。⽇志代码往往横向地散布在所有对象层次中,⽽与它对应的对象的核⼼功能毫⽆关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的⽆关的代码被称为横切(cross cutting),在OOP设计中,它导致了⼤量代码的重复,⽽不利于各个模块的重⽤。
AOP技术恰恰相反,它利⽤⼀种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公
共⾏为封装到⼀个可重⽤模块,并将其命名为"Aspect",即切⾯。所谓"切⾯",简单说就是那些与业务⽆关,却为业务模块所共同调⽤的逻辑或责任封装起来,便于减少系统的重复代码,降低模块之间的耦合度,并有利于未来的可操作性和可维护性。
使⽤"横切"技术,AOP把软件系统分为两个部分:核⼼关注点和横切关注点。业务处理的主要流程是核⼼关注点,与之关系不⼤的部分是横切关注点。横切关注点的⼀个特点是,他们经常发⽣在核⼼关注点的多处,⽽各处基本相似,⽐如权限认证、⽇志、事物。AOP的作⽤在于分离系统中的各种关注点,将核⼼关注点和横切关注点分离开来。
  AOP(Aspect Orient Programming),我们⼀般称为⾯向⽅⾯(切⾯)编程,作为⾯向对象的⼀种补充,⽤于处理系统中分布于各个模块的横切关注点,⽐如事务管理、⽇志、缓存等等。AOP实现的关键在于AOP框架⾃动创建的AOP代理,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;⽽动态代理则以Spring AOP为代表。
  与AspectJ的静态代理不同,Spring AOP使⽤的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,⽽是在内存中临时为⽅法⽣成⼀个AOP对象,这个AOP对象包含了⽬标对象的全部⽅法,并且在特定的切点做了增强处理,并回调原对象的⽅法。
  Spring AOP中的动态代理主要有两种⽅式,JDK动态代理和CGLIB动态代理。JDK动态代理通过反射
来接收被代理的类,并且要求被代理的类必须实现⼀个接⼝。JDK动态代理的核⼼是InvocationHandler接⼝和Proxy类。
  如果⽬标类没有实现接⼝,那么Spring AOP会选择使⽤CGLIB来动态代理⽬标类。CGLIB(Code Generation Library),是⼀个代码⽣成的类库,是利⽤asm开源包,可以在运⾏时动态的⽣成某个类的⼦类。注意,CGLIB是通过继承的⽅式做的动态代理,因此如果某个类被标记为final,那么它是⽆法使⽤CGLIB做动态代理的。
⼆、AOP核⼼概念
1、横切关注点
对哪些⽅法进⾏拦截,拦截后怎么处理,这些关注点称之为横切关注点
2、切⾯(aspect)
类是对物体特征的抽象,切⾯就是对横切关注点的抽象
3、连接点(joinpoint)
被拦截到的点,因为Spring只⽀持⽅法类型的连接点,所以在Spring中连接点指的就是被拦截到的⽅法,实际上连接点还可以是字段或者构造器
4、切⼊点(pointcut)
对连接点进⾏拦截的定义
5、通知(advice)
所谓通知指的就是指拦截到连接点之后要执⾏的代码,通知分为前置、后置、异常、最终、环绕通知五类
6、⽬标对象
代理的⽬标对象
7、织⼊(weave)
将切⾯应⽤到⽬标对象并导致代理对象创建的过程
8、引⼊(introduction)
在不修改代码的前提下,引⼊可以在运⾏期为类动态地添加⼀些⽅法或字段
三、Spring对AOP的⽀持
  Spring中AOP代理由Spring的IOC容器负责⽣成、管理,其依赖关系也由IOC容器负责管理。因此,AOP代理可以直接使⽤容器中的其它bean实例作为⽬标,这种关系可由IOC容器的依赖注⼊提供。Spring创建代理的规则为:
1、默认使⽤Java动态代理来创建AOP代理,这样就可以为任何接⼝实例创建代理了
2、当需要代理的类不是代理接⼝的时候,Spring会切换为使⽤CGLIB代理,也可强制使⽤CGLIB
AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:
1、定义普通业务组件
2、定义切⼊点,⼀个切⼊点可能横切多个业务组件
3、定义增强处理,增强处理就是在AOP框架为普通业务组件织⼊的处理动作
所以进⾏AOP编程的关键就是定义切⼊点和定义增强处理,⼀旦定义了合适的切⼊点和增强处理,AOP
框架将⾃动⽣成AOP代理,即:代理对象的⽅法=增强处理+被代理对
象的⽅法。
实现⽅式
Spring除了⽀持Schema⽅式配置AOP,还⽀持注解⽅式:使⽤@AspectJ风格的切⾯声明。
⼀、Aspectj介绍
@AspectJ 作为通过 Java 5 注释注释的普通的 Java 类,它指的是声明 aspects 的⼀种风格。
AspectJ是静态代理的增强,所谓的静态代理就是AOP框架会在编译阶段⽣成AOP代理类,因此也称为编译时增强。
AspectJ: 基于字节码操作(Bytecode Manipulation),通过编织阶段(Weaving Phase),对⽬标Java类型的字节码进⾏操作,将需要的Advice逻辑给编织进去,形成新的字节码。毕竟JVM执⾏的都是Java源代码编译后得到的字节码,所以AspectJ相当于在这个过程中做了⼀点⼿脚,让Advice能够参与进来。
⽽编织阶段可以有两个选择,分别是加载时编织(也可以成为运⾏时编织)和编译时编织
加载时编织(Load-Time Weaving):顾名思义,这种编织⽅式是在JVM加载类的时候完成的。
编译时编织(Compile-Time Weaving):需要使⽤AspectJ的编译器来替换JDK的编译器。
  详情:
1、添加spirng aop⽀持和AspectJ依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.0.7.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
</dependencies>
2、启⽤对@AspectJ的⽀持
  Spring默认不⽀持@AspectJ风格的切⾯声明,为了⽀持需要使⽤如下配置:
<!-- ⾃动扫描使⽤了aspectj注解的类 -->
<aop:aspectj-autoproxy/>
或者在配置类上加注解
@Configuration
@ComponentScan("ly.mate.springboot.aop")
@EnableAspectJAutoProxy//开启AspectJ注解
public class CustomAopConfigurer {
}
3、声明切⾯
@Aspect
@Component
public class CustomLogAspect {
}
或者
定⼀个普通类
public class CustomAuthorityAspect {
在配置⽂件中定义⼀个POJO
<bean id="customAuthorityAspect" class="ly.mate.springboot.aop.CustomAuthorityAspect"/>
然后在该切⾯中进⾏切⼊点及通知定义,接着往下看吧。
4、声明切⼊点
@AspectJ风格的命名切⼊点使⽤org.aspectj.lang.annotation包下的@Pointcut+⽅法(⽅法必须是返回void类型)实现。
@Pointcut(value="切⼊点表达式", argNames = "参数名列表")
public void pointcutName(……) {}
  value:指定切⼊点表达式;
argNames:指定命名切⼊点⽅法参数列表参数名字,可以有多个⽤“,”分隔,这些参数将传递给通知⽅法同名的参数,同时⽐如切⼊点表达式“args(param)”将匹配参数类型为命名切⼊点⽅法同名参数指定的参数类型。
pointcutName:切⼊点名字,可以使⽤该名字进⾏引⽤该切⼊点表达式。
案例:
@Pointcut(value="execution(* ly.ller.*.sayAdvisorBefore(..)) && args(param)", argNames = "param")
public void pointCut(String param) {}
定义了⼀个切⼊点,名字为“pointCut”,该切⼊点将匹配⽬标⽅法的第⼀个参数类型为通知⽅法实现中参数名为“param”的参数类型。
5、声明通知
@AspectJ风格的声明通知也⽀持5种通知类型:
A、前置通知:使⽤org.aspectj.lang.annotation 包下的@Before注解声明。
@Before(value = "切⼊点表达式或命名切⼊点", argNames = "参数列表参数名")
value:指定切⼊点表达式或命名切⼊点。
argNames:与Schema⽅式配置中的同义。
B、后置返回通知:使⽤org.aspectj.lang.annotation 包下的@AfterReturning注解声明。
@AfterReturning(
value="切⼊点表达式或命名切⼊点",
pointcut="切⼊点表达式或命名切⼊点",
argNames="参数列表参数名",
returning="返回值对应参数名")
value:指定切⼊点表达式或命名切⼊点。
pointcut:同样是指定切⼊点表达式或命名切⼊点,如果指定了将覆盖value属性指定的,pointcut具有⾼优先级。
argNames:与Schema⽅式配置中的同义。
returning:与Schema⽅式配置中的同义。
C、后置异常通知:使⽤org.aspectj.lang.annotation 包下的@AfterThrowing注解声明。
@AfterThrowing (
value="切⼊点表达式或命名切⼊点",
pointcut="切⼊点表达式或命名切⼊点",
argNames="参数列表参数名",
throwing="异常对应参数名")
value:指定切⼊点表达式或命名切⼊点。
pointcut:同样是指定切⼊点表达式或命名切⼊点,如果指定了将覆盖value属性指定的,pointcut具有⾼优先级。
argNames:与Schema⽅式配置中的同义。
throwing:与Schema⽅式配置中的同义。
D、后置最终通知:使⽤org.aspectj.lang.annotation 包下的@After注解声明。
@After (
value="切⼊点表达式或命名切⼊点",
argNames="参数列表参数名")
value:指定切⼊点表达式或命名切⼊点。
argNames:与Schema⽅式配置中的同义。
E、环绕通知:使⽤org.aspectj.lang.annotation 包下的@Around注解声明。
@Around (
value="切⼊点表达式或命名切⼊点",
argNames="参数列表参数名")
value:指定切⼊点表达式或命名切⼊点。
argNames:与Schema⽅式配置中的同义。
⼆、实践
1、Schema⽅式配置AOP
A、定⼀个切⼊点
/**
* ⾃定义⼀个切⼊点-权限校验
* @ClassName: CustomAuthorityAspect
* @Description: TODO
* @author OnlyMate
* @Date 2018年9⽉7⽇下午2:24:24
*/
public class CustomAuthorityAspect {
private Logger logger = Logger(CustomLogAspect.class);
/**
* 加密
* @Title: encode
* @Description: TODO
* @Date 2018年9⽉7⽇下午2:30:05
* @author OnlyMate
*/
public void encode() {
logger.info("CustomAuthorityAspect ==> encode method: encode data");
}
/
**
* 解密
* @Title: decode
* @Description: TODO
* @Date 2018年9⽉7⽇下午2:30:11
* @author OnlyMate
*/
public void decode() {
logger.info("CustomAuthorityAspect ==> decode method: decode data");
}
}
B、通过Schema⽅式配置AOP
<bean id="customAuthorityAspect" class="ly.mate.springboot.aop.CustomAuthorityAspect" />
<aop:config proxy-target-class="false">
<!-- AOP实现 -->
<aop:aspect id="customAuthority" ref="customAuthorityAspect">
<aop:pointcut id="addAllMethod" expression="execution(* ly.ller.*.*(..))" />
<aop:before method="encode" pointcut-ref="addAllMethod" />
<aop:after method="decode" pointcut-ref="addAllMethod" />
</aop:aspect>
</aop:config>
  前⾯说过Spring使⽤动态代理或是CGLIB⽣成代理是有规则的,⾼版本的Spring会⾃动选择是使⽤动态代理还是CGLIB⽣成代理内容,当然我们也可以强制使⽤CGLIB⽣成代理,那就是<aop:config>⾥⾯有⼀个"proxy-target-class"属性,这个属性值如果被设置为true,那么基于类的代理将起作⽤,如果proxy-target-class被设置为false或者这个属性被省略,那么基于接⼝的代理将起作⽤
2、使⽤@AspectJ风格的切⾯声明
A、定⼀个切⼊点
/**
* @Description: ⾃定义切⾯
* @ClassName: CustomLogAspect
* @author OnlyMate
* @Date 2018年9⽉10⽇下午3:51:32
*
*/
@Aspect
@Component
public class CustomLogAspect {
private Logger logger = Logger(CustomLogAspect.class);
/**
* @Description: 定义切⼊点
* @Title: pointCut
* @author OnlyMate
* @Date 2018年9⽉10⽇下午3:52:17
*/
/
/被注解CustomAopAnnotation表⽰的⽅法
//@Pointcut("@ly.mate.springboot.annotation.CustomAopAnnotation")
@Pointcut("execution(public * ly.ller.*.*(..))")
public void pointCut(){
}
/**
* @Description: 定义前置通知
* @Title: before
* @author OnlyMate
* @Date 2018年9⽉10⽇下午3:52:23
* @param joinPoint
* @throws Throwable
*/
@Before("pointCut()")
public void before(JoinPoint joinPoint) throws Throwable {
// 接收到请求,记录请求内容
logger.info("【注解:Before】------------------切⾯  before");
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestAttributes();
HttpServletRequest request = Request();
// 记录下请求内容
logger.info("【注解:Before】浏览器输⼊的⽹址=URL : " + RequestURL().toString());
logger.info("【注解:Before】HTTP_METHOD : " + Method());
logger.info("【注解:Before】IP : " + RemoteAddr());
logger.info("【注解:Before】执⾏的业务⽅法名=CLASS_METHOD : " + Signature().getDeclaringTypeName() + "." + Signature().getName());
logger.info("【注解:Before】业务⽅法获得的参数=ARGS : " + Args()));
}
/**
* @Description: 后置返回通知
* @Title: afterReturning
* @author OnlyMate
* @Date 2018年9⽉10⽇下午3:52:30
* @param ret
* @throws Throwable
@AfterReturning(returning = "ret", pointcut = "pointCut()")
public void afterReturning(Object ret) throws Throwable {
// 处理完请求,返回内容
logger.info("【注解:AfterReturning】这个会在切⾯最后的最后打印,⽅法的返回值 : " + ret);    }
/**
* @Description: 后置异常通知
* @Title: afterThrowing
* @author OnlyMate
* @Date 2018年9⽉10⽇下午3:52:37
* @param jp
*/
@AfterThrowing("pointCut()")
public void afterThrowing(JoinPoint jp){
logger.info("【注解:AfterThrowing】⽅法异常时执⾏.....");
springboot实现aop
}
/**
* @Description: 后置最终通知,final增强,不管是抛出异常或者正常退出都会执⾏
* @Title: after
* @author OnlyMate
* @Date 2018年9⽉10⽇下午3:52:48
* @param jp
*/
@After("pointCut()")
public void after(JoinPoint jp){
logger.info("【注解:After】⽅法最后执⾏.....");
}
/**
* @Description: 环绕通知,环绕增强,相当于MethodInterceptor
* @Title: around
* @author OnlyMate
* @Date 2018年9⽉10⽇下午3:52:56
* @param pjp
* @return
*/
@Around("pointCut()")
public Object around(ProceedingJoinPoint pjp) {
logger.info("【注解:Around . 环绕前】⽅法环绕");
try {
//如果不执⾏这句,会不执⾏切⾯的Before⽅法及controller的业务⽅法
Object o =  pjp.proceed();
logger.info("【注解:Around. 环绕后】⽅法环绕proceed,结果是 :" + o);
return o;
} catch (Throwable e) {
e.printStackTrace();
return null;
}
}
}
B、使⽤@AspectJ风格的切⾯声明
/**
* ⾃定义AOP配置类
* @ClassName: CustomAopConfigurer
* @Description: TODO
* @author OnlyMate
* @Date 2018年9⽉7⽇下午3:43:21
*
*/
@Configuration
@ComponentScan("ly.mate.springboot.aop")
@EnableAspectJAutoProxy//开启AspectJ注解
public class CustomAopConfigurer {
}
效果图

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。