spring揭秘11-aop05-aop应用经典场景及目标对象内部方法调用无法被拦截

news2024/11/14 6:15:27

文章目录

  • 【README】
  • 【1】基于aop实现全局异常处理
  • 【2】基于aop实现权限检查
  • 【3】基于aop实现缓存
  • 【4】aop无法拦截目标对象内部方法调用的问题
    • 【4.1】问题概述
    • 【4.2】解决方法

【README】

本文总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;

本文总结了aop的3个经典应用场景(当然,aop的应用范围不止这3点):

  • 基于aop的全局异常处理;
  • 基于aop的权限检查;
  • 基于aop的缓存;


【1】基于aop实现全局异常处理

1)背景:业务逻辑在运行过程中会抛出运行时异常,如数据库连接不可用,http超时,空指针等;不同业务逻辑都需要定义异常处理逻辑,即便异常处理逻辑相同,即异常处理逻辑散落在各个业务逻辑中(散弹式代码);

  • 解决方法: 定义异常处理逻辑切面,在一个地方统一拦截系统异常,实现异常处理代码内聚。也可以拦截后在通知或横切逻辑内部做相关处理,如发送kafka日志,发送即时运维消息等;

【AopAppFaultBarrierMain】异常处理(故障屏障)切面测试main

public class AopAppFaultBarrierMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext container =
                new ClassPathXmlApplicationContext("chapter11/beans11aopappfaultbarrier.xml");

        // 获取目标对象的代理对象
        Object proxy = container.getBean("target");
        try {
            ((BusiFileReader) proxy).readFileName("abcd");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }

        // 获取目标对象的代理对象
        System.out.println("==== 我是分割线 ====");
        Object userDaoProxy = container.getBean("userDAO");
        try {
            ((UserDAO) userDaoProxy).qryUserNameById("");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

【打印日志】

[@AfterThrowing]: faultBarrierHandleException
被切面拦截处理后的异常,原生异常消息=[文件不存在]
==== 我是分割线 ====
[@AfterThrowing]: faultBarrierHandleException
被切面拦截处理后的异常,原生异常消息=[用户id非法]

【beans11aopappfaultbarrier.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <bean class="com.tom.springnote.chapter11.faultbarrier.FaultBarrierAspect" />
    <bean id="target" class="com.tom.springnote.chapter11.target.BusiFileReader" />
    <bean id="userDAO" class="com.tom.springnote.chapter11.target.UserDAO" />
</beans>

【FaultBarrierAspect】异常处理切面 (环绕通知)

@Aspect
public class FaultBarrierAspect {

    @AfterThrowing(pointcut = "execution(* *(..))", throwing = "e")
    public void faultBarrierHandleException(Exception e) {
        System.out.println("[@AfterThrowing]: faultBarrierHandleException");
        throw new RuntimeException("被切面拦截处理后的异常,原生异常消息=[" + e.getMessage() + "]", e);
    }
}

【BusiFileReader】目标类1

public class BusiFileReader {

    public String readFileName(String path) {
        File file = new File(path);
        if (file.exists()) {
            return file.getName();
        } else {
            throw new RuntimeException("文件不存在");
        }
    }
}

【UserDAO】目标类2

public class UserDAO {

    public String qryUserNameById(String userId) {
        if (!StringUtils.hasText(userId)) {
            throw new RuntimeException("用户id非法");
        }
        System.out.println("UserDAO#qryUserNameById() 方法被调用");
        return "张三" + userId;
    }
}


【2】基于aop实现权限检查

1)背景:权限检查,包括但不限于安全检查,是一个web应用必须的功能; 如网关检查当前操作用户是否有操作权限,token解析是否成功等; 显然, 所有业务功能(对的就是所有业务功能)都需要权限检查,即权限检查是公共功能。 一般的,公共功能通过aop来实现,可以实现代码高内聚,且便于后续运维(如果某些方法不检查,仅需要修改pointcut表达式即可,而不用修改过多代码)

2)业务场景: 检查方法参数是否合法;不合法,抛出异常;

【AopAppSecurityCheckMain】

