文章目录
spring基础之-1-spring aop
一、相关概念
面向切面编程 aop (aspect oriented program)
将程序中不同业务的各个小而类似的功能抽取出来,在运行时动态织入增强的逻辑;
一般用于日志等的横向开发。
二、实现方式
1、jdk动态代理: 基于 目标类的接口来生成动态代理对象
-
接口
package org.jnr.proxy.jdk; /** * @program: Spring复习 * @description: * @author: jin rong@JNR * @create: 2020-11-20 10:02 **/ public interface TargetInterface { void insert(); void delete(); void update(); void queryOne(); void queryListAll(); }
-
目标对象
package org.jnr.proxy.jdk; /** * @program: Spring复习 * @description: * @author: jin rong@JNR * @create: 2020-11-20 10:03 **/ public class Target implements TargetInterface { @Override public void insert() { System.out.println("insert running ..."); } @Override public void delete() { System.out.println("delete running ..."); } @Override public void update() { System.out.println("update running ..."); } @Override public void queryOne() { System.out.println("queryOne running ..."); } @Override public void queryListAll() { System.out.println("queryListAll running ..."); } }
-
切面通知类
package org.jnr.proxy.jdk; /** * @program: Spring复习 * @description: 通知类 * @author: jin rong@JNR * @create: 2020-11-20 10:05 **/ public class Advice { public void before() { System.out.println("前置通知"); } public void afterRetuning() { System.out.println("后置通知"); } }
-
实现/测试
package org.jnr.proxy.jdk; /** * @program: Spring复习 * @description: * @author: jin rong@JNR * @create: 2020-11-20 10:04 **/ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 基于 jdk 接口 目标对象 代理对象 的动态代理 */ public class ProxyTest { public static void main(String[] args) { //目标对象 final Target target = new Target(); //通知对象 final Advice advice = new Advice(); //代理对象 Object o = Proxy.newProxyInstance( target.getClass().getClassLoader(), //目标对象类加载器 target.getClass().getInterfaces(),//目标对象所有接口 数组 new InvocationHandler() {//代理对象执行的方法,其中执行目标对象方法和增强逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.before();//前置通知 Object invoke = method.invoke(target, args);//目标方法 advice.afterRetuning();//后置通知 return invoke; } } ); //由于 代理对象 和 目标对象 都是实现了 接口,因此是兄弟类,只能强转为接口(父) TargetInterface targetInterface = (TargetInterface) o; //执行 目标方法,自动增强逻辑 targetInterface.insert(); targetInterface.delete(); targetInterface.update(); targetInterface.queryOne(); targetInterface.queryListAll(); } }
-
结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hfkIoM8T-1605925719261)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20201121101645403.png)]
2、 cglib动态代理 :通过生成一个继承自目标类的代理类来实现
-
目标对象
package org.jnr.proxy.cglib; import org.jnr.proxy.jdk.TargetInterface; /** * @program: Spring复习 * @description: * @author: jin rong@JNR * @create: 2020-11-20 10:03 **/ public class Target { public void insert() { System.out.println("insert running ..."); } public void delete() { System.out.println("delete running ..."); } public void update() { System.out.println("update running ..."); } public void queryOne() { System.out.println("queryOne running ..."); } public void queryListAll() { System.out.println("queryListAll running ..."); } }
-
切面类
package org.jnr.proxy.cglib; /** * @program: Spring复习 * @description: 通知类 * @author: jin rong@JNR * @create: 2020-11-20 10:05 **/ public class Advice { public void before() { System.out.println("前置通知"); } public void afterRetuning() { System.out.println("后置通知"); } }
-
实现/测试
package org.jnr.proxy.cglib; /** * @program: Spring复习 * @description: * @author: jin rong@JNR * @create: 2020-11-20 10:04 **/ import org.jnr.proxy.jdk.TargetInterface; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; import org.springframework.cglib.proxy.MethodProxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 基于 cglib 继承自 目标对象的 代理对象 的动态代理 */ public class ProxyTest { public static void main(String[] args) { //目标对象 final Target target = new Target(); //通知对象 final Advice advice = new Advice(); //创建增强器 Enhancer enhancer = new Enhancer(); //设置父类 enhancer.setSuperclass(target.getClass()); //设置回调方法 enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { advice.before();//前置通知 Object invoke = method.invoke(target, args);//目标方法 advice.afterRetuning();//后置通知 return invoke; } }); //生成代理对象 Target proxy = (Target) enhancer.create(); //执行 目标方法,自动增强逻辑 proxy.insert(); proxy.delete(); proxy.update(); proxy.queryOne(); proxy.queryListAll(); } }
-
结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ypt8VaV3-1605925719275)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20201121101611197.png)]
三、使用 spring aop
spring aop 需要导入 spring-aop 和 spring-aspect (或 spring-aspectjweave,是第三方的,但是spring也集成了,这个包在 spring-aspects依赖中有,因为 aspectj 做的比 spring 提供的好,所以一般都是用他的)
1、 基于 xml 配置方式
-
编写目标对象接口
package org.jnr.aop.xml; /** * @program: Spring复习 * @description: 目标对象接口 * @author: jin rong@JNR * @create: 2020-11-20 23:07 **/ public interface TargetInterface { void insert(); void delete(); void update(); void select(); }
-
编写目标对象
package org.jnr.aop.xml.impl; import org.jnr.aop.xml.TargetInterface; /** * @program: Spring复习 * @description: 目标对象 * @author: jin rong@JNR * @create: 2020-11-20 23:07 **/ public class Target implements TargetInterface { @Override public void insert() { System.out.println("Target insert ..."); } @Override public void delete() { System.out.println("Target delete..."); } @Override public void update() { System.out.println("Target update..."); } @Override public void select() { System.out.println("Target select ..."); } }
-
编写切面类(用于增强/通知的类)
package org.jnr.aop.xml; /** * @program: Spring复习 * @description: 通过xml配置aop * @author: jin rong@JNR * @create: 2020-11-20 23:05 **/ public class MyAspectByXml { public void before() { System.out.println("前置通知"); } public void afterReturning() { System.out.println("后置通知"); } }
-
在springioc容器中加载上面目标对象、切面类
<bean id="target" class="org.jnr.aop.xml.impl.Target"></bean> <bean id="myAspectByXml" class="org.jnr.aop.xml.MyAspectByXml"></bean>
-
在springioC容器中配置织入过程(需要导入aop命名空间和文件)(即配置切面(切入点+通知方法))
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 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 "> <!-- 加载注入 上面目标对象、切面类 --> <bean id="target" class="org.jnr.aop.xml.impl.Target"></bean> <bean id="myAspectByXml" class="org.jnr.aop.xml.MyAspectByXml"></bean> <aop:config> <!-- 配置切面 --> <aop:aspect ref="myAspectByXml"> <!-- 配置切面 (切入点+增强方法) --> <aop:before method="before" pointcut="execution(public void org.jnr.aop.xml.impl.Target.*())"></aop:before> </aop:aspect> </aop:config> </beans>
-
使用junit+spring-text 测试
package org.jnr; import org.jnr.aop.xml.TargetInterface; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @program: Spring复习 * @description: 通过 xml 方式配置使用 spring aop * @author: jin rong@JNR * @create: 2020-11-20 23:13 **/ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:Application-aop.xml") public class AopByXmlTest { @Autowired private TargetInterface target; @Test public void test01() { target.insert(); target.delete(); target.update(); target.select(); } }
2、基于注解的配置方式
-
编写目标对象接口
package org.jnr.aop.annotation; /** * @program: Spring复习 * @description: 目标对象接口 * @author: jin rong@JNR * @create: 2020-11-20 23:07 **/ public interface TargetInterface { void insert(); void delete(); void update(); void select(); }
-
编写目标对象
package org.jnr.aop.annotation.impl; import org.jnr.aop.annotation.TargetInterface; import org.springframework.stereotype.Component; /** * @program: Spring复习 * @description: 目标对象 * @author: jin rong@JNR * @create: 2020-11-20 23:07 **/ @Component public class Target implements TargetInterface { @Override public void insert() { System.out.println("Target insert ..."); } @Override public void delete() { System.out.println("Target delete..."); } @Override public void update() { System.out.println("Target update..."); } @Override public void select() { System.out.println("Target select ..."); System.out.println("即将抛出异常"); int i = 1 / 0; } }
-
编写切面类(用于增强/通知的类)
package org.jnr.aop.annotation; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /** * @program: Spring复习 * @description: 通过xml配置aop * @author: jin rong@JNR * @create: 2020-11-20 23:05 **/ @Aspect @Component public class MyAspectByAnnotation { @Before(value = "execution(public void org.jnr.aop.annotation.impl.Target.*())") public void before(JoinPoint joinPoint) { System.out.println(joinPoint); System.out.println("前置通知"); } @Around(value = "execution(public void org.jnr.aop.annotation.impl.Target.*())") public Object around(ProceedingJoinPoint pjp) throws Throwable { Object proceed = pjp.proceed();//执行 目标方法,下面是增强逻辑 //Proceeding JoinPoint :正在进行中的 连接点,也就是 切点 ;ProceedingJoinPoint 只能用于 环绕通知 System.out.println("环绕通知"); return proceed; } //将切点表达式 execution 抽取出来 @Pointcut(value = "execution(public void org.jnr.aop.annotation.impl.Target.*())")//value="","" 都行 public void pointCut() { } //可以直接用方法名使用切点表达式方法 @After(value = "pointCut()")//value="","" 都行 public void after() { System.out.println("最终通知"); } //也可以用类.切点表达式方法() @AfterReturning(pointcut = "MyAspectByAnnotation.pointCut()")//pointcut="" , value="","" 都行 private void afterReturning() { System.out.println("后置通知"); } @AfterThrowing(value = "execution(* org.jnr.aop.annotation.impl*.*(..))") public void afterThrowing() { System.out.println("异常通知"); } }
-
使用注解在springioc容器中加载上面目标对象、切面类(@Component注解 @Aspect注解、各种通知注解@Before @After @AfterRetuning @Around @AfterThrowing )
参见上面代码
-
编写配置类 扫描包注入容器和 开启aop自动代理注解(@Configuration @ComponentScan @EnableAspectjAutoProxy或者在xml配置文件中 使用aop:aspectj-autoproxy/)
package org.jnr.aop.annotation; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; /** * @program: Spring复习 * @description: * @author: jin rong@JNR * @create: 2020-11-21 08:54 **/ @Configuration @EnableAspectJAutoProxy @ComponentScan(basePackages = "org.jnr.aop.annotation") public class AopConfiguration { }
-
使用junit+spring-text 测试
package org.jnr; import org.jnr.aop.annotation.AopConfiguration; import org.jnr.aop.annotation.TargetInterface; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * @program: Spring复习 * @description: 通过 注解 方式配置使用 spring aop * @author: jin rong@JNR * @create: 2020-11-20 23:13 **/ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AopConfiguration.class) public class AopByAnnotationTest { @Autowired private TargetInterface target; @Test public void test01() { target.insert(); target.delete(); target.update(); target.select(); } }
3、总结
- ProceedingJoinPoint 参数只能用于环绕通知
- aspectj 版本如果太低,比如 1.5.* ,在使用 JoinPoint 作为方法参数时会报 错,建议更新版本为 1.8.1,或者干脆不要单独导入 aspectj依赖,直接导入 spring-aspects 包,里面就有较新的 aspectj 包
- 如果使用注解开发 aop ,记得要在 xml 中开启自动代理 注解 aop:apsect-autoproxy/ 或者在配置类中加上 @EnableAspectjAutoProxy 注解
- spring 框架根据 目标类是否是实现类(有接口)来决定采用哪种 动态代理对象。
4、execution 表达式 大全(记得空格问题)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uv149H7p-1605925719284)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20201121100437179.png)]