Spring中bean的初始化和销毁几种实现方式详解

关联博文:Spring中Bean的作用域与生命周期

Bean的生命周期 : 创建bean对象 – 属性赋值 – 初始化方法调用前的操作 – 初始化方法 – 初始化方法调用后的操作 – …-- 销毁前操作 – 销毁方法的调用。

先放一张图吧。
在这里插入图片描述

【1】init-method和destroy-method

bean 标签有两个重要的属性(init-method 和 destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct 和@PreDestroy)。

自定义初始化方法和销毁方法两种方式:xml配置和注解。

① xml配置

<bean id="person" class="com.core.Person" scope="singleton"  init-method="init"  destroy-method="cleanUp"autowire="byName" lazy-init="true" >  
</bean> 

② 注解配置

@Scope("singleton")@Lazy@Bean(name="person",initMethod="init",destroyMethod="cleanUp",autowire=Autowire.BY_NAME)public Person person01(){return new Person("lisi", 20);}

详见: https://blog.csdn.net/j080624/article/details/79779571。

单实例bean在容器创建完成前会进行创建并初始化,在容器销毁的时候进行销毁。多实例bean(scope=prototype)在第一次获取该bean实例时才会创建并初始化,且容器不负责该bean的销毁。


【2】InitializingBean 和DisposableBean

InitializingBean 接口

public interface InitializingBean {void afterPropertiesSet() throws Exception;
}
  • 在BeanFactory设置完bean属性后执行

  • 需要被bean实现的接口,一旦bean的属性被BeanFactory设置后需要做出反应: 如,执行自定义初始化,或者仅仅是检查是否设置了所有强制属性。

  • 实现InitializingBean 的可替代方式为给bean指定一个自定义的init-method,例如在一个xml bean 定义中。

  • 在bean的属性设置之后进行操作,不返回任何值但是允许抛出异常。


DisposableBean接口

public interface DisposableBean {void destroy() throws Exception;
}
  • 被bean实现的接口,在销毁时释放资源,在Bean销毁的时候调用该方法。
  • 如果销毁一个缓存的单例,一个BeanFactory 可能会调用这个销毁方法。
  • 在容器关闭时,应用上下文会销毁所有的单例bean。
  • 一种替代实现DisposableBean 接口的方案为指定一个自定义的destroy-method方法,例如在一个xml bean定义中。

自定义bean实现上述两个接口

@Component
public class Cat implements InitializingBean,DisposableBean {public Cat(){System.out.println("cat constructor...");}@Overridepublic void destroy() throws Exception {// TODO Auto-generated method stubSystem.out.println("cat...destroy...");}@Overridepublic void afterPropertiesSet() throws Exception {// TODO Auto-generated method stubSystem.out.println("cat...afterPropertiesSet...");}}

测试结果

cat constructor...
cat...afterPropertiesSet...
容器创建完成...
四月 08, 2018 6:35:46 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext 
doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@11028347: 
startup date [Sun Apr 08 18:35:46 CST 2018]; root of context hierarchy
cat...destroy...

【3】@PostConstruct和@PreDestroy

使用JSR250规范定义的两个注解:

  • @PostConstructPostConstruct注解作用在方法上,在依赖注入完成后进行一些初始化操作。这个方法在类被放入service之前被调用,所有支持依赖项注入的类都必须支持此注解。
  • @PreDestroy:在容器销毁bean之前通知我们进行清理工作

自定义类使用上述两个注解

@Component
public class Dog implements ApplicationContextAware {//@Autowiredprivate ApplicationContext applicationContext;public Dog(){System.out.println("dog constructor...");}//对象创建并赋值之后调用@PostConstructpublic void init(){System.out.println("Dog....@PostConstruct...");}//容器移除对象之前@PreDestroypublic void detory(){System.out.println("Dog....@PreDestroy...");}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {// TODO Auto-generated method stubthis.applicationContext = applicationContext;}}

测试结果如下:

dog constructor...
Dog....@PostConstruct...
容器创建完成...
四月 08, 2018 6:42:11 下午 org.springframework.context.annotation.AnnotationConfigApplicationContext 
doClose
信息: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@11028347: 
startup date [Sun Apr 08 18:42:10 CST 2018]; root of context hierarchy
Dog....@PreDestroy...

【4】BeanPostProcessor-Bean后置处理器

① 什么是bean后置处理器

Bean 后置处理器允许在调用初始化方法前后对 Bean 进行额外的处理。Bean 后置处理器对 IOC 容器里的所有 Bean 实例逐一处理, 而非单一实例。其典型应用是: 检查 Bean 属性的正确性或根据特定的标准更改 Bean 的属性。

在bean初始化前后进行一些处理工作

