当前位置: 首页 > wzjs >正文

wordpress获得当前文章的相关文章百度优化软件

wordpress获得当前文章的相关文章,百度优化软件,质量基础设施一站式服务工作站,网站备案信息查询申请目录 前言 一、AOP基础 1.入门程序 2. AOP核心概念 3. 底层原理 二、AOP进阶 1.通知类型 抽取切入点 2. 切入点表达式 2.1 execution 2.2 annoation 2.3 连接点详解 三、ThreadLocal 前言 AOP(面向切面编程),面向切面编程实际就…

目录

前言

一、AOP基础

1.入门程序

2. AOP核心概念

3. 底层原理

二、AOP进阶

1.通知类型

抽取切入点

2. 切入点表达式

2.1 execution

2.2 @annoation

2.3 连接点详解

三、ThreadLocal


前言

AOP(面向切面编程),面向切面编程实际就是面向特定方法编程

假如有一个项目,现在想知道这个项目中每个业务功能执行的市场,以便对执行时长比较长的功能进行优化。

首先想到的应该就是在每个方法执行前加一个System.currentTimeMillis()方法来记录时间,执行完后,再次记录时间。

但是如果业务功能很多,这样操作未免太过于冗余。利用AOP,只需要单独定义一段代码,就可以计算出所有业务功能所执行的时间。

所以,AOP的优势主要体现在以下四个方面:

  • 减少重复代码:不需要在业务方法中定义大量的重复性的代码,只需要将重复性的代码抽取到AOP程序中即可。

  • 代码无侵入:在基于AOP实现这些业务功能时,对原有的业务代码是没有任何侵入的,不需要修改任何的业务代码。

  • 提高开发效率

  • 维护方便

一、AOP基础

1.入门程序

@Component
@Aspect //当前类为切面类
@Slf4j
public class RecordTimeAspect {// Around注解中的属性表示会拦截com.itheima.service.impl这个包下DeptServiceImpl类中的所有方法@Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {// 记录方法执行开始时间long begin = System.currentTimeMillis();// 执行原始方法// 由于Around表示方法执行前后都会执行,所以被拦截的方法执行结束后,就会执行下面的代码。Object result = pjp.proceed();//记录方法执行结束时间long end = System.currentTimeMillis();//计算方法执行耗时log .info("方法执行耗时: {}毫秒",end-begin);return result;}
}

通过AOP入门程序完成了业务方法执行耗时的统计,那其实AOP的功能远不止于此,常见的应用场景如下:

  • 记录系统的操作日志

  • 权限控制

  • 事务管理:我们前面所讲解的Spring事务管理,底层其实也是通过AOP来实现的,只要添加@Transactional注解之后,AOP程序自动会在原始方法运行前先来开启事务,在原始方法运行完毕之后提交或回滚事务。

2. AOP核心概念

连接点:JoinPoint,可以被AOP控制的方法(不仅仅指当前被AOP控制的方法,也包含当前没有被AOP控制,但是可以被控制的方法)。

通知:Advice,指那些重复逻辑,也就是共性功能(最终体现为一个方法),例如计算每个业务执行的时间。

切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用,可以简单理解为被AOP控制的方法。

切面:Aspect,描述通知与切入点的对应关系(通知+切入点)。被@Aspect直接所修饰的类被称为切面类。

目标对象:Target,通知所应用的对象,目标对象指的就是通知所应用的对象,称之为目标对象。

3. 底层原理

Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。

如下图所示,切面类在执行原方法的时候,并不会直接去调用目标对象中的方法。而是创建一个代理对象DeptServiceProxy继承DeptService,在代理对象中执行目标对象的方法。在前端发起请求的时候,Spring通过IOC容器注入的也是这个代理对象。

就像把目标方法看作“核心业务”(制作手机),动态代理就是自动套在业务外的“流水线外壳”(代理商),在制作手机前自动贴标签(前置日志),制作后自动打包(后置日志),而手机工厂无需修改任何代码。如下图,在执行方法之前,计算时间;执行结束之后,计算时间,而Controller层执行代理商的代码即可。

二、AOP进阶

1.通知类型

AOP的通知类型有以下几种:

现有如下代码,通过代码来直观地感受不同通知类型的执行顺序:

@Slf4j
@Component
@Aspect
public class MyAspect1 {//前置通知@Before("execution(* com.itheima.service.*.*(..))")public void before(JoinPoint joinPoint){log.info("before ...");}//环绕通知@Around("execution(* com.itheima.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.itheima.service.*.*(..))")public void after(JoinPoint joinPoint){log.info("after ...");}//返回后通知(程序在正常执行的情况下,会执行的后置通知)@AfterReturning("execution(* com.itheima.service.*.*(..))")public void afterReturning(JoinPoint joinPoint){log.info("afterReturning ...");}//异常通知(程序在出现异常的情况下,执行的后置通知)@AfterThrowing("execution(* com.itheima.service.*.*(..))")public void afterThrowing(JoinPoint joinPoint){log.info("afterThrowing ...");}
}