public class AopAppSecurityCheckMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext container =
                new ClassPathXmlApplicationContext("chapter11/beans11aopappchecksecurity.xml");

        // 获取目标对象的代理对象
        Object userDaoProxy = container.getBean("userDAO");
        try {
            ((UserDAO) userDaoProxy).qryUserNameById("");
        } catch (Exception e) {
            System.out.println("[main] " + e.getMessage());
        }
    }
}

【打印日志 】

[@Around] checkSecurity() 被调用
[main] 安全检查不通过: userId为空

【beans11aopappchecksecurity.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <bean class="com.tom.springnote.chapter11.security.SecurityCheckAspect" />
    <bean class="com.tom.springnote.chapter11.faultbarrier.FaultBarrierAspect" />

    <bean id="userDAO" class="com.tom.springnote.chapter11.target.UserDAO" />
</beans>

【SecurityCheckAspect】

@Aspect
public class SecurityCheckAspect {

    @Around("execution(* *(..)) && args(userId)")
    public Object checkSecurity(ProceedingJoinPoint joinPoint, String userId) throws Throwable {
        System.out.println("[@Around] checkSecurity() 被调用");
        if (!StringUtils.hasText(userId)) {
            throw new RuntimeException("安全检查不通过: userId为空"); // 方法调用阻断,校验不通过,不继续调用目标对象方法
        }
        return joinPoint.proceed(); // 调用目标对象方法 
    }
}

【注意】

  • 有个问题:代理类(即切面类SecurityCheckAspect)抛出的异常,不会被切面类 FaultBarrierAspect 异常处理类捕获; 即便 FaultBarrierAspect 是全局异常处理切面,因为其pointcut = "execution(* *(…)) ,匹配所有方法;
  • 但需要注意的是,这里的所有方法指的是目标对象方法,不包括代理对象方法; 所以当代理对象方法 checkSecurity (如环绕通知方法)抛出异常,是不会被异常处理切面 FaultBarrierAspect 匹配到的


【3】基于aop实现缓存

1)aop另一个主要应用场景: 为系统透明添加缓存,缓存在很大程度上可以提升系统性能,缓存是系统需求,而不是业务功能需求;

【AopCacheAspectMain】aop缓存main

public class AopCacheAspectMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext container =
                new ClassPathXmlApplicationContext("chapter11/beans11aopappcache.xml");

        // 获取代理对象
        Object proxy = container.getBean("userDAO");
        ((UserDAO) proxy).qryUserNameById("01");
        ((UserDAO) proxy).qryUserNameById("01");
        ((UserDAO) proxy).qryUserNameById("02");
        ((UserDAO) proxy).qryUserNameById("01");
    }
}

【打印日志】 ( 虽然qryUserNameById方法调用了4次,但通过日志可以看到,实际上目标对象只调用了2次,因为有缓存

[@Around] userInfoCache()被调用
UserDAO#qryUserNameById() 方法被调用  // 实际第1次调用 
[@Around] userInfoCache()被调用
[@Around] userInfoCache()被调用 
UserDAO#qryUserNameById() 方法被调用  // 实际第2次调用 
[@Around] userInfoCache()被调用 

【beans11aopappcache.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器 --> 
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <bean class="com.tom.springnote.chapter11.cache.CacheAspect" />
    <bean id="userDAO" class="com.tom.springnote.chapter11.target.UserDAO" />
</beans>

【CacheAspect】缓存切面

@Aspect
public class CacheAspect {

    private static final ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<>();

    @Around("args(usreId)")
    public Object userInfoCache(ProceedingJoinPoint joinPoint, String userId) throws Throwable {
        System.out.println("[@Around] userInfoCache()被调用");
        Object value = cache.get(userId);
        if (Objects.isNull(value)) {
            cache.putIfAbsent(userId, joinPoint.proceed());
        }
        return value;
    }
}


【4】aop无法拦截目标对象内部方法调用的问题

【4.1】问题概述

1)代理对象与目标对象:

  • 表面看,织入器把横切逻辑或通知织入到与pointcut匹配的目标对象(或目标方法);
  • 但实际上,横切逻辑或通知是织入到代理对象;代理对象与目标对象有着相同的方法(JDK动态代理实现aop,则代理对象与目标对象实现相同接口;CGLIB动态代理,则代理对象继承目标类 ); 调用代理对象的方法,代理对象根据通知类型执行横切逻辑(前置或后置),接着执行目标对象方法;即通过代理对象调用方法才会被拦截,而通过目标对象本身调用方法不会被拦截