  • postProcessBeforeInitialization:在初始化之前工作
  • postProcessAfterInitialization:在初始化之后工作

其接口源码如下:

public interface BeanPostProcessor {Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}

自定义MyBeanPostProcessor实现该接口

/*** 后置处理器:初始化前后进行处理工作* 将后置处理器加入到容器中*/
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {System.out.println("BeanPostProcessor.postProcessBeforeInitialization..."+beanName+"=>"+bean);return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {System.out.println("BeanPostProcessor.postProcessAfterInitialization..."+beanName+"=>"+bean);return bean;}}

② BeanPostProcessor原理

AbstractAutowireCapableBeanFactory中关于bean和BeanPostProcessor执行次序由上到下

//给bean进行属性赋值
populateBean(beanName, mbd, instanceWrapper);//然后调用initializeBean方法
Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd)
{applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);//执行自定义初始化invokeInitMethods(beanName, wrappedBean, mbd);applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
}

AbstractAutowireCapableBeanFactory.initializeBean源码如下:

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {if (System.getSecurityManager() != null) {AccessController.doPrivileged(new PrivilegedAction<Object>() {@Overridepublic Object run() {invokeAwareMethods(beanName, bean);return null;}}, getAccessControlContext());}else {//调用意识/通知方法invokeAwareMethods(beanName, bean);}Object wrappedBean = bean;if (mbd == null || !mbd.isSynthetic()) {//调用bean后置处理器的前置方法wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);}//调用初始化方法try {invokeInitMethods(beanName, wrappedBean, mbd);}catch (Throwable ex) {throw new BeanCreationException((mbd != null ? mbd.getResourceDescription() : null),beanName, "Invocation of init method failed", ex);}if (mbd == null || !mbd.isSynthetic()) {//	//调用bean后置处理器的后置方法wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);}return wrappedBean;
}

AbstractAutowireCapableBeanFactory.invokeInitMethods方法源码如下:

protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd)throws Throwable {boolean isInitializingBean = (bean instanceof InitializingBean);if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {if (logger.isDebugEnabled()) {logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");}//调用InitializingBean.afterPropertiesSetif (System.getSecurityManager() != null) {try {AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {@Overridepublic Object run() throws Exception {((InitializingBean) bean).afterPropertiesSet();return null;}}, getAccessControlContext());}catch (PrivilegedActionException pae) {throw pae.getException();}}else {((InitializingBean) bean).afterPropertiesSet();}}
//调用自定义初始化方法if (mbd != null) {String initMethodName = mbd.getInitMethodName();if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&!mbd.isExternallyManagedInitMethod(initMethodName)) {invokeCustomInitMethod(beanName, bean, mbd);}}
}

【5】Spring底层使用的BeanPostProcessor

Spring框架底层存在大量BeanPostProcessor,如下图:

这里写图片描述

示例一 :BeanValidationPostProcessor是处理bean校验

其Javadoc如下:

//为spring管理的bean,校验JSR-303注解约束。如果校验不通过则在调用bean的init method前抛出初始化异常public class BeanValidationPostProcessor implements BeanPostProcessor, InitializingBean {private Validator validator;private boolean afterInitialization = false;//...
}

示例二:ApplicationContextAwareProcessor帮助获取容器上下文

其Javadoc如下:

// 传递ApplicationContext 
class ApplicationContextAwareProcessor implements BeanPostProcessor {private final ConfigurableApplicationContext applicationContext;private final StringValueResolver embeddedValueResolver;//...
}

如【3】中的dog类为例,其debug示意图如下:

这里写图片描述


【6】初始化和销毁方式测试

① 如果一个bean 综合应用下面六种种方式,执行顺序会怎样呢

Bean类如下:

public class Person implements InitializingBean,DisposableBean {private String name;private  Integer age=1;public Person(String name, Integer age) {this.name = name;this.age = age;System.out.println("Person(String name, Integer age) constructor"+this);}public Person() {super();System.out.println("Person() constructor"+age);}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}// 自定义init方法public void init(){System.out.println("-----Person.init()-----"+this);}// 自定义销毁方法public void cleanUp(){System.out.println("-----Person.cleanUp()-----"+this);}// InitializingBean的实现方法@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("-----InitializingBean.afterPropertiesSet()-----"+this);}//DisposableBean 的实现方法@Overridepublic void destroy() throws Exception {System.out.println("-----DisposableBean.destroy()-----"+this);}//对象创建并赋值之后调用@PostConstructpublic void init2(){System.out.println("-----@PostConstruct-----"+this);}//容器移除对象之前@PreDestroypublic void destory2(){System.out.println("-----@PreDestroy-----"+this);}
}

配置类如下:

@Scope("singleton")
@Bean(name="person",initMethod="init",destroyMethod="cleanUp",autowire= Autowire.BY_NAME)
public Person person01(){return new Person("lisi", 20);
}

测试结果如下:

// 创建并初始化
Person(String name, Integer age) constructorPerson{name='lisi', age=20}
-----@PostConstruct-----Person{name='lisi', age=20}
-----InitializingBean.afterPropertiesSet()-----Person{name='lisi', age=20}
-----Person.init()-----Person{name='lisi', age=20}//容器将要销毁
-----@PreDestroy-----Person{name='lisi', age=20}
-----DisposableBean.destroy()-----Person{name='lisi', age=20}
-----Person.cleanUp()-----Person{name='lisi', age=20}

即,最先使用bean的构造器为bean属性赋值,接着JSR250规范定义的两个注解,其次是InitializingBean和DisposableBean接口,最后才是我们自定义的初始化方法和销毁方法。注意,这里还没有引入BeanPostProcessor。


② 在①的基础上添加BeanPostProcessor

实例化bean并进行初始化

//调用构造方法
Person(String name, Integer age) constructorPerson{name='lisi', age=20}
//bean初始化前
BeanPostProcessor.postProcessBeforeInitialization...person=>Person{name='lisi', age=20}
//初始化操作
-----@PostConstruct-----Person{name='lisi', age=20}
-----InitializingBean.afterPropertiesSet()-----Person{name='lisi', age=20}
-----Person.init()-----Person{name='lisi', age=20}
//bean初始化后操作
BeanPostProcessor.postProcessAfterInitialization...person=>Person{name='lisi', age=20}

过程如下:类构造函数-->BeanPostProcessor-->@PostConstruct-->InitializingBean-->init()-->BeanPostProcessor


销毁bean

-----@PreDestroy-----Person{name='lisi', age=20}
-----DisposableBean.destroy()-----Person{name='lisi', age=20}
-----Person.cleanUp()-----Person{name='lisi', age=20}

完整图示如下(同颜色的说明相对应):
在这里插入图片描述
在调用bean的构造函数时会根据入参为bean属性赋值,如果入参为空则会给bean属性赋予默认值,引用类型为null,基本类型比如int为0。


【7】 @Autowired注解的值何时放入?

public Person(String name, Integer age) {this.name = name;this.age = age;System.out.println("Person(String name, Integer age) constructor"+this);System.out.println(" @Autowired adviceService:"+adviceService);
}@Override
public void setBeanName(String name) {System.out.println(" @Autowired adviceService:"+adviceService);System.out.println("BeanNameAware--setBeanName...."+name);
}

测试结果:

// 如下是构造函数中打印
Person(String name, Integer age) constructorPerson{name='lisi', age=20}
@Autowired adviceService:null
// 如下 是setBeanName方法中打印
@Autowired adviceService:com.recommend.service.impl.SysAdviceServiceImpl@5a739e57
BeanNameAware--setBeanName....person

那么也就是说,在类的构造函数调用、setXXX属性后,开始执行@Autowired依赖注入。这个过程发生在setBeanName时机之前。

那么具体什么时候哪个类完成的 @Autowired注解注入依赖呢?

在类被实例化后由BeanPostProcessor完成的,哪个BeanPostProcessor?

具体是由AutowiredAnnotationBeanPostProcessor 完成的:
在这里插入图片描述

【8】如果bean实现了XXXAware接口呢?

这里比如Person实现了BeanNameAware、BeanFactoryAware以及ApplicationContextAware接口。

如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的setBeanName(String) 方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值。

如果这个 Bean 已经实现了 BeanFactoryAware 接口,会调用它实现的 setBeanFactory,
setBeanFactory(BeanFactory)传递的是 Spring 工厂自身(可以用这个方式来获取其它 Bean,只需在 Spring 配置文件中配置一个普通的 Bean 就可以)。

如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用
setApplicationContext(ApplicationContext)方法,传入 Spring 上下文(同样这个方式也可以实现步骤 setBeanFactory 的内容,但比 setBeanFactory 更好,因为ApplicationContext 是 BeanFactory 的子接口,有更多的实现方法)

public class Person implements InitializingBean, DisposableBean, BeanNameAware, BeanFactoryAware, ApplicationContextAware {private String name;private  Integer age=1;public Person(String name, Integer age) {this.name = name;this.age = age;System.out.println("Person(String name, Integer age) constructor"+this);}public Person() {super();System.out.println("Person() constructor"+age);}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;System.out.println("setName方法..."+name);}@Overridepublic String toString() {return "Person{" +"name='" + name + '\'' +", age=" + age +'}';}// 自定义init方法public void init(){System.out.println("-----Person.init()-----"+this);}// 自定义销毁方法public void cleanUp(){System.out.println("-----Person.cleanUp()-----"+this);}// InitializingBean的实现方法@Overridepublic void afterPropertiesSet() throws Exception {System.out.println("-----InitializingBean.afterPropertiesSet()-----"+this);}//DisposableBean 的实现方法@Overridepublic void destroy() throws Exception {System.out.println("-----DisposableBean.destroy()-----"+this);}//对象创建并赋值之后调用@PostConstructpublic void init2(){System.out.println("-----@PostConstruct-----"+this);}//容器移除对象之前@PreDestroypublic void destory2(){System.out.println("-----@PreDestroy-----"+this);}@Overridepublic void setBeanName(String name) {System.out.println("BeanNameAware--setBeanName...."+name);}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("BeanFactoryAware.setBeanFactory...."+beanFactory);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {System.out.println("ApplicationContextAware.setApplicationContext...."+applicationContext);}
}

