闭关修炼180天--手写IOC和AOP(xml篇)

帝莘

首先先分享一波思维导图,涵盖了一些Spring的知识点,当然这里并不全面,后期我会持续更新知识点。

在手写实现IOC和AOP之前(也就是打造一个简单的Spring框架),先简单的了解一些Spring以及它的两个核心思想IOC和AOP的相关概念。

Spring:

  概述:spring是分层的全栈轻量级开源框架,以ioc和AOP为内核,提供了展现层spring mvc和业务层管理等众多的企业应用技术,还能整合众多开源的第三方框架。

  优势:1.方便解耦,简化开发 2.AOP编程的支持 3.声明式事务的支持。4.方便程序的测试。5.方便集成各种优秀框架 6.降低javaEE API的使用难度。6.源码是经典的java学习案例。

  Spring 的核⼼结构:Spring是⼀个分层⾮常清晰并且依赖关系、职责定位⾮常明确的轻量级框架,主要包括⼏个⼤模块:数据处理模块、Web模块、AOP(Aspect Oriented Programming)/Aspects模块、

Core Container模块和 Test 模块,如下图所示,Spring依靠这些基本模块,实现了⼀个令⼈愉悦的融合了现有解决⽅案的零侵⼊的轻量级框架。

IOC: 

  IOC概念:控制反转,它是一个技术思想,不是一个技术实现,描述的事情是:java开发领域对象的创建,管理的问题。

  传统开发方式:比如A依赖于B,往往会在A中new一个B的对象。

  IOC思想下的开发方式:我们不用自己去new对象了,而是由IOC容器(Spring框架)去帮助我们实例化对象并且管理它,我们需要使用哪个对象,去问IOC容器要即可。我们丧失了一个权力(创建、管理对

象的权力),得到了一个福利(不用考虑对象的创建、管理一系列事情)

  为什么叫控制反转?控制:指的是对象创建(实例化、管理)的权力;反转:控制权交给了外部环境(spring框架)

  IOC解决了什么问题:IOC解决了对象之间的耦合问题

  IOC和DI的区别?IOC是控制反转,DI是依赖注入。

  怎么理解:IOC和DI描述的是一件事情(对象实例化及依赖关系维护这件事),不过角度不同。

  IOC是站在对象的角度,对象实例化及其管理的权力交给了(反转)容器。

  DI是站在容器的角度,容器会把对象依赖的其他对象注入(送过去),比如A对象实例化过程中因为声明了一个B类型的属性,那么就需要容器把B对象注入给A。

手写Spring框架流程:

实现思路:

  1. 编写xml文件,在根标签beans下条件bean,每一组bean对应一个需要加入容器管理的对象。
  2. 编写BeanFactory:解析xml文件,将解析出的一个个bean装载进map集合中,其中id为bean标签内id属性的值,value为根据bean标签内class属性的全路径值反射得到的目标类的实例对象。
  3. BeanFactory还要提供一个获取实例的方法getBean(String id)
  4. 为了添加不同类的依赖关系,在xml文件里面的bean标签组内添加property标签,其中property内的name属性的值是父标签所代表的类的set方法后面的名字,ref属性的值是它所依赖的对象的bean-id。在BeanFactory中进一步解析,把对应依赖关系添加进对象中,更新map。
  5. 配置事务,保证一个线程使用一个数据库连接,根据代理模式生产代理对象,根据代理对象回调用invoke方法的特性,在原方法前后增加事务增强,在这里,具体的事务管理我们交给TransactionManager类进行具体的管理,配置好代理类和事务管理类之间的依赖关系。