2)通过代理对象调用方法才会被拦截,而通过目标对象本身调用方法不会被拦截;

  • 如 通过ProxyObject 调用方法(要被pointcut表达式匹配)会被拦截;
  • 而通过 targetObject.method()1 调用 target自身方法method2(),不会被拦截;(即便method2被pointcut表达式匹配)

在这里插入图片描述

3)aop不拦截目标对象内部方法之间调用

【NestedCallMain】目标对象内部方法调用测试main

public class NestedCallMain {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext container = new ClassPathXmlApplicationContext("chapter12/beans12nestedcall.xml");

        // 获取代理对象
        Object proxy = container.getBean("nestedCallTarget");
        ((NestedCallTarget) proxy).method1();
    }
}

【打印日志】

stopWatch.start()
NestedCallTarget#method1() 方法被调用
NestedCallTarget#method2() 方法被调用 // 显然,目标对象nestedCallTarget内部方法method1() 调用内部方法method2(),没有触发aop拦截
stopWatch.stop() 
方法执行耗时1.028E-4

显然,目标对象nestedCallTarget内部方法method1() 调用内部方法method2(),没有触发aop拦截

【beans12nestedcall.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:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 使用 <aop:aspectj-autoproxy> 元素,用于替换 AnnotationAwareAspectJAutoProxyCreator 自动代理织入器 --> 
    <aop:aspectj-autoproxy proxy-target-class="true" />

    <bean class="com.tom.springnote.chapter12.target.NestedCallAspect" />
    <bean id="nestedCallTarget" class="com.tom.springnote.chapter12.target.NestedCallTarget" />
</beans>

【NestedCallAspect】切面

@Aspect
public class NestedCallAspect {

    @Around("execution(* method1(..)) || execution(* method2(..))")
    public Object timeCost(ProceedingJoinPoint joinPoint) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        try {
            System.out.println("stopWatch.start()");
            stopWatch.start();
            return joinPoint.proceed();
        } finally {
            System.out.println("stopWatch.stop()");
            stopWatch.stop();
            System.out.printf("方法执行耗时%s\n", stopWatch.getTotalTime(TimeUnit.SECONDS));
        }
    }
}

【NestedCallTarget】目标对象 ( 内部方法method1() 调用内部方法method2() )

public class NestedCallTarget {

    public void method1() {
        System.out.println("NestedCallTarget#method1() 方法被调用");
        method2();
    }

    public void method2() {
        System.out.println("NestedCallTarget#method2() 方法被调用");
    }
}

【4.2】解决方法

1)目标对象内部方法method1() 调用 method2()时, 不调用目标对象的method2()方法,而调用代理对象的 method2()方法

class Target {
  method1() {
      proxy.method2();
  }    
} 

2)目标对象如何获取代理对象proxy ; 通过调用 AopContext.currentProxy() 来实现, 且同时设置织入器的 exposeProxy属性为true ;

【NestedCallTargetWithHoldProxyMain】持有代理对象的目标对象内部方法调用main

public class NestedCallTargetWithHoldProxyMain {
    public static void main(String[] args) {
        NestedCallTargetWithHoldProxy target = new NestedCallTargetWithHoldProxy();
        AspectJProxyFactory weaver = new AspectJProxyFactory(target);
        weaver.setExposeProxy(true); // 设置为true ,AopContext.currentProxy() 才生效 
        weaver.addAspect(NestedCallAspect.class); 

        // 获取代理对象
        Object proxy = weaver.getProxy();
        ((NestedCallTargetWithHoldProxy) proxy).method1();
    }
}

【打印日志】

stopWatch.start()
NestedCallTarget#method1() 方法被调用
stopWatch.start()
NestedCallTarget#method2() 方法被调用
stopWatch.stop()
方法执行耗时6.5E-5
stopWatch.stop()
方法执行耗时0.0029408

【NestedCallTargetWithHoldProxy】持有代理对象的内嵌调用目标类

public class NestedCallTargetWithHoldProxy {