同样在配置类中配置person bean如下

@Configuration
public class MyConfiguration {@Scope("singleton")@Bean(name="person",initMethod="init",destroyMethod="cleanUp",autowire= Autowire.BY_NAME)public Person person01(){return new Person("lisi", 20);}
}

在这里插入图片描述

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

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

相关文章

js-removeChild()

下面给大家介绍Javascript removeChild()删除节点的方法&#xff0c;具体详情如下所示&#xff1a; 在Javascript中&#xff0c;只提供了一种删除节点的方法&#xff1a;removeChild()。 removeChild() 方法用来删除父节点的一个子节点。 语法&#xff1a; parent.removeCh…

删除节点removeChild()

removeChild() 方法从子节点列表中删除某个节点。如删除成功&#xff0c;此方法可返回被删除的节点&#xff0c;如失败&#xff0c;则返回 NULL。 语法: nodeObject.removeChild(node) 参数: node &#xff1a;必需&#xff0c;指定需要删除的节点。 我们来看看下面代码&a…

Js removeChild、addChild

1. <html><head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"><title>添加删除记录练习升级</title><link rel"stylesheet" type"text/css" href"ex_2_style/css.css&qu…

【注意】js 里面 removeChild 使用的坑

这有坑啊&#xff0c;使用removeChild 循环删除 子元素们的时候 是【0】而不是【i】 这是因为 你每次删完一个 子元素们的 下标就会发生改变 比如 allSpan【0】 allSpan【1】 removeChild(allSpan【0】) 以后 &#xff0c;allSpan【1】 就不再是 allSpan【1】了&#xff0c;…