代码实现:

  先看一下整体的项目结构:

  

  sql建表语句:

  1. SET FOREIGN_KEY_CHECKS=0;
  2.  
  3. -- ----------------------------
  4. -- Table structure for users
  5. -- ----------------------------
  6. DROP TABLE IF EXISTS `users`;
  7. CREATE TABLE `users` (
  8. `id` int(11) NOT NULL,
  9. `username` varchar(255) COLLATE utf8_bin DEFAULT NULL,
  10. `balance` double(11,0) DEFAULT NULL,
  11. PRIMARY KEY (`id`)
  12. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
  13.  
  14. -- ----------------------------
  15. -- Records of users
  16. -- ----------------------------
  17. INSERT INTO `users` VALUES ('1', '汪苏泷', '1000');
  18. INSERT INTO `users` VALUES ('2', '薛之谦', '1000');

  几个pom依赖

  1. <!--dom4j-->
  2. <dependency>
  3. <groupId>dom4j</groupId>
  4. <artifactId>dom4j</artifactId>
  5. <version>1.6.1</version>
  6. </dependency>
  7. <dependency>
  8. <groupId>jaxen</groupId>
  9. <artifactId>jaxen</artifactId>
  10. <version>1.1.6</version>
  11. </dependency>
  12. <dependency>
  13. <groupId>mysql</groupId>
  14. <artifactId>mysql-connector-java</artifactId>
  15. <version>5.1.17</version>
  16. </dependency>
  17. <dependency>
  18. <groupId>c3p0</groupId>
  19. <artifactId>c3p0</artifactId>
  20. <version>0.9.1.2</version>
  21. </dependency>
  22. <!--引入cglib依赖-->
  23. <dependency>
  24. <groupId>cglib</groupId>
  25. <artifactId>cglib</artifactId>
  26. <version>2.1_2</version>
  27. </dependency>

  xml文件代码:bean.xml

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <beans>
  3. <bean id="UserDao" class="com.radorm.spring.dao.impl.UserDaoImpl">
  4. <property name="ConnectionUtils" ref="ConnectionUtils"></property>
  5. </bean>
  6. <bean id="UserService" class="com.radorm.spring.service.impl.UserServiceImpl">
  7. <property name="UserDao" ref="UserDao"></property>
  8. </bean>
  9.  
  10. <bean id="ConnectionUtils" class="com.radorm.spring.utils.ConnectionUtils"></bean>
  11.  
  12. <bean id="TransactionManager" class="com.radorm.spring.utils.TransactionManager">
  13. <property name="ConnectionUtils" ref="ConnectionUtils"></property>
  14. </bean>
  15.  
  16. <bean id="ProxyFactory" class="com.radorm.spring.factory.ProxyFactory">
  17. <property name="TransactionManager" ref="TransactionManager"></property>
  18. </bean>
  19. </beans>

  三个工具类:DataSourceUtils ConnectionUtils ,TransactionManager

  1. import com.mchange.v2.c3p0.ComboPooledDataSource;
  2. import javax.sql.DataSource;
  3. import java.beans.PropertyVetoException;
  4. import java.sql.Connection;
  5. import java.sql.SQLException;
  6.  
  7. public class DataSourceUtils {
  8.  
  9. private DataSourceUtils(){}
  10.  
  11. private static DataSourceUtils dataSourceUtils = new DataSourceUtils();
  12.  
  13. public static DataSourceUtils getInstance(){
  14. return dataSourceUtils;
  15. }
  16.  
  17. public DataSource createDataSource(){
  18. //创建连接池
  19. ComboPooledDataSource dataSource = new ComboPooledDataSource();
  20. try {
  21. dataSource.setDriverClass("com.mysql.jdbc.Driver");
  22. dataSource.setJdbcUrl("jdbc:mysql:///mybatis");
  23. dataSource.setUser("root");
  24. dataSource.setPassword("root");
  25. } catch (PropertyVetoException e) {
  26. e.printStackTrace();
  27. }
  28. return dataSource;
  29. }
  30.  
  31. public Connection getConnection(){
  32. DataSource dataSource = createDataSource();
  33. try {
  34. return dataSource.getConnection();
  35. } catch (SQLException e) {
  36. e.printStackTrace();
  37. }
  38. return null;
  39. }
  40. }
  1. import java.sql.Connection;
  2.  
  3. public class ConnectionUtils {
  4. /**
  5. * 声明当前线程
  6. */
  7. private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
  8.  
  9. /**
  10. * 获取当前线程里面的连接
  11. * @return
  12. */
  13. public Connection getLocalConnection(){
  14. Connection connection = threadLocal.get();
  15. if(connection == null){
  16. //如果在当前线程内获取不到连接,那就在线程池中获取,并且放到当前线程中
  17. connection = DataSourceUtils.getInstance().getConnection();
  18. threadLocal.set(connection);
  19. }
  20. return connection;
  21. }
  22. }
  1. import java.sql.SQLException;
  2.  
  3. /**
  4. * 事务管理工厂
  5. */
  6. public class TransactionManager {
  7.  
  8. private ConnectionUtils connectionUtils;
  9.  
  10. public void setConnectionUtils(ConnectionUtils connectionUtils) {
  11. this.connectionUtils = connectionUtils;
  12. }
  13.  
  14. public void beginTransaction() throws SQLException {
  15. connectionUtils.getLocalConnection().setAutoCommit(false);
  16. }
  17.  
  18. public void commitTransaction() throws SQLException{
  19. connectionUtils.getLocalConnection().commit();
  20. }
  21.  
  22. public void rollbackTransaction(){
  23. try {
  24. connectionUtils.getLocalConnection().rollback();
  25. } catch (SQLException e) {
  26. e.printStackTrace();
  27. }
  28. }
  29. }

  两个工厂类:BeanFactory,ProxyFactory

  1. import org.dom4j.Document;
  2. import org.dom4j.DocumentException;
  3. import org.dom4j.Element;
  4. import org.dom4j.io.SAXReader;
  5.  
  6. import java.io.InputStream;
  7. import java.lang.reflect.InvocationTargetException;
  8. import java.lang.reflect.Method;
  9. import java.util.HashMap;
  10. import java.util.List;
  11. import java.util.Map;
  12.  
  13. public class BeanFactory {
  14.  
  15. private static Map<String,Object> map = new HashMap<>();
  16.  
  17. /**
  18. * 1.使用dom4j技术读取配置文件信息
  19. * 2.将读取到的对象以及key封装进map中
  20. * key = id,value = class反射产生的实例对象
  21. */
  22. static {
  23. //根据xml的路径加载为字节流到内存中
  24. InputStream inputStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
  25. SAXReader saxReader = new SAXReader();
  26. try {
  27. //读取该字节流为document
  28. Document document = saxReader.read(inputStream);
  29. //获取根标签下的元素
  30. Element rootElement = document.getRootElement();
  31. //获取根标签内部所有的bean标签里面的内容
  32. List<Element> beanList = rootElement.selectNodes("//bean");
  33. for (Element element : beanList) {
  34. //根据属性名获取bean标签里面的内容
  35. String id = element.attributeValue("id");
  36. String aClass = element.attributeValue("class");
  37. //利用反射技术根据全路径获取类对象
  38. Class<?> aClass1 = Class.forName(aClass);
  39. //根据类对象获取实例
  40. Object o = aClass1.newInstance();
  41. //组装map
  42. map.put(id,o);
  43. }
  44. //获取所有的property
  45. List<Element> propertyList = rootElement.selectNodes("//property");
  46. for (Element element : propertyList) {
  47. //获取property标签里面的属性值
  48. String name = element.attributeValue("name");
  49. String ref = element.attributeValue("ref");
  50.  
  51. //获取父标签
  52. Element parentElement = element.getParent();
  53. //获取父标签的id
  54. String parentId = parentElement.attributeValue("id");
  55. //根据父标签的id获取出父标签的实例化对象
  56. Object parentObj = map.get(parentId);
  57. //获取父对象想要添加依赖的实例化对象
  58. Object refObj = map.get(ref);
  59. //获取父对象的所有方法
  60. Method[] methods = parentObj.getClass().getMethods();
  61. for (Method method : methods) {
  62. String methodName = method.getName();
  63. //判断是否是set方法
  64. if(methodName.equalsIgnoreCase("set"+name)){
  65. //往方法中传入参数
  66. method.invoke(parentObj,refObj);
  67. }
  68. }
  69. //重新将父对象加入到map中
  70. map.put(parentId,parentObj);
  71. }
  72. } catch (DocumentException e) {
  73. e.printStackTrace();
  74. } catch (ClassNotFoundException e) {
  75. e.printStackTrace();
  76. } catch (IllegalAccessException e) {
  77. e.printStackTrace();
  78. } catch (InstantiationException e) {
  79. e.printStackTrace();
  80. } catch (InvocationTargetException e) {
  81. e.printStackTrace();
  82. }
  83. }
  84.  
  85. public static <T> T getBean(String id){
  86. return (T) map.get(id);
  87. }
  88. }
  1. import com.radorm.spring.utils.TransactionManager;
  2. import net.sf.cglib.proxy.Enhancer;
  3. import net.sf.cglib.proxy.MethodInterceptor;
  4. import net.sf.cglib.proxy.MethodProxy;
  5.  
  6. import java.lang.reflect.InvocationHandler;
  7. import java.lang.reflect.Method;
  8. import java.lang.reflect.Proxy;
  9.  
  10. /**
  11. * JDK动态代理工厂
  12. */
  13. public class ProxyFactory {
  14.  
  15. private TransactionManager transactionManager;
  16.  
  17. public void setTransactionManager(TransactionManager transactionManager) {
  18. this.transactionManager = transactionManager;
  19. }
  20.  
  21. /**
  22. * jdk动态代理:要求被代理的对象实现了接口
  23. * @param object 委托对象
  24. * @param <T> 代理对象
  25. * @return
  26. */
  27. public <T> T getProxyJDK(Object object){
  28. Object result = Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler() {
  29. @Override
  30. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  31. Object re = null;
  32. try {
  33. //开启事务
  34. transactionManager.beginTransaction();
  35. re = method.invoke(object,args);
  36. //提交事务
  37. transactionManager.commitTransaction();
  38. } catch (Exception e) {
  39. e.printStackTrace();
  40. //回滚事务
  41. transactionManager.rollbackTransaction();
  42. throw e;
  43. }
  44. return re;
  45. }
  46. });
  47.  
  48. return (T) result;
  49. }
  50.  
  51. /**
  52. * cglib动态代理
  53. * @param obj 委托对象
  54. * @param <T> 代理对象
  55. * @return
  56. */
  57. public <T> T getProxyCglib(Object obj){
  58. Object result = Enhancer.create(obj.getClass(), new MethodInterceptor() {
  59. @Override
  60. public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
  61. Object od = null;
  62. try {
  63. //开启事务
  64. transactionManager.beginTransaction();
  65. od = method.invoke(obj,objects);
  66. //提交事务
  67. transactionManager.commitTransaction();
  68. } catch (Exception e) {
  69. e.printStackTrace();
  70. //回滚事务
  71. transactionManager.rollbackTransaction();
  72. throw e;
  73. }
  74. return od;
  75. }
  76. });
  77. return (T) result;
  78. }
  79. }

  一个pojo:Users

  1. public class Users {
  2.  
  3. private Integer id;
  4.  
  5. private String username;
  6.  
  7. private Double balance;
  8.  
  9. public Integer getId() {
  10. return id;
  11. }
  12.  
  13. public void setId(Integer id) {
  14. this.id = id;
  15. }
  16.  
  17. public String getUsername() {
  18. return username;
  19. }
  20.  
  21. public void setUsername(String username) {
  22. this.username = username;
  23. }
  24.  
  25. public Double getBalance() {
  26. return balance;
  27. }
  28.  
  29. public void setBalance(Double balance) {
  30. this.balance = balance;
  31. }
  32.  
  33. public Users(Integer id, String username, Double balance) {
  34. this.id = id;
  35. this.username = username;
  36. this.balance = balance;
  37. }
  38. }

  一个dao + 它的impl:UserDao + UserDaoImpl

  1. import com.radorm.spring.pojo.Users;
  2.  
  3. public interface UserDao {
  4.  
  5. void draw();
  6.  
  7. Users select(Integer id);
  8.  
  9. void updateUsers(Users users);
  10. }
  1. import com.radorm.spring.dao.UserDao;
  2. import com.radorm.spring.pojo.Users;
  3. import com.radorm.spring.utils.ConnectionUtils;
  4.  
  5. import java.sql.Connection;
  6. import java.sql.PreparedStatement;
  7. import java.sql.ResultSet;
  8. import java.sql.SQLException;
  9.  
  10. public class UserDaoImpl implements UserDao {
  11.  
  12. private ConnectionUtils connectionUtils;
  13.  
  14. public void setConnectionUtils(ConnectionUtils connectionUtils) {
  15. this.connectionUtils = connectionUtils;
  16. }
  17.  
  18. @Override
  19. public void draw() {
  20. System.out.println("11111111");
  21. }
  22.  
  23. @Override
  24. public Users select(Integer id) {
  25. Connection connection = connectionUtils.getLocalConnection();
  26. try {
  27. PreparedStatement preparedStatement = connection.prepareStatement("select * from users where id = ?");
  28. preparedStatement.setObject(1,id);
  29. ResultSet resultSet = preparedStatement.executeQuery();
  30. while (resultSet.next()){
  31. int id1 = resultSet.getInt("id");
  32. String username = resultSet.getString("username");
  33. double balance = resultSet.getDouble("balance");
  34. Users users = new Users(id1,username,balance);
  35. return users;
  36. }
  37.  
  38. } catch (SQLException e) {
  39. e.printStackTrace();
  40. }
  41. return null;
  42. }
  43.  
  44. @Override
  45. public void updateUsers(Users users) {
  46. Connection localConnection = connectionUtils.getLocalConnection();
  47. try {
  48. PreparedStatement preparedStatement = localConnection.prepareStatement("update users set username = ?,balance = ? where id = ?");
  49. preparedStatement.setString(1,users.getUsername());
  50. preparedStatement.setDouble(2,users.getBalance());
  51. preparedStatement.setInt(3,users.getId());
  52. preparedStatement.executeUpdate();
  53. } catch (SQLException e) {
  54. e.printStackTrace();
  55. }
  56.  
  57. }
  58. }

  一个service+它的impl:UserService,UserServiceImpl

  1. public interface UserService {
  2.  
  3. void transfer(Integer fromId,Integer toId,Double money) throws Exception;
  4.  
  5. }
  1. import com.radorm.spring.dao.UserDao;
  2. import com.radorm.spring.pojo.Users;
  3. import com.radorm.spring.service.UserService;
  4.  
  5. public class UserServiceImpl implements UserService {
  6.  
  7. private UserDao userDao;
  8.  
  9. public void setUserDao(UserDao userDao) {
  10. this.userDao = userDao;
  11. }
  12.  
  13. @Override
  14. public void transfer(Integer fromId, Integer toId, Double money) throws Exception {
  15.  
  16. Users from = userDao.select(fromId);
  17.  
  18. Users to = userDao.select(toId);
  19.  
  20. from.setBalance(from.getBalance() - money);
  21.  
  22. to.setBalance(to.getBalance() + money);
  23.  
  24. userDao.updateUsers(from);
  25. //该异常代码可以选择打开或者关闭,检验事务的作用
  26. //int i = 1/0
  27.  
  28. userDao.updateUsers(to);
  29.  
  30. }
  31. }

  test:BeanTest

  1. import com.radorm.spring.factory.ProxyFactory;
  2. import com.radorm.spring.service.UserService;
  3. import com.radorm.spring.factory.BeanFactory;
  4. import org.junit.Test;
  5.  
  6. public class BeanTest {
  7.  
  8. @Test
  9. public void test_bean(){
  10. ProxyFactory proxyFactory = BeanFactory.getBean("ProxyFactory");
  11. UserService userService = proxyFactory.getProxyJDK(BeanFactory.getBean("UserService"));
  12. try {
  13. userService.transfer(1,2,100d);
  14. System.out.println("转账成功");
  15. } catch (Exception e) {
  16. System.out.println("转账失败");
  17. }
  18. }
  19. }