随便执行一个业务程序,在程序没有发生异常的情况下,@AfterThrowing标识的通知方法不会执行。

可以发现Around先执行,然后Before执行,因为这两个都是在原始方法执行之前执行的。原始方法执行完毕之后,AfterReturning执行,接着是After,最后是Around。因为@Around是环绕通知,在原始方法执行前后都会执行。

如果程序出现异常,结果如下,@Around环绕通知中原始方法调用时有异常,通知中的环绕后的代码逻辑也不会在执行了 (因为原始方法调用已经出异常了):

抽取切入点

在上面的代码中,可以发现每一个通知都有相同的切入点表达式。假如此时切入点表达式需要变动,就需要将所有的切入点表达式一个一个的来改动,就变得非常繁琐了。因此我们需要将相同的切入点表达式抽取出来。

Spring提供了@PointCut注解,该注解的作用是将公共的切入点表达式抽取出来,需要用到时引用该切入点表达式即可。

将切入点表达式抽取出来,在通知中就直接引入切入点函数名即可。

@Slf4j
@Component
@Aspect
public class MyAspect1 {//切入点方法(公共的切入点表达式)@Pointcut("execution(* com.itheima.service.*.*(..))")private void pt(){}//前置通知(引用切入点)@Before("pt()")public void before(JoinPoint joinPoint){log.info("before ...");}//环绕通知@Around("pt()")public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {log.info("around before ...");//调用目标对象的原始方法执行Object result = proceedingJoinPoint.proceed();//原始方法在执行时:发生异常//后续代码不在执行log.info("around after ...");return result;}//后置通知@After("pt()")public void after(JoinPoint joinPoint){log.info("after ...");}//返回后通知(程序在正常执行的情况下,会执行的后置通知)@AfterReturning("pt()")public void afterReturning(JoinPoint joinPoint){log.info("afterReturning ...");}//异常通知(程序在出现异常的情况下,执行的后置通知)@AfterThrowing("pt()")public void afterThrowing(JoinPoint joinPoint){log.info("afterThrowing ...");}
}

如果外部其他切面类也想使用这个切入点,就需要把private改为public,而在引用的时候,具体的语法为:

@Slf4j
@Component
@Aspect
public class MyAspect2 {//引用MyAspect1切面类中的切入点表达式//必须是切入点的全类名@Before("com.itheima.aspect.MyAspect1.pt()")public void before(){log.info("MyAspect2 -> before ...");}
}

2. 切入点表达式

切入点表达式分为两种:execution(……):根据方法的签名来匹配;@annotation(……) :根据注解匹配。

2.1 execution

execution主要根据方法的返回值、包名、类名、方法名、方法参数等信息来匹配,语法为:

execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

其中带?号的部分是可以省略的。

主要理解下面这个表达式即可:

第一个*号指的是任意的访问修饰符,com.itheima.service.impl指的是包名,DeptServiceImpl指的是这个包中的一个类,类名是DeptServiceImpl,delete是这个类中的一个方法,后面的*号指的是delete方法中的所有参数。

execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))

如果想要com.itheima.service.impl包下的所有类,可以改为如下代码:

execution(* com.itheima.service.impl.DeptServiceImpl.*(..))

注意事项:

  • 根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。

execution(* com.itheima.service.DeptService.list(..)) || execution(* com.itheima.service.DeptService.delete(..))

2.2 @annoation

使用execution,当方法过多的时候,代码就会很繁杂。此时就可以借助另一种切入点表达式 @annotation 来描述这一类的切入点,从而来简化切入点表达式的书写。

实现步骤:

  1. 编写自定义注解

  2. 在业务类要做为连接点的方法上添加自定义注解

自定义注解LogOperation

// Target注解指定注解的作用目标 
// Retention指定注解的生命周期(在运行时有效)
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogOperation{
}

业务类DeptServiceImpl

@Slf4j
@Service
public class DeptServiceImpl implements DeptService {@Autowiredprivate DeptMapper deptMapper;@Override@LogOperation //自定义注解(表示:当前方法属于目标方法)public List<Dept> list() {List<Dept> deptList = deptMapper.list();//模拟异常//int num = 10/0;return deptList;}@Override@LogOperation //自定义注解(表示:当前方法属于目标方法)public void delete(Integer id) {//1. 删除部门deptMapper.delete(id);}@Override   // 没有自定义注解,表示当前方法不属于目标方法public void save(Dept dept) {dept.setCreateTime(LocalDateTime.now());dept.setUpdateTime(LocalDateTime.now());deptMapper.save(dept);}}

切面类

@Slf4j
@Component
@Aspect
public class MyAspect6 {//针对list方法、delete方法进行前置通知和后置通知//前置通知//括号里是自定义注解的全类名@Before("@annotation(com.itheima.anno.LogOperation)")public void before(){log.info("MyAspect6 -> before ...");}//后置通知@After("@annotation(com.itheima.anno.LogOperation)")public void after(){log.info("MyAspect6 -> after ...");}
}

上述就是@annoation的基本用法。

总结:

  • execution切入点表达式

    • 根据我们所指定的方法的描述信息来匹配切入点方法,这种方式也是最为常用的一种方式

    • 如果我们要匹配的切入点方法的方法名不规则,或者有一些比较特殊的需求,通过execution切入点表达式描述比较繁琐

  • annotation 切入点表达式

    • 基于注解的方式来匹配切入点方法。这种方式虽然多一步操作,我们需要自定义一个注解,但是相对来比较灵活。我们需要匹配哪个方法,就在方法上加上对应的注解就可以了

2.3 连接点详解

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型

  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

三、ThreadLocal

当在做业务代码的时候,存在以下问题:

  • 员工登录成功后,哪里存储的有当前登录员工的信息? 给客户端浏览器下发的jwt令牌中

  • 如何从JWT令牌中获取当前登录用户的信息呢? 获取请求头中传递的jwt令牌,并解析

  • TokenFilter 中已经解析了令牌的信息,如何传递给AOP程序、Controller、Service呢?ThreadLocal

ThreadLocal 是 Java 中用于实现线程局部变量的核心类,它通过为每个线程创建独立的变量副本,解决多线程环境下共享变量的并发问题。ThreadLocal 并不是一个Thread,而是Thread的局部变量,为每个线程提供一份单独的存储空间,具有线程隔离的效果,不同的线程之间不会相互干扰。

那么如何操作ThreadLocal呢?

首先定义一个ThreadLocal操作的工具类,用于操作当前登录员工ID。

package com.itheima.utils;public class CurrentHolder {private static final ThreadLocal<Integer> CURRENT_LOCAL = new ThreadLocal<>();public static void setCurrentId(Integer employeeId) {CURRENT_LOCAL.set(employeeId);}public static Integer getCurrentId() {return CURRENT_LOCAL.get();}public static void remove() {CURRENT_LOCAL.remove();}
}

然后我们在解析JWT令牌的时候,将登入员工的id存入ThreadLocal中,

        //5. 如果token不为空, 调用JWtUtils工具类的方法解析token, 如果解析失败, 响应401状态码try {Claims claims = JwtUtils.parseJWT(token);Integer empId = Integer.valueOf(claims.get("id").toString());// 在这里,将用户ID存入线程空间中CurrentHolder.setCurrentId(empId);log.info("token解析成功, 放行");} catch (Exception e) {log.info("token解析失败, 响应401状态码");response.setStatus(401);return;}

然后我们就可以在任何地方取出这次线程执行的员工ID

    // 示例方法,获取当前用户IDprivate int getCurrentUserId() {return CurrentHolder.getCurrentId();}

在同一个线程/同一个请求中,进行数据共享就可以使用 ThreadLocal

http://www.dtcms.com/wzjs/30225.html

相关文章:

  • 长春门户网站建设制作网站seo优化方案
  • 上海网页建站强化防疫指导
  • 专业做网站 郑州高端品牌网站建设
  • 我国的课程一般通过seo 深圳
  • 厦门杏林建设企业网站疫情防控最新数据
  • 宝和网站建设企业培训员工培训平台
  • 做网站的图片分类seo霸屏
  • 中国风网页设计欣赏seo搜索排名影响因素主要有
  • 博艺园 网站建设灰色产业推广引流渠道
  • 建公司网站流程免费发布信息的网站平台
  • 个人简历自我评价怎么写广州网站优化页面
  • 做微信平台图片网站腾讯新闻潍坊疫情
  • wordpress代码编辑插件trinseo公司
  • 金耀网站建设网站制作电商平台哪个最好最可靠
  • 广西网站建设建议企业推广文案
  • 平台如何做推广青岛网站seo
  • oss可以做网站根目录吗免费设计模板网站
  • 做英文的小说网站有哪些免费制作logo的网站
  • 常州哪家做网站好关键词seo价格
  • 日本做a的动画视频网站有哪些自己建网站怎么建
  • 政府单位建设微网站的好处百度网盘客服
  • 永康做企业网站的公司今天国际新闻最新消息
  • 佛山网站建设开发免费seo网站优化
  • 新手如何做英文网站赚美元网络营销策划书的结构
  • 阿坝北京网站建设怎么让客户主动找你
  • 建设网站用什么软件排版上海网站排名seo公司哪家好
  • 家用电脑桌面做网站农产品网络营销
  • wordpress大前端1.9长沙专业竞价优化首选
  • 音乐网站建设论文的目的和意义优化网络的软件下载
  • 青岛做网站哪里好明年2024年有疫情吗