一直想着怎么去通俗的讲解AOP,这两篇博客给了我启发

(https://blog.csdn.net/qukaiwei/article/details/50367761),(https://blog.csdn.net/q982151756/article/details/80513340)下面我加入自己的理解,咱们来说说AOP!

目录

大白话从思想上理解AOP

一、什么是AOP(面向切面编程)?

二、AOP中的相关概念

1.通知(Advice)

2.连接点(JoinPoint)

3.切入点(Pointcut)

4.切面(Aspect)

5.引入(introduction)

6.目标(target)

7.代理(proxy)

8.织入(weaving)

我所理解的AOP原理

通过代码来学习

Spring中的通知类型

前置通知:在目标方法执行之前进行操作

后置通知:在目标方法执行之后进行操作

环绕通知:在目标方法执行之前和之后进行操作

异常抛出通知:在程序出现异常的时候,进行操作

最终通知:无论代码是否会有异常,总是会执行

引介通知:(不会用)

Spring切入点的表达式写法

切入点的表达式语法


大白话从思想上理解AOP

一、什么是AOP(面向切面编程)?

百度百科:在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

我的大白话:无论在学习或者面试的时候,大家都会张口说spring的特性AOP和IOC(控制反转上一篇有讲),有些大神理解的很到位,但是对于大多数初中级工程师来讲还是模糊阶段,但是为什么会有AOP这种技术呢?(使用AOP的原因,AOP的好处)那就是为了开发者的方便!!!就是为了我们少写代码,省劲!要记住上面我说的话!

然后我们举一个比较容易理解的例子(来自:Spring 之 AOP):

要理解切面编程,就需要先理解什么是切面。用刀把一个西瓜分成两瓣,切开的切口就是切面;炒菜,锅与炉子共同来完成炒菜,锅与炉子就是切面。web层级设计中,web层->网关层->服务层->数据层,每一层之间也是一个切面。编程中,对象与对象之间,方法与方法之间,模块与模块之间都是一个个切面。

我们一般做活动的时候,一般对每一个接口都会做活动的有效性校验(是否开始、是否结束等等)、以及这个接口是不是需要用户登录。

按照正常的逻辑,我们可以这么做。

这有个问题就是,有多少接口,就要多少次代码copy。对于一个“懒人”,这是不可容忍的。好,提出一个公共方法,每个接口都来调用这个接口。这里有点切面的味道了。

同样有个问题,我虽然不用每次都copy代码了,但是,每个接口总得要调用这个方法吧。于是就有了切面的概念,我将方法注入到接口调用的某个地方(切点)。

这样接口只需要关心具体的业务,而不需要关注其他非该接口关注的逻辑或处理。 
红框处,就是面向切面编程。

二、AOP中的相关概念

看过了上面的例子,我想大家脑中对AOP已经有了一个大致的雏形,但是又对上面提到的切面之类的术语有一些模糊的地方,接下来就来讲解一下AOP中的相关概念,了解了AOP中的概念,才能真正的掌握AOP的精髓。

初看这么多术语,一下子都不好接受,慢慢来,很快就会搞懂。

1.通知(Advice)

  就是你想要的功能,也就是上面说的 安全,事物,日志等。你给先定义好把,然后在想用的地方用一下。

2.连接点(JoinPoint)

  这个更好解释了,就是spring允许你使用通知的地方,那可真就多了,基本每个方法的前,后(两者都有也行),或抛出异常时都可以是连接点,spring只支持方法连接点.其他如aspectJ还可以让你在构造器或属性注入时都行,不过那不是咱关注的,只要记住,和方法有关的前前后后(抛出异常),都是连接点。

3.切入点(Pointcut)

  上面说的连接点的基础上,来定义切入点,你的一个类里,有15个方法,那就有几十个连接点了对把,但是你并不想在所有方法附近都使用通知(使用叫织入,以后再说),你只想让其中的几个,在调用这几个方法之前,之后或者抛出异常时干点什么,那么就用切点来定义这几个方法,让切点来筛选连接点,选中那几个你想要的方法。

4.切面(Aspect)

  切面是通知和切入点的结合。现在发现了吧,没连接点什么事情,连接点就是为了让你好理解切点,搞出来的,明白这个概念就行了。通知说明了干什么和什么时候干(什么时候通过方法名中的before,after,around等就能知道),而切入点说明了在哪干(指定到底是哪个方法),这就是一个完整的切面定义。

5.引入(introduction)

  允许我们向现有的类添加新方法属性。这不就是把切面(也就是新方法属性:通知定义的)用到目标类中吗

6.目标(target)

  引入中所提到的目标类,也就是要被通知的对象,也就是真正的业务逻辑,他可以在毫不知情的情况下,被咱们织入切面。而自己专注于业务本身的逻辑。

7.代理(proxy)

  怎么实现整套aop机制的,都是通过代理,这个一会给细说。

8.织入(weaving)

  把切面应用到目标对象来创建新的代理对象的过程。有3种方式,spring采用的是运行时,为什么是运行时,后面解释。

  关键就是:切点定义了哪些连接点会得到通知

我所理解的AOP原理

spring用代理类包裹切面,把他们织入到Spring管理的bean中。也就是说代理类伪装成目标类,它会截取对目标类中方法的调用,让调用者对目标类的调用都先变成调用伪装类,伪装类中就先执行了切面,再把调用转发给真正的目标bean。

  现在可以自己想一想,怎么搞出来这个伪装类,才不会被调用者发现(过JVM的检查,JAVA是强类型检查,哪里都要检查类型)。

  1.实现和目标类相同的接口,我也实现和你一样的接口,反正上层都是接口级别的调用,这样我就伪装成了和目标类一样的类(实现了同一接口,咱是兄弟了),也就逃过了类型检查,到java运行期的时候,利用多态的后期绑定(所以spring采用运行时),伪装类(代理类)就变成了接口的真正实现,而他里面包裹了真实的那个目标类,最后实现具体功能的还是目标类,只不过伪装类在之前干了点事情(写日志,安全检查,事物等)。

  这就好比,一个人让你办件事,每次这个时候,你弟弟就会先出来,当然他分不出来了,以为是你,你这个弟弟虽然办不了这事,但是他知道你能办,所以就答应下来了,并且收了点礼物(写日志),收完礼物了,给把事给人家办了啊,所以你弟弟又找你这个哥哥来了,最后把这是办了的还是你自己。但是你自己并不知道你弟弟已经收礼物了,你只是专心把这件事情做好。

  顺着这个思路想,要是本身这个类就没实现一个接口呢,你怎么伪装我,我就压根没有机会让你搞出这个双胞胎的弟弟,那么就用第2种代理方式,创建一个目标类的子类,生个儿子,让儿子伪装我

  2.生成子类调用,这次用子类来做为伪装类,当然这样也能逃过JVM的强类型检查,我继承的吗,当然查不出来了,子类重写了目标类的所有方法,当然在这些重写的方法中,不仅实现了目标类的功能,还在这些功能之前,实现了一些其他的(写日志,安全检查,事物等)。

  这次的对比就是,儿子先从爸爸那把本事都学会了,所有人都找儿子办事情,但是儿子每次办和爸爸同样的事之前,都要收点小礼物(写日志),然后才去办真正的事。当然爸爸是不知道儿子这么干的了。这里就有件事情要说,某些本事是爸爸独有的(final的),儿子学不了,学不了就办不了这件事,办不了这个事情,自然就不能收人家礼了。

  前一种兄弟模式,spring会使用JDK的java.lang.reflect.Proxy类,它允许Spring动态生成一个新类来实现必要的接口,织入通知,并且把对这些接口的任何调用都转发到目标类。

  后一种父子模式,spring使用CGLIB库生成目标类的一个子类,在创建这个子类的时候,spring织入通知,并且把对这个子类的调用委托到目标类。

  相比之下,还是兄弟模式好些,他能更好的实现松耦合,尤其在今天都高喊着面向接口编程的情况下,父子模式只是在没有实现接口的时候,也能织入通知,应当做一种例外。

那在继续学习之前,什么是代理模式以及上述两种AOP动态代理的底层实现都在这篇博客中,我强烈建议先看完,不然很难理解后面的内容:https://blog.csdn.net/qq_28863191/article/details/101517631

让我们通过代码来学习把:

1.导入aop所需要的对应Jar包

一会我们需要使用JUnit来测试,所以需要导入Spring整合单元测试的jar包

2.然后在配置文件 application.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: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/aop ----http://www.springframework.org/schema/aop/spring-aop.xsd"></beans>

横线部分就是aop的约束,我们学习到这里发现什么了么,没错,引入新的约束在多数情况下是添加一个xmlns:XXX的变量,然后在 xsi:schemaLocation 中加入其对应的 url,比如 aop 就是添加

----http://www.springframework.org/schema/aop 
----http://www.springframework.org/schema/aop/spring-aop.xsd

3.接着,我们就要开始写接口和实现类了

/*** 一个产品的接口* @author hp**/
public interface ProductDAO {public void save();public void update();public void find();public void delete();
}
/*** 产品接口的实现类* @author hp**/
public class ProductDAOImpl implements ProductDAO {@Overridepublic void save() {// TODO Auto-generated method stubSystem.out.println("保存商品。。。");}@Overridepublic void update() {// TODO Auto-generated method stubSystem.out.println("修改商品。。。");}@Overridepublic void find() {// TODO Auto-generated method stubSystem.out.println("查找商品。。。");}@Overridepublic void delete() {// TODO Auto-generated method stubSystem.out.println("删除商品。。。");}}

4.定义一个切面类,将通知(你所需要插入的功能)写成方法:

/*** 定义切面类,它有一个checkPrice的权限校验方法* @author hp**/
public class MyAspectXML {public void checkPrice() {System.out.println("权限校验==========");}
}

5.我们将产品类和切面类都交给Spring管理

 <!-- 配置目标对象,被增强对象=================== --><bean id="productDao" class="com.ysx.spring.demo2.ProductDAOImpl" /><!-- 将切面类交给Spring --><bean id="myAspect" class="com.ysx.spring.demo2.MyAspectXML" /><!-- 通过AOP的配置完成对目标类产生的代理  --><aop:config><!-- 设置一个切入点在表达式execution(* com.ysx.spring.demo2.ProductDAOImpl.save(..))这里 --><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.save(..))" id="pointcut1"/><!-- 配置切面 --><aop:aspect ref="myAspect"><!-- aop:before表示在表达式之前pointcut-ref对应着ID所代表的表达式 --><aop:before method="checkPrice" pointcut-ref="pointcut1"/></aop:aspect></aop:config>

6.测试

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;//RunWith是固定格式
@RunWith(SpringJUnit4ClassRunner.class)//加载配置文件
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo2 {//注入到需要被增强的类@Resource(name="productDao")private ProductDAO productDao;@Testpublic void demo2() {productDao.save();}
}

可以在自己的IDE上试一下,得到的结果是

Spring中的通知类型

前置通知:在目标方法执行之前进行操作

前置通知可以获得切入点信息

在切面类加入

public class MyAspectXML {public void checkPrice(JoinPoint joinPoint) {System.out.println("权限校验==========" + joinPoint);}
}

结果就会变成

后置通知:在目标方法执行之后进行操作

后置通知除了可以获得切入点信息之外,还可以获得方法的返回值

这次我们拿 update() 方法举例

接口和实现类

/*** 一个产品的接口* @author hp**/
public interface ProductDAO {public void save();public void update();public void find();public String delete();
}/*** 产品接口的实现类* @author hp**/
public class ProductDAOImpl implements ProductDAO {@Overridepublic void save() {// TODO Auto-generated method stubSystem.out.println("保存商品。。。");}@Overridepublic void update() {// TODO Auto-generated method stubSystem.out.println("修改商品。。。");}@Overridepublic void find() {// TODO Auto-generated method stubSystem.out.println("查找商品。。。");}@Overridepublic String delete() {// TODO Auto-generated method stubSystem.out.println("删除商品。。。");return "杨大侠";}}

切面类

public class MyAspectXML {/*** 前置通知* @param joinPoint*/public void checkPrice(JoinPoint joinPoint) {System.out.println("权限校验==========" + joinPoint);}/*** 后置通知* @param r*/public void writeLog(Object result) {System.out.println("日志记录==========" + result);}
}

application.xml配置文件

<aop:config><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.save(..))" id="pointcut1"/><!-- 配置连接到delete方法 --><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.delete(..))" id="pointcut2"/><!-- 配置切面 --><aop:aspect ref="myAspect"><aop:before method="checkPrice" pointcut-ref="pointcut1"/><!-- pointcut-ref 通过上面id连接method       表示要连接的切面类里面的通知方法returning    里面是返回值,这个变量的名字必须和writeLog方法的参数一致 --><aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/></aop:aspect></aop:config>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo2 {@Resource(name="productDao")private ProductDAO productDao;@Testpublic void demo2() {productDao.save();productDao.delete();}
}

最后是我们得到的结果,可以看到“杨大侠”名字在日志记录的时候被输出出来了。

环绕通知:在目标方法执行之前和之后进行操作

环绕通知除了可以获得切入点信息和方法的返回值之外,还可以阻止目标方法的执行

这次我们拿 update() 方法举例

接口和实现类

/*** 一个产品的接口* @author hp**/
public interface ProductDAO {public void save();public void update();public void find();public String delete();
}/*** 产品接口的实现类* @author hp**/
public class ProductDAOImpl implements ProductDAO {@Overridepublic void save() {// TODO Auto-generated method stubSystem.out.println("保存商品。。。");}@Overridepublic void update() {// TODO Auto-generated method stubSystem.out.println("修改商品。。。");}@Overridepublic void find() {// TODO Auto-generated method stubSystem.out.println("查找商品。。。");int i = 1/0;}@Overridepublic String delete() {// TODO Auto-generated method stubSystem.out.println("删除商品。。。");return "杨大侠";}}

切面类

/*** 定义切面类,它有一个checkPrice的权限校验方法* @author hp**/
public class MyAspectXML {/*** 前置通知* @param joinPoint*/public void checkPrice(JoinPoint joinPoint) {System.out.println("权限校验==========" + joinPoint);}/*** 后置通知* @param r*/public void writeLog(Object result) {System.out.println("日志记录==========" + result);}public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕前通知============");//这里就相当于执行目标程序,如果有返回值接收返回,没有就voidObject obj = joinPoint.proceed();System.out.println("环绕前通知============");return obj;}/*** 异常抛出通知*/public void afterThrowing(Throwable ex) {System.out.println("异常抛出通知===============" + ex);}public void after() {System.out.println("最终通知==============");}
}

application.xml配置文件

<!-- 通过AOP的配置完成对目标类产生的代理  --><aop:config><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.save(..))" id="pointcut1"/><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.delete(..))" id="pointcut2"/><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.update(..))" id="pointcut3"/><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.find(..))" id="pointcut4"/><!-- 配置切面 --><aop:aspect ref="myAspect"><!-- 前置通知=========== --><aop:before method="checkPrice" pointcut-ref="pointcut1"/><!-- 后置通知=========== --><aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/><!-- 环绕通知=========== --><aop:around method="around" pointcut-ref="pointcut3"/><!-- 异常抛出通知=========== --><aop:after-throwing method="afterThrowing" pointcut-ref="pointcut4" throwing="ex"/><!-- 最终通知 --><aop:after method="after" pointcut-ref="pointcut4"/></aop:aspect></aop:config>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo2 {@Resource(name="productDao")private ProductDAO productDao;@Testpublic void demo2() {productDao.save();productDao.delete();productDao.update();productDao.find();}
}

运行结果

异常抛出通知:在程序出现异常的时候,进行操作

可以获得异常信息

最终通知:无论代码是否会有异常,总是会执行

相当于执行 finally 代码块里的内容,跟异常抛出通知一起讲,相当于try/catch/finally

这次我们拿 find() 方法举例

接口和实现类

/*** 一个产品的接口* @author hp**/
public interface ProductDAO {public void save();public void update();public void find();public String delete();
}/*** 产品接口的实现类* @author hp**/
public class ProductDAOImpl implements ProductDAO {@Overridepublic void save() {// TODO Auto-generated method stubSystem.out.println("保存商品。。。");}@Overridepublic void update() {// TODO Auto-generated method stubSystem.out.println("修改商品。。。");}@Overridepublic void find() {// TODO Auto-generated method stubSystem.out.println("查找商品。。。");}@Overridepublic String delete() {// TODO Auto-generated method stubSystem.out.println("删除商品。。。");return "杨大侠";}}

切面类

public class MyAspectXML {/*** 前置通知* @param joinPoint*/public void checkPrice(JoinPoint joinPoint) {System.out.println("权限校验==========" + joinPoint);}/*** 后置通知* @param r*/public void writeLog(Object result) {System.out.println("日志记录==========" + result);}public Object around(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("环绕前通知============");//这里就相当于执行目标程序,如果有返回值接收返回,没有就voidObject obj = joinPoint.proceed();System.out.println("环绕前通知============");return obj;}
}

application.xml配置文件

 <aop:config><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.save(..))" id="pointcut1"/><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.delete(..))" id="pointcut2"/><aop:pointcut expression="execution(* com.ysx.spring.demo2.ProductDAOImpl.update(..))" id="pointcut3"/><!-- 配置切面 --><aop:aspect ref="myAspect"><!-- 前置通知=========== --><aop:before method="checkPrice" pointcut-ref="pointcut1"/><!-- 后置通知=========== --><aop:after-returning method="writeLog" pointcut-ref="pointcut2" returning="result"/><!-- 环绕通知=========== --><aop:around method="around" pointcut-ref="pointcut3"/></aop:aspect></aop:config>

测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class SpringDemo2 {@Resource(name="productDao")private ProductDAO productDao;@Testpublic void demo2() {productDao.save();productDao.delete();productDao.update();}
}

运行结果

引介通知:(不会用)

Spring切入点的表达式写法

切入点的表达式语法

基于execution的函数完成的

语法:

  • [访问修饰符]  方法返回值  包名.类名.方法名(参数)(注:[]括号表示可以省略)
  • public void com.ysx.spring.CustomerDAO.save(..)   (参数位置两个点表示任意参数)
  • 我们还可以用 * 表示, * 代表任意类型,比如在上面通知的代码中 用 * 表示任意返回类型
  • * com.ysx.spring.CustomerDAO. *(..) 表示CustomerDAO中的每一个方法,返回类型任意
  • * com.ysx.spring.*.*(..)表示com.ysx.spring包下的每一个类的每一个方法
  • * com.ysx.spring.*DAO.*(..)表示com.ysx.spring包下的每一个叫 XXXDAO 的类的每一个方法

Spring 学习 (三)大话AOP相关推荐

  1. Spring学习笔记002 - AOP

    2019独角兽企业重金招聘Python工程师标准>>> 一.AOP的几个概念 1.Aspect,表示切面功能 配置如下: <?xml version="1.0&quo ...

  2. 【Spring学习笔记】AOP

    文章目录 一.何为AOP? 二.AOP相关术语 三.AOP流行框架比较 四.动态代理 1.创建接口UserDao 2.创建实现类 UserDaoImpl 3.创建切面类MyAspect 4.创建代理类 ...

  3. Spring 学习笔记----->AOP

    Spring 学习笔记----->AOP 代理模式 为什么学代理模式? 因为这就是Spring Aop的底层 代理模式的分类: 静态代理 动态代理 静态代理 生活用的例子: 房东 public ...

  4. Spring学习11之AOP

    一.AOP是什么? AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善 ...

  5. Spring学习笔记之AOP配置篇(一) 注解配置

    目录 1. 创建并声明一个切面 2. 使用切面的通知 3. 定义切面的优先级 4. 使用切入点表达式 1. 创建并声明一个切面 首先,创建一个类,添加@Component注解使其添加到IoC容器 然后 ...

  6. 第四阶段--Spring学习-03

    第四阶段--Spring学习 13.AOP简介 13.1 什么是AOP? 13.2 AOP作用 13.3 AOP核心概念 14.AOP入门案例 14.1 AOP入门案例思路分析 14.2 环境准备 1 ...

  7. Spring学习进阶 (三) Spring AOP

    一.是什么AOP是Aspect Oriented Programing的简称,最初被译为"面向方面编程":AOP通过横向抽取机制为无法通过纵向继承体系进行抽象的重复性代码提供了解决 ...

  8. Spring学习(三)Spring AOP 简介

    本文借鉴:Spring学习(特此感谢!) 一.简介 定义 aop就是面向切面编程,在数据库事务中切面编程被广泛使用. 在面向切面编程的思想里面,把功能分为核心业务功能,和周边功能. 核心业务:比如登陆 ...

  9. Spring学习笔记(三) AOP_annotation,AOP_XML

    在学习课程以前,听说AOP有种很神秘的感觉,好像很好深的技术.其实原理很简单,使用动态代理的方式给程序增加逻辑.与此相似的有struts2中的filter拦截器. 再讲AOP之前先把需求说一下: 同S ...

最新文章

  1. 哈佛最受学生欢迎的CS50课程来啦!「撕书教授」在家录了一套4K网课
  2. DOS命令输出的重定向
  3. Camelot:从pdf中提取表格数据
  4. 一个html表单赋给另一个表单,是否可以将表单输入到另一个html表单
  5. 强化学习(三)—— 策略学习(Policy-Based)及策略梯度(Policy Gradient)
  6. js的时间函数实现一个电子表
  7. css expression
  8. SAP Webclient UI和Fiori UI的混搭
  9. .NET CORE在ubuntu1604上运行
  10. SAP License:ERP概述
  11. python免费教程视频-Python免费视频教程
  12. java将数组加上千分号_PHP实现对数字分隔加千分号的方法
  13. python greenlet背景介绍与实现机制
  14. 《JAVA常用算法手册》学习笔记(一)
  15. html字体加粗且变色,简单的html代码 加粗 加亮 字型加大 变色 分别是写什么`
  16. 地图 svg中国地图、echarts百度迁徙图
  17. 运维开发工程师(BKDS)理论基础
  18. PHP后端跨域HEADER头的设置
  19. 分类常用的神经网络模型,典型神经网络模型包括
  20. python递归查找文件

热门文章

  1. 基于DNN的鼻咽癌鼻咽淋巴瘤数据处理
  2. “沙箱机制”是什么?
  3. 远离喧嚣,拥抱自然,洗尽铅华
  4. 项目实战之aiguibin-protal-gateway集成门户
  5. linux磁盘加密bitlocker,亲身体验Windows 2008 Server R2下的BitLocker驱动器加密
  6. 【入门AUTOSAR网络管理测试】NOS-RSS状态转换
  7. 实现人脸磨皮算法---OpenCV-Python开发指南(58)
  8. 03基础自绘-13滑动选择-tumbler
  9. rapidly exploring random tree(快速搜索随机树)路径规划
  10. 微信小程序--页面劫持