到此代码完成!

 我是Slience帝莘,期待与你的技术交流和思想碰撞。

闭关修炼180天--手写IOC和AOP(xml篇)的更多相关文章

  1. 闭关修炼180天--手写持久层框架(mybatis简易版)

    闭关修炼180天--手写持久层框架(mybatis简易版) 抛砖引玉 首先先看一段传统的JDBC编码的代码实现: //传统的JDBC实现 public static void main(String[ ...

  2. 初学源码之——银行案例手写IOC和AOP

    手写实现lOC和AOP 上一部分我们理解了loC和AOP思想,我们先不考虑Spring是如何实现这两个思想的,此处准备了一个『银行转账」的案例,请分析该案例在代码层次有什么问题?分析之后使用我们已有知 ...

  3. 手写IOC实现过程

    一.手写ioc前基础知识 1.什么是IOC(Inversion of Control 控制反转)? IoC不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合.更优良 ...

  4. SpringBoot项目里,让TKmybatis支持可以手写sql的Mapper.xml文件

    SpringBoot项目通常配合TKMybatis或MyBatis-Plus来做数据的持久化. 对于单表的增删改查,TKMybatis优雅简洁,无需像传统mybatis那样在mapper.xml文件里 ...

  5. 手写IOC实践

    一.IOC 1.什么是IOC? 控制反转(英语:Inversion of Control,缩写为IoC),是[面向对象编程]中的一种设计原则,可以用来减低计算机代码之间的[耦合度]其中最常见的方式叫做 ...

  6. 一文全解:利用谷歌深度学习框架Tensorflow识别手写数字图片(初学者篇)

    笔记整理者:王小草 笔记整理时间2017年2月24日 原文地址 http://blog.csdn.net/sinat_33761963/article/details/56837466?fps=1&a ...

  7. 《四 spring源码》利用TransactionManager手写spring的aop

    事务控制分类 编程式事务控制          自己手动控制事务,就叫做编程式事务控制. Jdbc代码: Conn.setAutoCommite(false);  // 设置手动控制事务 Hibern ...

  8. 手写Promise看着一篇就足够了

    目录 概要 博客思路 API的特性与手写源码 构造函数 then catch Promise.resolved Promise.rejected Promise.all Promise.race 概要 ...

  9. 第三节:工厂+反射+配置文件(手写IOC)对缓存进行管理。

    一. 章前小节 在前面的两个章节,我们运用依赖倒置原则,分别对 System.Web.Caching.Cache和 System.Runtime.Cacheing两类缓存进行了封装,并形成了ICach ...