    public void method1() {
        System.out.println("NestedCallTarget#method1() 方法被调用");
        NestedCallTargetWithHoldProxy proxy = (NestedCallTargetWithHoldProxy) AopContext.currentProxy();
        proxy.method2();
    }

    public void method2() {
        System.out.println("NestedCallTarget#method2() 方法被调用");
    }
}

【补充】当然,我们可以注入 NestedCallTargetWithHoldProxy代理对象(依赖)到 NestedCallTargetWithHoldProxy目标对象中;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2071005.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TFTP error: ‘Permission denied‘ (0)

项目场景&#xff1a; 环境&#xff1a;ubuntu 5.4.150 开发板&#xff1a;s3c2440 在u-boot中&#xff0c;通过tftp传输uImage文件失败。 问题描述 SMDK2410 # tftp 0x30008000 uImage dm9000 i/o: 0x20000000, id: 0x90000a46 DM9000: running in 16 bit mode MAC: 00:0…

【变化检测】基于UNet建筑物变化检测

主要内容如下&#xff1a; 1、LEVIR-CD数据集介绍及下载 2、运行环境安装 3、基于likyoo变化检测代码模型训练与预测 4、Onnx运行及可视化 运行环境&#xff1a;Python3.8&#xff0c;torch1.12.0cu113 likyoo变化检测源码&#xff1a;https://github.com/likyoo/change_dete…

数据仓库中的表设计模式:全量表、增量表与拉链表

在现代数据仓库中&#xff0c;管理和分析海量数据需要高效且灵活的数据存储策略。全量表、增量表和拉链表是三种常见的数据存储模式&#xff0c;各自针对不同的数据管理需求提供了解决方案。全量表通过保存完整的数据快照确保数据的一致性&#xff0c;增量表则通过记录数据的变…

如何在 Ubuntu 系统中安装PyCharm集成开发环境?

在上一篇文章中&#xff0c;我们探讨了Jupyter notebook&#xff0c;今天再来看看另一款常用的Python 工具&#xff0c;Pycharm。 PyCharm也是我们日常开发和学习常用的Python 集成开发环境 (IDE)&#xff0c;由 JetBrains 开发。 PyCharm 带有一整套可以帮助用户在使用Pytho…

大数据-91 Spark 集群 RDD 编程-高阶 RDD广播变量 RDD累加器 Spark程序优化

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

【代码随想录训练营第42期 Day39打卡 - 打家劫舍问题 - LeetCode 198.打家劫舍 213.打家劫舍II 337.打家劫舍III

目录 一、做题心得 二、题目与题解 题目一&#xff1a;198.打家劫舍 题目链接 题解&#xff1a;动态规划 题目二&#xff1a;213.打家劫舍II 题目链接 题解&#xff1a;动态规划 题目三&#xff1a;337.打家劫舍III 题目链接 题解&#xff1a;动态规划 三、小结 一、…

卸载nomachine

网上的方法:提示找不到命令 我的方法: step1. 终端输入 sudo find / -name nxserver 2>/dev/null确认 NoMachine 的实际安装路径。你可以使用 find 命令在系统中查找 nxserver 脚本的位置。 找到路径后,你可以使用该路径来卸载 NoMachine。 如下图,紫色框中是我的路径…

【ACM出版】第三届公共管理、数字经济与互联网技术国际学术会议(ICPDI 2024,9月06-08)

第三届公共管理、数字经济与互联网技术国际学术会议&#xff08;ICPDI 2024&#xff09;定于2024年9月06-08日在中国-济南举行。 会议主要围绕公共管理、数字经济&#xff0c;互联网技术等研究领域展开讨论。会议旨在为从事公共管理、经济、大数据、互联网研究的专家学者提供一…

解决LabVIEW配置文件中文乱码问题

LabVIEW配置文件中的中文字符在程序调用时出现乱码&#xff0c;通常是由于字符编码不匹配引起的。LabVIEW默认使用ANSI编码格式&#xff0c;而配置文件可能使用了不同的编码格式&#xff08;如UTF-8&#xff09;&#xff0c;导致中文字符在读取时无法正确解析。 解决方法 统一编…

导数的基本法则与常用导数公式的推导

