Sping AOP


1.什么是AOP

面向切面编程(AOP) 是 面向对象编程的补充(OOP)

传统的业务处理代码中,通常会惊醒事务处理、日志处理等操作。虽然可以使用OOP的组合或继承来实现代码重用,但如果要实现某个功能,同样的代码还是会分散到各个方法中。

如果想要关闭某个功能,或者修改,就必须修改所有相关方法,增加了工作量和出错率。

AOP采用横向抽取机制,将重复代码抽取出来,在程序编译或运行时将代码应用到需要执行的地方。

AOP可以使开发人员编写业务逻辑时专心于核心业务,提高了开发效率,增强了代码的可维护性。

常用框架: Spring AOPAspectJ

转:

  • 静态AOP
    编译阶段对程序源代码进行修改,生成静态AOP代理类(生成的*.class已经被改掉,需要特定的编译器) 如 AspectJ
  • 动态AOP
    运行阶段动态生成Proxy对象

2.AOP术语

  • Aspect(切面):实际应用中,切面通常指封装的用于横向插入系统功能的类,当然也要先通过<bean>元素注册
  • Joinpoint(连接点):指方法的调用
  • Pointcut(切入点):类或方法名 满足某一条件的方法
  • Advice(通知/增强处理):切入点要执行的程序代码,切面的具体实现
  • Target Object(目标对象):被增强对象
  • Weaving(织入):将切面代码插入到目标对象上,生成代理对象的过程

3.动态代理

①JDK动态代理—基础

JDK动态代理通过 java.lang.reflect.Proxy实现,我们可以使用Proxy.newProxyInstance()来创建代理对象