关于removeChild() 方法

removeChild() 方法 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 开发工具与关键技术&#xff1a; JS 撰写时间&#xff1a;2021/7/8 注意红色箭头指向内容&#xff0c;本文将分为6张图片&#xff0c;并按从上到下顺序操作 1.我们先简…

4.C++多线程-- unique_lock(类模板)

1.unique_lock 1. unique_lock<mutex> myUniLock(myMutex); 完全可以取代lock_guard 2. unique_lock 也可以使用----std::adopt_lock 3.使用adopt_lock&#xff0c;之前要先使用lock. 4.std::chrono::milliseconds my_sleepTime(20000)//20000毫秒 std::this_thread:…

打开浏览器的同时会在主页外同时打开芒果TV,抖音等网站

问题&#xff1a;每次打开谷歌浏览器的同时会同时打开芒果TV&#xff0c;抖音等网站。导致每次都要进行网页关闭&#xff0c;非常麻烦。 解决&#xff1a;在扩展程序中有一个叫做“省钱购”的程序&#xff0c;只需要将其移走就可以了。 扩展程序的位置&#xff1a;自定义及控…

最新超漂亮UI仿芒果TV听书网站模板双端+苹果CMS内核

正文: 苹果cms超漂亮UI高仿芒果TV听书网站模板带手机端。 手机版修改logo&#xff0c;ting_wap/images/logo.png 电脑版修改logo&#xff0c;ting_pc/img/logo.png 编辑推荐后台推荐5颗星&#xff0c;新势力/热播榜单后台推荐9颗星。 程序: wwyfeu.lanzoum.com/iQ0F00gbt…

芒果播放器介绍

