北京哪个网站制作公司,广州企业网站建站公司哪家好,商城网站建设服务,深圳市建设文章目录 简介相关术语①横切关注点②通知#xff08;增强#xff09;③切面④目标⑤代理⑥连接点⑦切入点 场景模拟代理模式静态代理动态代理 基于注解的AOP#xff08;重点#xff09;准备工作各种通知切入点表达式语法重用切入点表达式获取通知的相关信息 环绕通知 切面… 文章目录 简介相关术语①横切关注点②通知增强③切面④目标⑤代理⑥连接点⑦切入点 场景模拟代理模式静态代理动态代理 基于注解的AOP重点准备工作各种通知切入点表达式语法重用切入点表达式获取通知的相关信息 环绕通知 切面的优先级基于XML的AOP(看看就行) 简介
AOPAspect Oriented Programming是一种设计思想是软件设计领域中的面向切面编程它是面向对象编程的一种补充和完善它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离从而使得业务逻辑各部分之间的耦合度降低提高程序的可重用性同时提高了开发的效率。 作用 简化代码把方法中固定位置的重复的代码抽取出来让被抽取的方法更专注于自己的核心功能提高内聚性。 代码增强把特定的功能封装到切面类中看哪里有需要就往上套被套用了切面逻辑的方法就被切面给增强了。
相关术语
①横切关注点
分散在每个各个模块中解决同一样的问题如用户验证、日志管理、事务处理、数据缓存都属于横切关注点。
从每个方法中抽取出来的同一类非核心业务。在同一个项目中我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。
这个概念不是语法层面的而是根据附加功能的逻辑上的需要有十个附加功能就有十个横切关注点。 ②通知增强
增强通俗说就是你想要增强的功能比如 安全事务日志等。
每一个横切关注点上要做的事情都需要写一个方法来实现这样的方法就叫通知方法。
前置通知在被代理的目标方法前执行返回通知在被代理的目标方法成功结束后执行寿终正寝异常通知在被代理的目标方法异常结束后执行死于非命后置通知在被代理的目标方法最终结束后执行盖棺定论环绕通知使用try…catch…finally结构围绕整个被代理的目标方法包括上面四种通知对应的所有位置
③切面
封装通知方法的类。
④目标
被代理的目标对象。
⑤代理
向目标对象应用通知之后创建的代理对象。
⑥连接点
这也是一个纯逻辑概念不是语法定义的。
把方法排成一排每一个横切位置看成x轴方向把方法从上到下执行的顺序看成y轴x轴和y轴的交叉点就是连接点。通俗说就是spring允许你使用通知的地方
⑦切入点
定位连接点的方式。
每个类的方法中都包含多个连接点所以连接点是类中客观存在的事物从逻辑上来说。
如果把连接点看作数据库中的记录那么切入点就是查询记录的 SQL 语句。
Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说要实际去增强的方法
切点通过 org.springframework.aop.Pointcut 接口进行描述它使用类和方法作为连接点的查询条件。 场景模拟
声明计算器接口Calculator包含加减乘除的抽象方法
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);}创建实现类
public class CalculatorImpl implements Calculator {Overridepublic int add(int i, int j) {int result i j;System.out.println(方法内部 result result);return result;}Overridepublic int sub(int i, int j) {int result i - j;System.out.println(方法内部 result result);return result;}Overridepublic int mul(int i, int j) {int result i * j;System.out.println(方法内部 result result);return result;}Overridepublic int div(int i, int j) {int result i / j;System.out.println(方法内部 result result);return result;}
}创建带日志功能的实现类
public class CalculatorLogImpl implements Calculator {Overridepublic int add(int i, int j) {System.out.println([日志] add 方法开始了参数是 i , j);int result i j;System.out.println(方法内部 result result);System.out.println([日志] add 方法结束了结果是 result);return result;}Overridepublic int sub(int i, int j) {System.out.println([日志] sub 方法开始了参数是 i , j);int result i - j;System.out.println(方法内部 result result);System.out.println([日志] sub 方法结束了结果是 result);return result;}Overridepublic int mul(int i, int j) {System.out.println([日志] mul 方法开始了参数是 i , j);int result i * j;System.out.println(方法内部 result result);System.out.println([日志] mul 方法结束了结果是 result);return result;}Overridepublic int div(int i, int j) {System.out.println([日志] div 方法开始了参数是 i , j);int result i / j;System.out.println(方法内部 result result);System.out.println([日志] div 方法结束了结果是 result);return result;}
}提出问题 ①现有代码缺陷 针对带日志功能的实现类我们发现有如下缺陷 对核心业务功能有干扰导致程序员在开发核心业务功能时分散了精力附加功能分散在各个业务功能方法中不利于统一维护 ②解决思路 解决这两个问题核心就是解耦。我们需要把附加功能从业务功能代码中抽取出来。 ③困难 解决问题的困难要抽取的代码在方法内部靠以前把子类中的重复代码抽取到父类的方式没法解决。所以需要引入新的技术。 代理模式 静态代理
创建静态代理类
public class CalculatorStaticProxy implements Calculator {// 将被代理的目标对象声明为成员变量private Calculator target;public CalculatorStaticProxy(Calculator target) {this.target target;}Overridepublic int add(int i, int j) {// 附加功能由代理类中的代理方法来实现System.out.println([日志] add 方法开始了参数是 i , j);// 通过目标对象来实现核心业务逻辑int addResult target.add(i, j);System.out.println([日志] add 方法结束了结果是 addResult);return addResult;}
}静态代理确实实现了解耦但是由于代码都写死了完全不具备任何的灵活性。就拿日志功能来说将来其他地方也需要附加日志那还得再声明更多个静态代理类那就产生了大量重复的代码日志功能还是分散的没有统一管理。
提出进一步的需求将日志功能集中到一个代理类中将来有任何日志需求都通过这一个代理类来实现。这就需要使用动态代理技术了。
动态代理
public class ProxyFactory {private Object target;public ProxyFactory(Object target) {this.target target;}public Object getProxy(){/*** newProxyInstance()创建一个代理实例* 其中有三个参数* 1、classLoader加载动态生成的代理类的类加载器* 2、interfaces目标对象实现的所有接口的class对象所组成的数组* 3、invocationHandler设置代理对象实现目标对象方法的过程即代理类中如何重写接口中的抽象方法*/ClassLoader classLoader target.getClass().getClassLoader();Class?[] interfaces target.getClass().getInterfaces();InvocationHandler invocationHandler new InvocationHandler() {Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {/*** proxy代理对象* method代理对象需要实现的方法即其中需要重写的方法* argsmethod所对应方法的参数*/Object result null;try {System.out.println([动态代理][日志] method.getName()参数 Arrays.toString(args));result method.invoke(target, args);System.out.println([动态代理][日志] method.getName()结果 result);} catch (Exception e) {e.printStackTrace();System.out.println([动态代理][日志] method.getName()异常e.getMessage());} finally {System.out.println([动态代理][日志] method.getName()方法执行完毕);}return result;}};return Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);}
}测试
Test
public void testDynamicProxy(){ProxyFactory factory new ProxyFactory(new CalculatorLogImpl());Calculator proxy (Calculator) factory.getProxy();proxy.div(1,0); //报错//proxy.div(1,1);
}基于注解的AOP重点
技术说明
动态代理分为JDK动态代理和cglib动态代理当目标类有接口的情况使用JDK动态代理和cglib动态代理没有接口时只能使用cglib动态代理JDK动态代理动态生成的代理类会在com.sun.proxy包下类名为$proxy1和目标类实现相同的接口cglib动态代理动态生成的代理类会和目标在在相同的包下会继承目标类动态代理InvocationHandlerJDK原生的实现方式需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口兄弟两个拜把子模式。cglib通过继承被代理的目标类认干爹模式实现代理所以不需要目标类实现接口。AspectJ是AOP思想的一种实现。本质上是静态代理将代理逻辑“织入”被代理的目标类编译得到的字节码文件所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解。
准备工作
①添加依赖 在IOC所需依赖基础上再加入下面依赖即可
dependencies!--spring context依赖--!--当你引入Spring Context依赖之后表示将Spring的基础依赖引入了--dependencygroupIdorg.springframework/groupIdartifactIdspring-context/artifactIdversion6.0.2/version/dependency!--spring aop依赖--dependencygroupIdorg.springframework/groupIdartifactIdspring-aop/artifactIdversion6.0.2/version/dependency!--spring aspects依赖--dependencygroupIdorg.springframework/groupIdartifactIdspring-aspects/artifactIdversion6.0.2/version/dependency!--junit5测试--dependencygroupIdorg.junit.jupiter/groupIdartifactIdjunit-jupiter-api/artifactIdversion5.3.1/version/dependency!--log4j2的依赖--dependencygroupIdorg.apache.logging.log4j/groupIdartifactIdlog4j-core/artifactIdversion2.19.0/version/dependencydependencygroupIdorg.apache.logging.log4j/groupIdartifactIdlog4j-slf4j2-impl/artifactIdversion2.19.0/version/dependency
/dependencies②准备被代理的目标资源 接口
public interface Calculator {int add(int i, int j);int sub(int i, int j);int mul(int i, int j);int div(int i, int j);}实现类
Component
public class CalculatorImpl implements Calculator {Overridepublic int add(int i, int j) {int result i j;System.out.println(方法内部 result result);return result;}Overridepublic int sub(int i, int j) {int result i - j;System.out.println(方法内部 result result);return result;}Overridepublic int mul(int i, int j) {int result i * j;System.out.println(方法内部 result result);return result;}Overridepublic int div(int i, int j) {int result i / j;System.out.println(方法内部 result result);return result;}
}创建切面类并配置
package com.test.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;import java.util.Arrays;// Aspect表示这个类是一个切面类
Aspect
// Component注解保证这个切面类能够放入IOC容器
Component
public class LogAspect {Before(execution(public int com.test.aop.CalculatorImpl.*(..)))public void beforeMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);}After(execution(* com.test.aop.CalculatorImpl.*(..)))public void afterMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();System.out.println(Logger--后置通知方法名methodName);}AfterReturning(value execution(* com.test.aop.CalculatorImpl.*(..)), returning result)public void afterReturningMethod(JoinPoint joinPoint, Object result){String methodName joinPoint.getSignature().getName();System.out.println(Logger--返回通知方法名methodName结果result);}AfterThrowing(value execution(* com.test.aop.CalculatorImpl.*(..)), throwing ex)public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){String methodName joinPoint.getSignature().getName();System.out.println(Logger--异常通知方法名methodName异常ex);}Around(execution(* com.test.aop.CalculatorImpl.*(..)))public Object aroundMethod(ProceedingJoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());Object result null;try {System.out.println(环绕通知--目标对象方法执行之前);//目标对象连接点方法的执行result joinPoint.proceed();System.out.println(环绕通知--目标对象方法返回值之后);} catch (Throwable throwable) {throwable.printStackTrace();System.out.println(环绕通知--目标对象方法出现异常时);} finally {System.out.println(环绕通知--目标对象方法执行完毕);}return result;}}
在Spring的配置文件中配置
?xml version1.0 encodingUTF-8?
beans xmlnshttp://www.springframework.org/schema/beansxmlns:xsihttp://www.w3.org/2001/XMLSchema-instancexmlns:contexthttp://www.springframework.org/schema/contextxmlns:aophttp://www.springframework.org/schema/aopxsi:schemaLocationhttp://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd!--基于注解的AOP的实现1、将目标对象和切面交给IOC容器管理注解扫描2、开启AspectJ的自动代理为目标对象自动生成代理3、将切面类通过注解Aspect标识--context:component-scan base-packagecom.test.aop/context:component-scanaop:aspectj-autoproxy /
/beans执行测试
public class CalculatorTest {private Logger logger LoggerFactory.getLogger(CalculatorTest.class);Testpublic void testAdd(){ApplicationContext ac new ClassPathXmlApplicationContext(beans.xml);Calculator calculator ac.getBean( Calculator.class);int add calculator.add(1, 1);logger.info(执行成功:add);}}各种通知
前置通知使用Before注解标识在被代理的目标方法前执行返回通知使用AfterReturning注解标识在被代理的目标方法成功结束后执行寿终正寝异常通知使用AfterThrowing注解标识在被代理的目标方法异常结束后执行死于非命后置通知使用After注解标识在被代理的目标方法最终结束后执行盖棺定论环绕通知使用Around注解标识使用try…catch…finally结构围绕整个被代理的目标方法包括上面四种通知对应的所有位置 各种通知的执行顺序 Spring版本5.3.x以前 前置通知目标操作后置通知返回通知或异常通知 Spring版本5.3.x以后 前置通知目标操作返回通知或异常通知后置通知 切入点表达式语法
①作用 ②语法细节 用*号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限 在包名的部分一个“*”号只能代表包的层次结构中的一层表示这一层是任意的。 例如*.Hello匹配com.Hello不匹配com.test.Hello 在包名的部分使用“*…”表示包名任意、包的层次深度任意 在类名的部分类名部分整体用*号代替表示类名任意 在类名的部分可以使用*号代替类名的一部分 例如*Service匹配所有名称以Service结尾的类或接口 在方法名部分可以使用*号表示方法名任意 在方法名部分可以使用*号代替方法名的一部分 例如*Operation匹配所有方法名以Operation结尾的方法 在方法参数列表部分使用(…)表示参数列表任意 在方法参数列表部分使用(int,…)表示参数列表以一个int类型的参数开头 在方法参数列表部分基本数据类型和对应的包装类型是不一样的 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的 在方法返回值部分如果想要明确指定一个返回值类型那么必须同时写明权限修饰符 例如execution(public int …Service.(…, int)) 正确 例如execution( int *…Service.(…, int)) 错误
重用切入点表达式
①声明
Pointcut(execution(* com.test.aop.*.*(..)))
public void pointCut(){}②在同一个切面中使用
Before(pointCut())
public void beforeMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);
}③在不同切面中使用
Before(com.test.aop.CommonPointCut.pointCut())
public void beforeMethod(JoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);
}获取通知的相关信息
①获取连接点信息
获取连接点信息可以在通知方法的参数位置设置JoinPoint类型的形参
Before(execution(public int com.test.aop.CalculatorImpl.*(..)))
public void beforeMethod(JoinPoint joinPoint){//获取连接点的签名信息String methodName joinPoint.getSignature().getName();//获取目标方法到的实参信息String args Arrays.toString(joinPoint.getArgs());System.out.println(Logger--前置通知方法名methodName参数args);
}②获取目标方法的返回值
AfterReturning中的属性returning用来将通知方法的某个形参接收目标方法的返回值
AfterReturning(value execution(* com.test.aop.CalculatorImpl.*(..)), returning result)
public void afterReturningMethod(JoinPoint joinPoint, Object result){String methodName joinPoint.getSignature().getName();System.out.println(Logger--返回通知方法名methodName结果result);
}③获取目标方法的异常
AfterThrowing中的属性throwing用来将通知方法的某个形参接收目标方法的异常
AfterThrowing(value execution(* com.test.aop.CalculatorImpl.*(..)), throwing ex)
public void afterThrowingMethod(JoinPoint joinPoint, Throwable ex){String methodName joinPoint.getSignature().getName();System.out.println(Logger--异常通知方法名methodName异常ex);
}环绕通知
Around(execution(* com.test.aop.CalculatorImpl.*(..)))
public Object aroundMethod(ProceedingJoinPoint joinPoint){String methodName joinPoint.getSignature().getName();String args Arrays.toString(joinPoint.getArgs());Object result null;try {System.out.println(环绕通知--目标对象方法执行之前);//目标方法的执行目标方法的返回值一定要返回给外界调用者result joinPoint.proceed();System.out.println(环绕通知--目标对象方法返回值之后);} catch (Throwable throwable) {throwable.printStackTrace();System.out.println(环绕通知--目标对象方法出现异常时);} finally {System.out.println(环绕通知--目标对象方法执行完毕);}return result;
}切面的优先级
相同目标方法上同时存在多个切面时切面的优先级控制切面的内外嵌套顺序。
优先级高的切面外面优先级低的切面里面
使用Order注解可以控制切面的优先级
Order(较小的数)优先级高Order(较大的数)优先级低 基于XML的AOP(看看就行)
注意注解的AOP环境 aop.xml
context:component-scan base-packagecom.test.aop.xml/context:component-scanaop:config!--配置切面类--aop:aspect refloggerAspectaop:pointcut idpointCut expressionexecution(* com.test.aop.xml.CalculatorImpl.*(..))/aop:before methodbeforeMethod pointcut-refpointCut/aop:beforeaop:after methodafterMethod pointcut-refpointCut/aop:afteraop:after-returning methodafterReturningMethod returningresult pointcut-refpointCut/aop:after-returningaop:after-throwing methodafterThrowingMethod throwingex pointcut-refpointCut/aop:after-throwingaop:around methodaroundMethod pointcut-refpointCut/aop:around/aop:aspect
/aop:config