//切片类
public class MyAspect {
public void check_Permissions(){
System.out.println("模拟检查权限");
}
public void log(){
System.out.println("模拟记录日志...");
}
}
//代理类
public class JdkProxy implements InvocationHandler {
//声明目标类接口
private UserDao userDao;
//创建代理方法
public Object createProxy(UserDao userDao){
this.userDao = userDao;
// 1.类加载器
ClassLoader classLoader = UserDao.class.getClassLoader();
// 2.被代理对象实现的所有接口
Class[] clazz = userDao.getClass().getInterfaces();
// 3.使用代理类,进行增强,返回的是代理后的对象
return Proxy.newProxyInstance(classLoader, clazz,this);
}
/*
所有动态代理类的方法调用都会交给invoke() 方法来处理
proxy 被代理后的对象
method 将要被执行的方法信息(反射)
args 执行方法时需要的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//声明切片
MyAspect myAspect = new MyAspect();
//前增强
myAspect.check_Permissions();
Object obj = method.invoke(userDao,args);
myAspect.log();
return obj;
}
}
//调用类
public class JdkTest {
public static void main(String[] args) {
JdkProxy jdkProxy = new JdkProxy();
UserDao userDao = new UserDaoImp1();
UserDao userDao1 = (UserDao)jdkProxy.createProxy(userDao);
userDao1.addUser();
userDao1.deleteUser();
}
}

② CGLIB代理—基础

JDK动态代理要求使用动态代理的对象必须实现一个或多个接口

CGLIB(Code Generation Library)使用底层字节码技术,对指定的目标类生成一个子类,并对子类进行增强

//目标类
public class UserDao {
public void addUser() {
System.out.println("增加用户...");
} public void deleteUser() {
System.out.println("删除用户...");
}
} //代理类
public class CglibProxy implements MethodInterceptor {
//代理方法
public Object createProxy(Object target){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass());
enhancer.setCallback(this);
return enhancer.create();
}
/*
proxy CGlib生成的代理对象
method 拦截的方法
args 拦截方法的参数数组
methodProxy 方法的代理对象,用于执行父类的方法
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
MyAspect myAspect = new MyAspect();
myAspect.check_Permissions();
Object obj = methodProxy.invokeSuper(proxy, args);
myAspect.log();
return obj;
}
} //测试类
public class CglibTest {
public static void main(String[] args) {
CglibProxy cglibProxy = new CglibProxy();
UserDao userDao = new UserDao();
UserDao userDao1 = (UserDao)cglibProxy.createProxy(userDao);
userDao1.addUser();
userDao1.deleteUser();
}
}

运行结果

模拟检查权限
增加用户...
模拟记录日志...
模拟检查权限
删除用户...
模拟记录日志...

③ProxyFactoryBean类

演示在Spring中,使用在配置文件中创建ProxyFactoryBean,并使用它创建一个AOP环绕通知案例

首先需要两个JAR包

pom.xml

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>aopalliance</groupId>
<artifactId>aopalliance</artifactId>
<version>1.0</version>
</dependency>

切面类 (环绕通知需要实现MethodInterceptro接口)

public class MyAspect implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
this.check_Permissions();
Object obj = methodInvocation.proceed();
this.log();
return obj;
}
public void check_Permissions(){
System.out.println("模拟检查权限...");
}
public void log(){
System.out.println("模拟记录日志...");
}
}

applicationContext.xml 配置文件

    <!-- 1 目标类-->
<bean id="userDao" class="com.itheima.jdk.UserDaoImp1"/>
<!-- 2 切面类-->
<bean id="myAspect" class="com.itheima.factorybean.MyAspect"/>
<!-- 3 使用Spring代理工厂定义一个名称为userDaoProxy的代理对象 -->
<bean id="userDaoProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 3.1 指定代理实现的接口-->
<property name="proxyInterfaces" value="com.itheima.jdk.UserDao"/>
<!-- 3.2 指定目标对象-->
<property name="target" ref="userDao"/>
<!-- 3.3 指定切面,植入环绕通知 -->
<property name="interceptorNames" value="myAspect"/>
<!-- 3.4 指定代理方式,true:使用cglib,false(默认):使用jdk动态代理 -->
<property name="proxyTargetClass" value="true"/>
</bean>

//测试类

public class ProxyFactoryBeanTest {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDaoProxy");
userDao.addUser();
userDao.deleteUser();
}
}

4.AspectJ开发

常用元素配置代码

  <!--定义切面 Bean-->
<bean id="myAspect" class="com.itheima.aspectj.xml.MyAspect"/>
<aop:config>
<!-- 1.配置切面-->
<aop:aspect ref="myAspect">
<!-- 2.配置切入点-->
<aop:pointcut expression="execution(* com.itheima.jdk.*.*(..))" id="myPointCut"/> <!-- 3.配置通知-->
<!-- 前置通知-->
<aop:before method="myBefore" pointcut-ref="myPointCut"/>
<!-- 后置通知-->
<aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="returnVal" />
<!-- 环绕通知-->
<aop:around method="myAround" pointcut-ref="myPointCut"/>
<!-- 异常通知-->
<aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
<!-- 最终通知-->
<aop:after method="myAfter" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>

1.Spring的通知类型

  • 环绕通知  在目标执行前后实施增强,可以应用于日志、事务管理等功能。
  • 前置通知  目标方法执行前实施增强,可以应用与权限管理等功能
  • 后置通知  目标方法执行后实施增强,可以应用于关闭流、上传文件、删除临时文件(相当于最终通知)
  • 异常通知  抛出异常后实施增强,应用于处理异常记录日志等功能

2.配置切面

<aop:aspect>元素会将一个已定义好的Spring Bean转换为切面Bean,定义完成后用ref属性即可引用该Bean。

3.配置切入点

通过<aop:pointcut>定义。当作为<aop:config>的子元素定义时,表示该切入点时全局切入点可被多个切面所共享。

当作为<aop:aspect>元素的子元素时,切入点只对当前切面有效

execution(* com.itheima.jdk.*.*(..))  第一个*表示返回类型,表示所有类型;com.itheima.jdk表示需要拦截的包名,第二个*表示类名;第三个*表示方法名;(..)表示参数 ,..表示任意方法

给出表达式基本格式

excution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

  • modifiers-pattern 目标方法访问修饰符 public,private
  • ret-type-pattern 返回值类型 void,String
  • declaring-type-pattern 类路径
  • name-pattern: 目标方法名
  • param-pattern: 目标方法的参数
  • throws-pattern: 需要被代理的目标方法抛出的异常
  • ? 表示不必须配置项

4.配置通知

  • pointcut : 切入点表达式
  • pointcut-ref:一个已经存在的切入点名称,与pointcut只需要其中一个
  • method:指定一个方法名(切面中的一个方法)
  • throwing:只对<after-throwing>元素有效,用于指定一个形参名,异常通知方法通过该形参访问目标方法抛出的异常
  • returing:只对<after-returning>元素有效,用于指定一个形参名,后置通知方法通过该形参访问目标方法的返回值

5.环境配置

  注意:aspectjweaver最好使用新版本下面也给出目前的最新版maven,不然使用注解时可能会报 error at ::0 can't find referenced pointcut XXX

pom.xml

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>

6.实施代码

/*
切面类此页编写通知
*/
public class MyAspect {
//前置通知
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查...,");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被植入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
//后置通知
public void myAfterReturning(JoinPoint joinPoint,Object returnVal){
System.out.print("后置通知:模拟记录日志...,");
System.out.println("被植入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
/*环绕通知
ProceedingJoinPoint 是JoinPoint的子接口,表示可以执行目标方法
1.必须是Object类型的返回值
2.必须接收一个参数,类型为 ProceedingJoinPoint
3.必须throws Throwable
*/
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
//异常通知
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
//最终通知
public void myAfter(){
System.out.println("最终通知:模拟方法结束后释放资源...");
}
} //测试类
public class TestXmlAspectj {
public static void main(String[] args) {
ApplicationContext applicationContext =
new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.addUser();
}
}

运行结果

前置通知:模拟执行权限检查...,目标类是:com.itheima.jdk.UserDaoImp1@791f145a,被植入增强处理的目标方法为:addUser
环绕开始:执行目标方法之前,模拟开启事务...
增加用户...
最终通知:模拟方法结束后释放资源...
环绕结束:执行目标方法之后,模拟关闭事务...
后置通知:模拟记录日志...,被植入增强处理的目标方法为:addUser

模拟出错

public void addUser() {
int i=1/0;
System.out.println("增加用户...");
} //输出
前置通知:模拟执行权限检查...,目标类是:com.itheima.jdk.UserDaoImp1@791f145a,被植入增强处理的目标方法为:addUser
环绕开始:执行目标方法之前,模拟开启事务...
最终通知:模拟方法结束后释放资源...
异常通知:出错了/ by zero

总结:后置通知只有在目标方法执行成功后才会被织入,而最终通知无论如何都会被织入。

5.基于注解的AspectJ

  • @Aspect  定义一个切面
  • @Pointcut  定义切入点表达式。需要个一返回值是void ,函数体为空的函数辅助
  • @Before  用于顶一个前置通知。需要用value/pointcut指定切入点表达式 / 已有切入点
  • @AfterReturning  后置通知。 需要指定 value/pointcut 和 returning属性
  • @Around  环绕通知。有value/pointcut属性
  • @AfterThrowing  处理程序中未处理的异常。 有value/pointcut和throwing属性
  • @After   定义final通知 有value/pointcut属性
  • @DeclareParents  定义引介通知

演示代码

/*
切面类,在此类中编写通知
*/
@Aspect
@Component
public class MyAspect {
//定义切入点表达式
@Pointcut("execution(* com.itheima.jdk.*.*(..))")
//使用一个返回值为void、方法体为空的方法来命名切入点
public void myPointCut(){}
//前置通知
@Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
System.out.print("前置通知:模拟执行权限检查...,");
System.out.print("目标类是:"+joinPoint.getTarget());
System.out.println(",被植入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
//后置通知
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint){
System.out.print("后置通知:模拟记录日志...,");
System.out.println("被植入增强处理的目标方法为:"
+joinPoint.getSignature().getName());
}
//环绕通知
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{
System.out.println("环绕开始:执行目标方法之前,模拟开启事务...");
Object obj = proceedingJoinPoint.proceed();
System.out.println("环绕结束:执行目标方法之后,模拟关闭事务...");
return obj;
}
//异常通知
@AfterThrowing(value="myPointCut()",throwing="e")
public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
System.out.println("异常通知:"+"出错了"+e.getMessage());
}
//最终通知
@After("myPointCut()")
public void myAfter(){
System.out.println("最终通知:模拟方法结束后释放资源...");
}
}

配置文件

<!-- 指定需要扫描的包,使注解生效 -->
<context:component-scan base-package="com.itheima"/>
<!-- 启动基于注解的声明式AspectJ支持 -->
<aop:aspectj-autoproxy />

Sping AOP的更多相关文章