目录 n 次幂函数导数公式的推导导数和的运算法则的证明正弦、余弦函数导数公式的推导代数证明两个重要极限&#xff08;引理&#xff09;及证明具体推导 几何直观 导数积的运算法则的证明导数商的法则的证明链式法则的证明有理幂函数求导法则的证明反函数求导法则的证明反正切函…

SSH 远程登录报错:kex_exchange_identification: Connection closed.....

一 问题起因 在公司,使用ssh登录远程服务器。有一天,mac终端提示:`kex_exchange_identification: Connection closed by remote host Connection closed by UNKNOWN port 65535`。 不知道为啥会出现这样的情形,最近这段时间登录都是正常的,不知道哪里抽风了,就提示这个。…

C++初学(15)

前面学习了循环的工作原理&#xff0c;接下来来看看循环完成的一项最常见的任务&#xff1a;逐字符地读取来自文本或键盘的文本。 15.1、使用cin进行输入 如果需要程序使用循环来读取来自键盘的文本输入&#xff0c;则必须有办法直到何时停止读取。一种方式是选择某个特殊字符…

发布分班查询,老师都在用哪个小程序?

新学期伊始&#xff0c;校园里又迎来了一批朝气蓬勃的新生。老师们的日程表上&#xff0c;除了日常的教学准备&#xff0c;还多了一项重要的任务——分班。这项工作不仅需要老师们精心策划&#xff0c;以确保每个班级的平衡&#xff0c;还要在分班完成后&#xff0c;及时将结果…

系统架构不是设计出来的

今天给大家分享一个 X/ Twitter 早期系统架构演变的故事&#xff0c;内容来自《数据密集型应用系统设计》这本书&#xff0c;具体数据来自 X/ Twitter 在 2012 年 11 月发布的分享。 推特的两个主要业务是&#xff1a; 发布推文&#xff08;Tweets&#xff09;。用户可以向其粉…

零基础入门~汇编语言(第四版王爽)~第3章寄存器(内存访问)

文章目录 前言3.1 内存中字的存储3.2 DS 和[address]3.3 字的传送3.4 mov、add、sub指令3.5 数据段检测点3.13.6 栈3.7 CPU提供的栈机制3.8 栈顶超界的问题3.9 push、pop指令3.10 栈 段检测点3.2实验2 用机器指令和汇编指令编程 前言 第2章中&#xff0c;我们主要从CPU 如何执…

2月公开赛Web-ssrfme

考点&#xff1a; redis未授权访问 源码&#xff1a; <?php highlight_file(__file__); function curl($url){ $ch curl_init();curl_setopt($ch, CURLOPT_URL, $url);curl_setopt($ch, CURLOPT_HEADER, 0);echo curl_exec($ch);curl_close($ch); }if(isset($_GET[url…

回归预测 | Matlab实现WOA-ESN鲸鱼算法优化回声状态网络多输入单输出回归预测

回归预测 | Matlab实现WOA-ESN鲸鱼算法优化回声状态网络多输入单输出回归预测 目录 回归预测 | Matlab实现WOA-ESN鲸鱼算法优化回声状态网络多输入单输出回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现WOA-ESN鲸鱼算法优化回声状态网络多输入单输出…

vue3 生命周期钩子

在 Vue 3 中&#xff0c;可以在组件不同阶段的生命周期执行特定逻辑。 生命周期整体分为四个阶段&#xff1a;创建、挂载、更新、卸载。 创建阶段 组合式APIsetup() 这是组合式 API 的入口点&#xff0c;在组件实例创建之前被调用。在此阶段&#xff0c;可以初始化响应式数据…

一键批量查询邮政快递,物流状态尽在掌握

邮政快递批量查询&#xff0c;轻松掌握物流动态 在电商行业蓬勃发展的今天&#xff0c;邮政快递作为连接商家与消费者的桥梁&#xff0c;其物流信息的及时性和准确性对于提升客户体验至关重要。然而&#xff0c;面对海量的快递单号&#xff0c;如何高效地进行批量查询&#xf…

【最长上升子序列】

题目 代码 #include <bits/stdc.h> using namespace std; const int N 1010; int a[N], f[N]; int main() {int n;cin >> n;for(int i 1; i < n; i) cin >> a[i];int res 0;for(int i 1; i < n; i){f[i] 1;for(int j 1; j < i; j){if(a[j] &…