spring_AOP
Posted doaoao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring_AOP相关的知识,希望对你有一定的参考价值。
我们先创建下方一个简单的程序,再从长远角度来看,该程序存在的问题
# 比如我们创建一个可以记录日志的工具类,然后在我们实际开发应用过程中使用这个工具类,代码如下
1:创建一个记录日志的工具类
package com.doaoao.util; public class TestLog { // 记录日志 public static void doLog(Class<?> c){ System.out.println("记录日志"+ c.getName()); } }2:在实际应用中使用这个工具类
package com.doaoao.impl; import com.doaoao.dao.UserDao; import com.doaoao.util.TestLog; import org.springframework.stereotype.Repository; @Repository("userDao") public class UserDaoImpl implements UserDao { @Override public void addUser() { // 记录日志代码 TestLog.doLog(this.getClass()); // 实际的业务 System.out.println("实际操作"); } }
注:上方直接将日志代码添加在业务逻辑中,会导致这个工具了侵入到业务逻辑的代码中,如果我们有多个业务逻辑,比如实现某个数据库的数据的增删改查,我们就必须在每个业务逻辑中添加该代码,当修改日志代码时,就得在所有的方法中将代码进行修改,可维护性太差了。同时页增加了业务的开发难度
## 使用动态代理
使用动态代理,可以让之前遇到的问题得以解决,上代码
1:创建动态代理类
package com.doaoao.proxy; import com.doaoao.util.TestLog; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class TestInvocationHandler implements InvocationHandler { // 添加成员变量(使用Object类型可以处理所有类型 userDao,studentDao...) private Object object; // 添加构造方法将要增强的类的对象传入 public TestInvocationHandler(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 记录日志 TestLog.doLog(object.getClass()); // 业务处理 method.invoke(object,args); return null; } }2:修改之前的类(修改后你会发现,世界是多么的清净)
package com.doaoao.impl; import com.doaoao.dao.UserDao; public class UserDaoImpl implements UserDao { @Override public void addUser() { // 实际的业务 System.out.println("实际操作"); } }3:创建测试方法
package com.doaoao.test; import com.doaoao.dao.UserDao; import com.doaoao.impl.UserDaoImpl; import com.doaoao.proxy.TestInvocationHandler; import org.junit.jupiter.api.Test; import java.lang.reflect.Proxy; public class test01 { @Test public void testProxy(){ // 创建目标类对象 UserDao userDao = new UserDaoImpl(); // 创建代理 UserDao userDaoPeoxy = (UserDao) Proxy.newProxyInstance(userDao.getClass().getClassLoader(),userDao.getClass().getInterfaces(),new TestInvocationHandler(userDao)); // 调用目标对象中的方法 userDaoPeoxy.addUser(); } }## AOP
# AOP(Aspect Orient Programming),面向切面编程;我们都知道Java是一种面向对象编程语言,其实这两种之间是没有冲突的,反而面向切面编程是面向对象编程的一种补充。在程序运行过程中,动态的将代码切入到类的指定位置(如上方的代码切入到业务处理之前),指定位置的编程思想就是面向切面编程。
# AOP的最大好处就是可以很好的分离业务代码和非业务代码,降低代码之间的耦合,提高代码的复用性
# 我们来回顾一下spring为了简化Java开发所采取的4中方法
1:基于POJO的轻量级和最小侵入性编程
2:通过依赖注入和面向接口实现松耦合
3:基于切面和惯例进行声明式编程
4:通过切面和模板减少样板式代码
其中面向切面就是简化Java开发的一种重要的方法
# spring底层使用两种代理模式来实现AOP
1:JDK的动态代理(如果目标类实现接口,spring默认使用JDK的动态代理)
底层使用反射,效率较低,JDK动态代理由JDK提供,不需要再额外添加Jar包
2:CGLIB的动态代理(不管目标类没有实现接口,spring 都会使用CGLIB动态代理)
底层使用字节码处理器,效率强于JDK的动态代理,需要添加字节码处理框架ASM,也需要CGLIB框架
AOP中常用的术语
1:目标对象(Target)
目标对象指将要被增强的对象,即包含主业务逻辑的类的对象。上例中的 UserDaoImpl 的对象若被增强,则该类称为目标类,UserDaoImpl实例化的类为目标类2:切面(Aspect)
切面泛指非业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面有通知3:织入(Weaving)
织入是指将切面代码插入到目标对象的过程。上例中 MyInvocationHandler 类中的 invoke()
方法完成的工作,就可以称为织入。4:连接点(JoinPoint)
连接点指可以被切面织入的方法,如之前例子中的 addUser ,通常业务接口中的方法均为连接点。5:切入点(Pointcut)
切入点指切面具体织入的方法。在 UserDaoImpl 类中,若 addUser()被增强,而doOther()不被增强,则 addUser()为切入点,而 doOther()仅为连接点。 被标记为 final 的方法是不能作为连接点与切入点的,因为是不能被修改的,不能被增强的。6:通知(Advice)
指定切面在切入点的之前还是之后执行
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。上例中的MyInvocationHandler 就可以理解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。Advice有下面几种,这里使用常用的AspectJ方式:前置通知(Before advice):在连接点之前执行,即目标方法执行之前执行。
后置通知(After returning advice):在连接点正常结束之后执行,如果连接点抛出异常,则不执行。
异常通知(After throwing advice):在连接点抛出异常后执行
最终通知(After (finally) advice):在连接点结束之后执行,无论是否抛出异常,都会执行。
环绕通知(Around advice):在连接点之前和之后均执行。
7:aop代理(AOP proxy)
spring中的aop代理有两种:jdk自带的动态代理和CGLIB代理。
Aspect和框架和spring都对AOP进行了实现,Aspect的实现方法更为方便快捷。支持注解式开发
## 基于xml实现AOP的方式
1:添加相应的依赖jar包
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.4.RELEASE</version> </dependency>2:创建UserService接口
package com.doaoao.dao; public interface UserService { void addUser(); void selectUserById(int id) throws Exception; int updateUser(); void deleteUser(); void selectUser(); }3:创建UserService接口的实现类
package com.doaoao.impl; import com.doaoao.dao.UserService; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("执行addUser方法"); } @Override public void selectUserById(int id) throws Exception { System.out.println("执行selectUserById方法"); if(id == 0){ throw new Exception(); } } @Override public int updateUser() { System.out.println("执行updateUser方法"); return 1; } @Override public void deleteUser() { System.out.println("执行deleteUser方法"); } @Override public void selectUser() { System.out.println("执行selectUser方法"); } }4:创建切面类MyAspect
package com.doaoao.aspect; import org.aspectj.lang.ProceedingJoinPoint; public class MyAspect { public void before(){ System.out.println("前置通知"); } public void after(){ System.out.println("最终通知"); } public void afterThrowing(Exception e){ System.out.println("异常通知" + e); } public void afterReturning(int result){ System.out.println("后置通知" + result); } public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("前环绕通知"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("前环绕通知"); return proceed; } }5:添加spring的配置文件,此处命名为 spring-aop.xml
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 注册bean --> <bean id="userService" class="com.doaoao.impl.UserServiceImpl" /> <bean id="myAspect" class="com.doaoao.aspect.MyAspect" /> <!-- 配置aop --> <aop:config> <!-- 定义切入点 --> <!-- *表示方法可以由任意返回值 -->
<!-- id表示切入点的名称,expression表示切入点的值(其利用execution表达式) --> <aop:pointcut id="addUserPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl.addUser())" /> <aop:pointcut id="selectUserPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl..selectUser())"/> <!-- ".."表示可以有参数也可以没有参数 --> <aop:pointcut id="selectUserByIdPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl.selectUserById(..))"/> <aop:pointcut id="updateUserPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl.updateUser())"/> <aop:pointcut id="deleteUserPointcut" expression="execution(* com.doaoao.impl.UserServiceImpl.deleteUser())"/> <!-- 定义切面 --> <!-- 引用到我们上方定义的bean id 表示应用于哪个切面-->
<!-- method:指定通知该切面中的哪个方法 pointcut-ref:指定该通知要织入的切入点 --> <aop:aspect ref="myAspect"> <!-- 前置通知 --> <aop:before method="before" pointcut-ref="addUserPointcut"/> <!-- 后置通知 "result"参数要与类中定义的参数名一致 --> <aop:after-returning method="afterReturning" pointcut-ref="updateUserPointcut" returning="result"/> <!-- 异常通知 "e"要与类中定义的参数一致,当抛异常时会调用到该方法 --> <aop:after-throwing method="afterThrowing" pointcut-ref="selectUserByIdPointcut" throwing="e"/> <!-- 最终通知 --> <aop:after method="after" pointcut-ref="selectUserPointcut"/> <!-- 环绕通知 --> <aop:around method="around" pointcut-ref="deleteUserPointcut"/> </aop:aspect> </aop:config> </beans><aop:before/>:前置通知 <aop:after-returning/>: 后置通知 <aop:around/>:环绕通知 <aop:after-throwing/>:异常通知 <aop:after/>:最终通知6:创建测试类
package com.doaoao.test; import com.doaoao.dao.UserService; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test01 { @Test public void new_test(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop.xml"); UserService userService = (UserService)context.getBean("userService"); userService.addUser(); userService.selectUser(); try { // 传0时抛异常 userService.selectUserById(0); } catch (Exception e) { e.printStackTrace(); } userService.updateUser(); userService.deleteUser(); } }## AspectJ 的切入点表达式
execution ( [modifiers-pattern] 访问权限类型 ret-type-pattern 返回值类型 [declaring-type-pattern] 全限定性类名 name-pattern(param-pattern) 方法名(参数名) [throws-pattern] 抛出异常类型 )
# 注:上方被[] 包裹的可以省略不写,而其它的都得写,各个部分以空格隔开
例子:
execution(public * *(..)) // 指定切入点为:任意public方法,返回值类型任意(第一个*号),任意方法名(第二个*号),参数可有可无(..) execution(* set*(..)) // 指定切入点为:任何一个以“set”开始的方法, execution(* com.doaoao.*.*(..)) // 指定切入点为:任意返回值类型,定义在 service 包里的任意类中的任意方法(com.doaoao.*.*) execution(* com.xyz.service..*.*(..)) // 指定切入点为:定义在 service 包或者子包里的任意类的任意方法。“..”出现在类名中时,后面必须跟“*”,表示包、子包下的所有类。 execution(* *.service.*.*(..)) // 指定只有一级包下的 serivce 子包下所有类(接口)中所有方法为切入点 execution(* *..service.*.*(..)) // 指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点 execution(* *.ISomeService.*(..)) // 指定只有一级包下的 ISomeSerivce 接口中所有方法为切入点 execution(* *..ISomeService.*(..)) // 指定所有包下的 ISomeSerivce 接口中所有方法为切入点 execution(* com.xyz.service.IAccountService.*(..)) // 指定切入点为: IAccountService 接口中的任意方法。 execution(* com.xyz.service.IAccountService+.*(..)) // 指定切入点为: IAccountService 若为接口,则为接口中的任意方法及其所有实现类中的任意方法;若为类,则为该类及其子类中的任意方法。 execution(* joke(String,int))) // 指定切入点为:所有的 joke(String,int)方法,且 joke()方法的第一个参数是 String,第二个参 数是 int。如果方法中的参数类型是 java.lang 包下的类,可以直接使用类名,否则必须使用全限定类名,如 joke( java.util.List, int)。 execution(* joke(String,*))) // 指定切入点为:所有的 joke()方法,该方法第一个参数为 String,第二个参数可以是任意类型,如 joke(String s1,String s2)和 joke(String s1,double d2)都是,但 joke(String s1,double d2,String s3)不是。 execution(* joke(String,..))) // 指定切入点为:所有的 joke()方法,该方法第 一个参数为 String,后面可以有任意个参数且参数类型不限,如 joke(String s1)、joke(String s1,String s2)和 joke(Strings1,double d2,String s3)都是。 execution(* joke(Object)) // 指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型。joke(Object ob)是,但,joke(String s)与 joke(User u)均不是。 execution(* joke(Object+))) // 指定切入点为:所有的 joke()方法,方法拥有一个参数,且参数是 Object 类型或该类的子类。不仅 joke(Object ob)是,joke(String s)和 joke(User u)也是。## 基于注解的AOP 实现方式
1:添加对应依赖
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.13</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.4.RELEASE</version> </dependency>2:添加接口(与上方相同)
package com.doaoao.dao; public interface UserService { void addUser(); void selectUserById(int id) throws Exception; int updateUser(); void deleteUser(); void selectUser(); }3:添加接口的实现类(与上方相同)
package com.doaoao.impl; import com.doaoao.dao.UserService; import org.springframework.stereotype.Service; @Service("userService") public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("执行addUser方法"); } @Override public void selectUserById(int id) throws Exception { System.out.println("执行selectUserById方法"); if(id == 0){ throw new Exception(); } } @Override public int updateUser() { System.out.println("执行updateUser方法"); return 1; } @Override public void deleteUser() { System.out.println("执行deleteUser方法"); } @Override public void selectUser() { System.out.println("执行selectUser方法"); } }4:添加切面类(不同于上方)
package com.doaoao.aspect; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; @Aspect @Component public class MyAspect { @Before("execution(* *..UserServiceImpl.addUser())") public void before(){ System.out.println("前置通知"); } @After("execution(* *..UserServiceImpl.selectUser())") public void after(){ System.out.println("最终通知"); } @AfterThrowing(value = "execution(* *..UserServiceImpl.selectUserById(..))" ,throwing = "e") public void afterThrowing(Exception e){ System.out.println("异常通知" + e); } @AfterReturning(value = "execution(* *..UserServiceImpl.updateUser())",returning = "result") public void afterReturning(int result){ System.out.println("后置通知" + result); } @Around(value = "execution(* *..UserServiceImpl.deleteUser())") public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable{ System.out.println("前环绕通知"); Object proceed = proceedingJoinPoint.proceed(); System.out.println("前环绕通知"); return proceed; } }5:添加配置文件(不同于上方)
<?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:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="com.doaoao" /> <!-- 配置AspectJ自动代理 --> <aop:aspectj-autoproxy /> </beans>6:添加测试方法(与上方相同)
package com.doaoao.test; import com.doaoao.dao.UserService; import org.junit.jupiter.api.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Test01 { @Test public void new_test(){ ApplicationContext context = new ClassPathXmlApplicationContext("spring-aop.xml"); UserService userService = (UserService)context.getBean("userService"); userService.addUser(); userService.selectUser(); try { // 传0时抛异常 userService.selectUserById(0); } catch (Exception e) { e.printStackTrace(); } userService.updateUser(); userService.deleteUser(); } }...待补充
本笔记参考自:小猴子老师教程 http://www.monkey1024.com
以上是关于spring_AOP的主要内容,如果未能解决你的问题,请参考以下文章