  1. 基于注解的Sping AOP详解

    一.创建基础业务 package com.kang.sping.aop.service; import org.springframework.stereotype.Service; //使用注解@S ...

  2. [置顶] 使用sping AOP 操作日志管理

    记录后台操作人员的登陆.退出.进入了哪个界面.增加.删除.修改等操作 在数据库中建立一张SYSLOG表,使用Sping 的AOP实现日志管理,在Sping.xml中配置 <!-- Spring ...

  3. 基于XML配置的Sping AOP详解

    一.编写基本处理方法 package com.kang.sping.xml.aop; public class Math{ //加 public int add(int n1,int n2){ int ...

  4. Sping AOP初级——入门及简单应用

    在上一篇<关于日志打印的几点建议以及非最佳实践>的末尾提到了日志打印更为高级的一种方式——利用Spring AOP.在打印日志时,通常都会在业务逻辑代码中插入日志打印的语句,这实际上是和业 ...

  5. Sping AOP Capabilities and Goals

    Spring AOP是用纯的java实现的.不需要任何个性的实现过程.Spring AOP不需要控制类加载器,并且它适用于Servlet容器或者应用服务器. Spring AOP当前只支持方法执行的连 ...

  6. sping aop 源码分析(-)-- 代理对象的创建过程分析

    测试项目已上传到码云,可以下载:https://gitee.com/yangxioahui/aopdemo.git 具体如下: public interface Calc { Integer add( ...

  7. 约定编程与Sping AOP

    一.约定编程 Spring AOP是一种约定流程的编程,咱们可以先通过动态代理模式的实现来理解Spring AOP的概念. 代理的逻辑很简单,例如,当你需要采访一名儿童时,首先需要经过他父母的同意,在 ...

  8. Sping Aop日志实现(六)--日志查询功能

    最终效果: 日志查询流程分析: Controller代码: Mapper:

  9. Sping Aop日志实现(五)--使用Mybatis注解保存日志

随机推荐

  1. 让你的浏览器变成Siri一样的语音助手

    最近业余时间浏览技术文章的时候,看到了一篇关于语音朗读的文章:Use JavaScript to Make Your Browser Speak(用Javascript让你的浏览器说话),文章中提到可 ...

  2. Vue3手册译稿 - 深入组件 - 自定义事件

    本章节需要掌握组件基础 emit我译成发射,觉得发射这个词比较形象的形容将子组件事件发射出来的一个动作. 事件名 像组件和props,事件名也会进行自动转换,如果你在子组件里发射一个驼峰命名的事件,你 ...

  3. 围绕 Kubernetes 的 8 大 DevOps 生产关键实践

    本文主要介绍 DevOps 的 8 大关键实践在 Kubernetes 平台下如何落地,结合我们目前基于 Kubernetes 平台的 DevOps 实践谈谈是如何贯彻相关理念的,这里不会对其具体实现 ...

  4. C++并发与多线程学习笔记--基本概念和实现

    基本概念 并发 可执行程序.进程.线程 学习心得 并发的实现方法 多进程并发 多线程并发 总结 C++标准库 基本概念 (并发.进程.线程)区分C++初级编程和中高级编程 并发 两个或者更多的任务同时 ...

  5. [Fundamental of Power Electronics]-PART II-9. 控制器设计-9.4 稳定性

    9.4 稳定性 众所周知的是,增加反馈回路可能会导致原本稳定的系统变得不稳定.尽管原变换器传递函数(式(9.1))以及环路增益\(T(s)\)不包含右半平面极点,但式(9.4)的闭环传递函数仍然可能存 ...

  6. 201871030114-蒋鑫 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告

    项目 内容 课程班级博客链接☛ 班级博客 这个作业要求链接☛ 作业要求 我的课程学习目标☛ 1. 体验软件项目开发中的两人合作,练习结对编程(Pair programming).2. 掌握Github ...

  7. Dynamics CRM新加了组织后提示数据加密错误的解决方法

    新加组织后登录报错如下: 这个是因为你新还原的组织原来绑定的加密GUID和现有的组织冲突导致的,所以需要重新为数据加密绑定一个GUID 解决办法:随机生成一个GUID 可以在https://guidg ...

  8. 跨域库herryPostMessage.js的一些优化,多iframe跨域

    旧库见文章:https://www.cnblogs.com/wuhairui/p/14595893.html 新版库主要做了下多个iframe和父页面交互的优化.主要使用构造函数的方式将多个ifram ...

  9. Java基础 Java-IO流 深入浅出

    建议阅读 重要性由高到低 Java基础-3 吃透Java IO:字节流.字符流.缓冲流 廖雪峰Java IO Java-IO流 JAVA设计模式初探之装饰者模式 为什么我觉得 Java 的 IO 很复 ...

  10. Java8中的Stream流式操作 - 入门篇

    作者:汤圆 个人博客:javalover.cc 前言 之前总是朋友朋友的叫,感觉有套近乎的嫌疑,所以后面还是给大家改个称呼吧 因为大家是来看东西的,所以暂且叫做官人吧(灵感来自于民间流传的四大名著之一 ...