芒果播放器 芒果全能播放器能播放所有格式的视频、音频文件、以及主流图片文件。短小精悍、简洁明了、绿色&#xff0c;无广告&#xff0c;其FLASH播放比暴风音影更真实。原本为教育教学服务&#xff0c;广大师生反应较好&#xff0c;现全面推广。 【1.1版下载地址】 http://…

非常简单下载芒果tv视频的方法(无需安装任何软件)

1、打开芒果tv网站&#xff0c;找到需要下载的视频地址比如&#xff1a;https://www.mgtv.com/b/328606/5533377.html?fpase 2、打开网页&#xff1a;https://www.parsevideo.com/mgtv/ 3、输入需要分析的视频地址&#xff1a; 4、分析结果如下&#xff0c;并点击第二行的下…

最新芒果TV视频下载方法-马赛克视频助手

芒果TV是一款资源丰富的互联网视频平台。它除了可以看视频外&#xff0c;还可以将这些视频下载下来。但官方是不支持视频下载的&#xff0c;那么芒果TV该怎么下载视频么&#xff1f;接下来就让我们一起去看看吧。 今天小编就教大家如何把上面喜欢的视频下载下来 1.这里我们需…

芒果TV 2021 互联网人才招聘

长沙&#xff0c;关键词是什么&#xff1f; 小龙虾、臭豆腐 马栏山 中国最具幸福感城市 芒果TV&#xff0c;关键词是什么&#xff1f; 天生青春&#xff0c;NO.1 中国互联网百强 世界媒体五百强 理想&#xff0c;非得在北上广实现么&#xff1f; 其实&#xff0c;追梦的路上不一…

Android TV 开发之 TV视频播放器

Android TV视频播放器VideoView 不想往下看可以直接在GitHub上面克隆到自己的项目中 GitHub地址 闲谈 最近公司又给了一个新任务&#xff0c;说要做电视机顶盒开发&#xff0c;这个机顶盒开发之前也没有接触过啊&#xff0c;没经验&#xff0c;这使我走了很多坑&#xff0c;写…

芒果TV会员,月卡最低9.9元,年卡最低128元!

全国首部湘商题材电视剧《一代洪商》&#xff0c;将于3月27日在央视八套&#xff08;电视剧频道&#xff09;播出&#xff0c;芒果TV将线上播出。该剧由王少华编剧&#xff0c;路奇担纲导演&#xff0c;孟凡耀担任总制片人&#xff0c;张丰毅、李立群、张睿、张含韵等人主演&am…

芒果tv官网服务器维护,芒果tv看不了【解决方案】

win7系统有很多人都喜欢使用,我们操作的过程中常常会碰到win7系统芒果tv看不了的问题。如果遇到win7系统芒果tv看不了的问题该怎么办呢&#xff1f;很多电脑水平薄弱的网友不知道win7系统芒果tv看不了究竟该怎么解决&#xff1f;其实不难根据下面的操作步骤就可以解决问题1.DNS…

jquery mini下载_【芒果tv湖南卫视直播】-芒果TV播放器下载v6.3.4 官方正式版

芒果tv湖南卫视直播电脑版最近也改版更新了&#xff0c;在西西在认识中这些媒体一般是不会重视客户端这块的&#xff0c;新版比旧版大了不少&#xff0c;因此软件的版面也是大气了很多&#xff0c;不过对于用户来说只要能及时的观看湖南卫视的节目已经热播的电视剧就OK了哦。芒…

芒果Tv服务器维护,芒果tv怎么看直播?芒果tv直播看不了怎么办?

芒果tv怎么看直播 芒果TV是湖南卫视新媒体金鹰网旗下的网络电视播放器&#xff0c;为用户提供包括电视剧、电影、电视节目、新闻纪实、音乐等多种类型的点播服务。那么芒果tv怎么看直播? 1.打开【芒果TV】&#xff0c;往左划动上方的导航栏。 2.点击【直播】&#xff0c;这里就…

芒果tv视频抓包分析

今天遇到个朋友问我怎么下载芒果tv的蓝光视频&#xff0c;说她也有芒果tv的会员&#xff0c;但是用网上的一些软件下载下来的视频很模糊&#xff0c;根本不是什么蓝光1080p的&#xff0c;所以我们今天就来分析下芒果tv的蓝光视频怎么下载 1.还是老规矩&#xff0c;打开我们的马…

芒果TV 平板端 以EPG(IPTV)形式复刻

技术介绍 使用View-App框架&#xff08;单页面&#xff09;实现交互Web端&#xff0c;使用阿里web播放IPTV端&#xff0c;使用机顶盒播放器使用webpack打包&#xff0c;兼容es5及之前版本功能方面&#xff0c;暂未使用第三方库 项目地址 项目效果&#xff1a;部署地址View-A…