长沙房价一览表,哈尔滨seo推广优化,大连建站费用,下载互联网IOC AOP 一、 分层解耦 内聚#xff1a; 软件中各个功能模块内部的功能联系 耦合#xff1a; 衡量软件中各个层/模块之间的依赖、关联的程度 软件设计原则#xff1a;高内聚、低耦合 控制反转#xff1a;Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到…IOC AOP 一、 分层解耦 内聚 软件中各个功能模块内部的功能联系 耦合 衡量软件中各个层/模块之间的依赖、关联的程度 软件设计原则高内聚、低耦合 控制反转Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部容器这种思想成为控制反转
依赖注入Dependency Injection简称DI。容器为应用程序提供运行时所依赖的资源称为依赖注入。
Bean对象 IOC容器中创建、管理的对象称为bean 1.1 IOC - 控制反转 详细 把某个对象交给IOC容器管理需要添加如下注解之一 声名bean的时候可以通过value属性指定bean的名字如果没有指定默认是类名首字母小写
使用以上四个注解都可以生命bean但是在Springboot集成web开发中声名控制器bean只能用Controller
bean的四大注解想要生效需要被组件扫描注解ComponentScan扫描
ComponentScan注解虽然没有显示配置但是实际上已经包含在了启动类生命注解SpringBootApplication中默认扫描的范围是启动类所在包及其子包
如下包名是从“java”包后开始的但是下面这种不推荐我们希望的是按照Spring的规范将包设置在启动类所在包及其子包
ComponentScan({dao,com.zhangjingqi})1.2 DI - 依赖注入 详解 Autowired 注解默认是按照类型进行的如果存在多个相同的bean会报错。
EmpServiceA 实现 EmpService类EmpServiceB 实现 EmpService类我们在某个地方注入EmpService对象时便会出现注入错误。
解决方案
Primary 设置bean的优先级 如果我们想要哪个bean填入容器可以在类名之上添加Primary
Qualifier 指定bean的名字 Qualifier(empServiceA)Autowiredprivate EmpService empService;Resource 按照名称注入
Autowired 注解默认按照类型注入Resource默认按照类名进行注入 Resource(name empServiceB)private EmpService empService;二、AOP 2.1 了解 Spring的第二大核心第一大核心是IOC
AOP面向切面编程、面向方面编程其实就是面向特定方法编程
实现
**动态代理是面向切面编程最主流的实现。**而SpringAOP是Spring框架的高级技术目的是在管理bean对象的过程中主要通过底层的动态代理机制对特定的方法进行编程
为什么要面向方法编程
场景案例部分功能运行较慢定位执行耗时较长的业务方法此时需要统计每一个业务方法的执行耗时找到耗时较长的业务进行优化
按照之前的方式就是在方法开始前和开时候分别获取一个时间两个时间相减就是执行耗时但是这种方式是非常繁琐的 ———————————————— 版权声明本文为CSDN博主「我爱布朗熊」的原创文章遵循CC 4.0 BY-SA版权协议转载请附上原文出处链接及本声明。 原文链接https://blog.csdn.net/weixin_51351637/article/details/130779252二、AOP
2.1 了解 Spring的第二大核心第一大核心是IOC
AOP面向切面编程、面向方面编程其实就是面向特定方法编程
实现
**动态代理是面向切面编程最主流的实现。**而SpringAOP是Spring框架的高级技术目的是在管理bean对象的过程中主要通过底层的动态代理机制对特定的方法进行编程
为什么要面向方法编程
场景案例部分功能运行较慢定位执行耗时较长的业务方法此时需要统计每一个业务方法的执行耗时找到耗时较长的业务进行优化
按照之前的方式就是在方法开始前和开时候分别获取一个时间两个时间相减就是执行耗时但是这种方式是非常繁琐的 如果我们基于AOP面向方法编程我们可以做到在不改动原始方法的基础上来针对原始的方法进行编程可以是对原始方法功能的增强也可以改变原始方法的功能
比如我们现在要统计方法的耗时我们只需要定义一个模板方法将公共的代码定义在模板方法中
原始业务方法在这里指的是需要统计执行耗时的业务方法。而这样面向一个或者多个方法进行编程就称为面向切面编程
比如我们调用list()方法此时并不会直接执行原始的list方法而是自动的去执行模板方法。
模板中所定义的代码逻辑其实是创建出来的代理对象方法中的逻辑 2.2 快速入门 - AOP 开发步骤 需求统计各个业务层方法执行耗时
2.2.1 Maven依赖
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId
/dependency2.2.2 代码实现 针对于特定方法根据业务需要进行编程
Slf4j
Component //交给容器IOC进行管理
Aspect //加上这个注解表示不是一个普通的类而是一个AOP类在此类中定义模板方法
public class TimeAspect {// 参数是一个表达式表示针对哪些特定方法进行编程
// com.zhangjingqi.service 包名
// 第一个*代表任意返回值 第二个*代表类名或者接口名 第三个*代表方法名Around(execution(* com.zhangjingqi.service.*.*(..))) //切入点表达式public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {long begin System.currentTimeMillis();// result 原始方法执行返回值Object result proceedingJoinPoint.proceed();//调用原始方式运行long end System.currentTimeMillis();// proceedingJoinPoint.getSignature() 获取方法的签名我们就知道是哪个方法了
// 如 List com.zhangjingqi.service.impl.DeptServiceImpl.list()执行耗时239mslog.info(proceedingJoinPoint.getSignature() 执行耗时{}ms, end - begin);// 原始方法的返回值我们需要返回回去return result;}
}
2.2.3 AOP 应用场景及优势 应用场景 记录操作日志 权限控制 事务管理 优势 代码无侵入 减少重复代码 提高开发效率 维护方便 2.3 核心概念 2.3.1 连接点 - JoinPoint 连接点JoinPoint可以被AOP控制的方法暗含方法执行时的相关信息
通知Advice指那些重读的逻辑也就是共性功能最终体现为一个方法
切入点PointCut匹配连接点的条件通知仅会在切入点方法执行时被应用就是实际被AOP控制的方法
我们通常会使用下面的切入点表达式来描述切入点
Around(execution(* com.zhangjingqi.service.*.*(..)))
切面Aspect描述通知与切入点的对应关系通知切入点被Aspect注解修饰的类我们一般称为切面类
目标对象Target通知所应用的对象。 2.3.2 AOP执行流程 通知如何与目标对象结合在一起对目标对象中的方法进行功能增强的
①SpringAOP是基于动态代理技术来实现的。程序运行的时候会自动的基于动态代理技术为目标对象生成一个对应的代理对象。
② 在代理对象中就会对目标对象中的原始方法进行功能的增强。
如何来增强的增强的逻辑是什么样子的
其实就是我们的通知
③最终在Spring容器中注入的是代理对象调用的方法也是代理对象中的对应方法 2.4 通知 2.4.1 通知类型 Around环绕通知此注解标注的通知方法在目标方法前、后都被执行出现异常后后置代码不会执行。因为原始方法出现异常了 Before前置通知此注解标注的通知方法在目标方法前被执行 After后置通知此注解标注的通知方法在目标方法后被执行无论是否有异常都会执行 AfterReturning返回后通知此注解标注的通知方法在目标方法后被执行有异常不会执行 AfterThrowing异常后通知此注解标注的通知方法在发生异常后执行
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;Slf4j
Component
Aspect
public class MyAspect1 {//前置通知Before(execution(* com.zhangjingqi.service.*.*(..)))public void before(JoinPoint joinPoint) {log.info(before ...);}//环绕通知Around(execution(* com.zhangjingqi.service.*.*(..)))public Object around(ProceedingJoinPoint proceedingJoinPoint)throws Throwable {log.info(around before ...);//调用目标对象的原始方法执行Object result proceedingJoinPoint.proceed();//原始方法如果执行时有异常环绕通知中的后置代码不会在执行了log.info(around after ...);return result;}//后置通知After(execution(* com.zhangjingqi.service.*.*(..)))public void after(JoinPoint joinPoint) {log.info(after ...);}//返回后通知程序在正常执行的情况下会执行的后置通知AfterReturning(execution(* com.zhangjingqi.service.*.*(..)))public void afterReturning(JoinPoint joinPoint) {log.info(afterReturning ...);}//异常通知程序在出现异常的情况下执行的后置通知AfterThrowing(execution(* com.zhangjingqi.service.*.*(..)))public void afterThrowing(JoinPoint joinPoint) {log.info(afterThrowing ...);}
}
注意事项
Around环绕通知需要自己调用ProceedingJoinPoint.proceed()来执行原始方法其他通知不需要考虑原始方法的执行
Around环绕通知的方法的返回值必须指定为Object来接收原始方法的返回值
如果不return在调用这个方法的地方时拿不到返回值的
对切入点表达式进行抽取
// 生命切入点表达式的注解,切点Pointcut(execution(* com.zhangjingqi.service.*.*(..)))private void pt(){}//前置通知Before(pt())public void before(JoinPoint joinPoint) {log.info(before ...);}
其他类中也可以进行抽取,只需要定位到切入点表达式的位置即可。
Slf4j
Component
Aspect
public class MyAspect2 {
//引用MyAspect1切面类中的切入点表达式
Before(com.zhangjingqi.aspect.MyAspect1.pt())
public void before(){
log.info(MyAspect2 - before ...);}
}2.4.2 通知顺序 当有多个切面的切入点都匹配到了目标方法目标方法运行多个通知方法都会被执行。
下面研究多个切面类的通知顺序。同个切面类的通知顺序不再研究
不同切面类中默认按照切面类的类名字母排序 目标方法前的通知方法字母排名靠前的先执行
目标方法后的通知方法字母排名靠前的后执行
使用Order(数字)加在切面类上来控制顺序 2.5 切入点表达式 切入点表达式描述切入点方法的一种表达式 作用主要用来决定项目中哪些方法需要加入通知 常见形式 execution(…):根据方法的签名来匹配
annotation…根据注解匹配
2.5.1 execution 主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配
下面来描述的时候可以基于接口。也可以基于实现类
execution(访问修饰符? 返回值 包名.类名.?方法名(方法参数) throws 异常)
其中表示可省略的部分
访问修饰符可省略比如public、protected 包名.类名可省略但是不建议 throws 异常可省略注意是方法上声明抛出的异常不是实际抛出的异常 2.5.1.1 execution通配符 *:单个独立的任意符号可以匹配任意返回值、包名、类名、方法名、方法参数等信息来匹配
此案例表示返回值人任意二级包任意类或接口任意方法参数任意但是有且只有一个
execution(* com.*.service.*.update(*)匹配类名以Service结尾方法以delete开头的方法
execution(void
com.itheima.service.impl.*Service.delete*(java.lang.Integer)
)…多个连续的任意符号可以通配任意层级的包或者任意类型、任意个数的参数
层级包任意方法的参数任意
execution(* com.zhangjingqi..DeptService.*(..)返回值任意方法名任意方法参数任意
execution(* *(..)2.5.1.2 execution表达式案例
Pointcut(execution(* com.zhangjingqi.service.*.*(..)))省略异常
execution(public void
com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer)
)省略方法访问修饰符
参数是全类名
execution(void
com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer)
)使用…省略包名
execution(public void com..DeptServiceImpl.delete(java.lang.Integer))省略包名类名 指定方法名。
不建议将包名和方法名省略。一旦省略将表达式的范围扩大一是影响匹配的效率而是可能匹配到其他不需要的方法
execution(public void delete(java.lang.Integer))匹配所有的方法 此时表示匹配DeptServiceImpl类中的所有方法
execution(public void com..DeptServiceImpl.*(java.lang.Integer))使用 且、或||、非! 来组合比较复杂的切入点表达式
execution(* com.zhangjingqi.service.DeptService.list(..)) ||
execution(* com.zhangjingqi.service.DeptService.delete(..))2.5.1.3 切入点表达式建议 所有业务方法名在命名时尽量规范方便切入点快速匹配。 如查询方法find开头更新类方法update开头
描述切入点方法通常基于接口描述而不是直接描述实现类增强拓展性
在满足业务需要的前提下尽量缩小切入点的匹配范围
包名匹配进行不使用“…”,使用“*”匹配单个包
2.5.2 annotation 用于匹配标识有特定注解的方法
Before(annotation(com.zhangjingqi.anno.MyLog))简化下列表达式
execution(* com.zhangjingqi.service.DeptService.list(..)) ||
execution(* com.zhangjingqi.service.DeptService.delete(..))实现步骤
编写自定义注解
在业务类要做为连接点的方法上添加自定义注解
创建自定义注解类
Retention(RetentionPolicy.RUNTIME)//描述注解什么时候生效的运行时有效
Target(ElementType.METHOD)//当前注解可以作用在哪些地方
public interface MyLog {
}添加自定义注解
Override
MyLog //自定义注解表示当前方法属于目标方法
public ListDept list() { ... }Override
MyLog //自定义注解表示当前方法属于目标方法
public void delete(Integer id) { ... }切面类
Slf4j
Component
Aspect
public class MyAspect6 {
//针对list方法、delete方法进行前置通知和后置通知
//前置通知
Before(annotation(com.zhangjingqi.anno.MyLog))public void before(){log.info(MyAspect6 - before ...);//后置通知
After(annotation(com.zhangjingqi.anno.MyLog))public void after(){log.info(MyAspect6 - after ...);}
}2.5.3 切入点表达式总结 execution切入点表达式 根据我们所指定的方法的描述信息来匹配切入点方法这种方式也是最为常用的一种方式
如果我们要匹配的切入点方法的方法名不规则或者有一些比较特殊的需求通过
execution切入点表达式描述比较繁琐
annotation 切入点表达式 基于注解的方式来匹配切入点方法。这种方式虽然多一步操作我们需要自定义一个注解但
是相对来比较灵活。我们需要匹配哪个方法就在方法上加上对应的注解就可以了
2.6 连接点 被AOP控制的方法目标对象中所有的方法都可以被AOP控制在Spring AOP中又特制方法的执行 在Spring中用JoinPoint抽象了连接点用它可以获得方法执行时的相关信息如目标类名、方法类名、方法参数等 对于Around通知获取连接点信息只能用ProceedingJoinPoint 对于其他四种通知获取连接点信息只能使用JoinPoint它是ProceedingJoinPoint父类型 对于Around通知为什么获取连接点信息只能用ProceedingJoinPoint
在Spring AOP中Around通知是最为强大和灵活的通知类型它可以决定是否执行连接点以及如何处理连接点返回的结果。因此Around通知需要通过ProceedingJoinPoint参数来获取连接点信息。
ProceedingJoinPoint是JoinPoint的子类同时也是JoinPoint的扩展版本。JoinPoint表示连接点也就是被Advice修饰的方法。而ProceedingJoinPoint除了表示连接点外还具有一个proceed()方法该方法是执行目标方法的关键。在Before和After通知中JoinPoint足以满足需要因为它们只需要获取连接点信息即可不需要执行目标方法。但在Around中除了获取连接点信息还需要控制目标方法的执行因此需要用到ProceedingJoinPoint。
在Around通知中可以通过ProceedingJoinPoint的proceed()方法手动控制目标方法的执行。例如可以在proceed()方法前后进行一些预处理或后处理。同时ProceedingJoinPoint还提供了一些其他的工具方法例如getArgs()获取目标方法参数getSignature()获取目标方法签名等这些方法在编写Around通知时也非常有用。