随机推荐

  1. Guitar Pro 7教程之如何导入吉他谱

    在前面的章节小编为大家也讲解了不少关于Guitar Pro 的相关教程,譬如{cms_selflink page='index' text='Guitar Pro下载'},安装等等一系列的使用教程,前 ...

  2. 巧妙运用Camtasia 旅行Vlog轻松get

    旅行时,除了要欣赏当地的美丽风景.享受当地美食外,当然还要将旅行中的各种小细节记录下来.以前我们可能更多地使用相机拍照,现在呢,越来越多的人采用视频拍摄的方式制作Vlog.这种兼具影像与叙事的视频表现 ...

  3. 下载器Folx扩展程序支持哪些浏览器

    Folx使用多线程的下载方式大大提升了下载的速度,可以完全替代浏览器自带的下载工具,使下载文件的管理更加简单高效.但是,必须给浏览器安装Folx扩展程序,才能使用Folx下载页面链接. Folx在偏好 ...

  4. jenkins 安装与创建项目

    一.安装1.jenkins下载地址:https://jenkins.io/zh/ 中文版2.下载下来,是msi文件,直接安装3.本地访问,localhost:8080 二.访问 如果访问不了,以下原因 ...

  5. 为什么Java不允许创建范型数组

    问题示例 List<Integer>[] intListArr = new ArrayList<Integer>[8]; // 编译时报错 能看到这么看似没啥问题的一个简单语句 ...

  6. Java 滴IO系统

    JAVA IO 流可以概括为 "两个对应,一个桥梁".两个对应指字节流(Byte Stream)和字符流(Char Stream)的对应,输入流和输出流的对应. 一个桥梁指从字节流 ...

  7. 蓝桥杯——字母阵列(2018JavaC组第3题)

    字母阵列(18JavaC3) 标题:字母阵列 仔细寻找,会发现:在下面的8x8的方阵中,隐藏着字母序列:"LANQIAO". SLANQIAO ZOEXCCGB MOAYWKHI ...

  8. Java基础教程——线程通信

    线程通信:等待.唤醒 Object方法 这些方法在拥有资源时才能调用 notify 唤醒某个线程.唤醒后不是立马执行,而是等CPU分配 wait 等待,释放锁,不占用CPU资源 notifyAll 唤 ...

  9. LeetCode 024 Swap Nodes in Pairs

    题目描述:Swap Nodes in Pairs Given a linked list, swap every two adjacent nodes and return its head. For ...

  10. moviepy音视频剪辑:视频剪辑基类VideoClip详解

    ☞ ░ 前往老猿Python博文目录 ░ 一.概述 在<moviepy音视频剪辑:moviepy中的剪辑基类Clip详解>和<moviepy音视频剪辑:moviepy中的剪辑基类Cl ...