答:
1.struts是一个按MVC模式设计的Web层框架,其实它就是一个大大的servlet,这个Servlet名为ActionServlet,或是ActionServlet的子类。我们可以在web.xml文件中将符合某种特征的所有请求交给这个Servlet处理,这个Servlet再参照一个配置文件(通常为/WEB-INF/struts-config.xml)将各个请求分别分配给不同的action去处理。
一个扩展知识点:struts的配置文件可以有多个,可以按模块配置各自的配置文件,这样可以防止配置文件的过度膨胀;
2.ActionServlet把请求交给action去处理之前,会将请求参数封装成一个formbean对象(就是一个Java类,这个类中的每个属性对应一个请求参数),封装成一个什么样的formbean对象呢?看配置文件。
3.要说明的是, ActionServlet把formbean对象传递给action的execute方法之前,可能会调用formbean的validate方法进行校验,只有校验通过后才将这个formbean对象传递给action的execute方法,否则,它将返回一个错误页面,这个错误页面由input属性指定,(看配置文件)作者为什么将这里命名为input属性,而不是error属性,我们后面结合实际的运行效果进行分析。
4.action执行完后要返回显示的结果视图,这个结果视图是用一个ActionForward对象来表示的,actionforward对象通过struts-config.xml配置文件中的配置关联到某个jsp页面,因为程序中使用的是在struts-config.xml配置文件为jsp页面设置的逻辑名,这样可以实现action程序代码与返回的jsp页面名称的解耦。
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Struts 2的工作流程与WebWork框架基本相同,所以说Struts2是WebWork的升级版本。
基本简要流程如下:1、客户端浏览器发出HTTP请求。2、根据web.xml配置,该请求被FilterDispatcher接收。3、根据struts.xml配置,找到需要调用的Action类和方法,并通过IoC方式,将值注入给Aciton。4、Action调用业务逻辑组件处理业务逻辑,这一步包含表单验证。5、Action执行完毕,根据struts.xml中的配置找到对应的返回结果result,并跳转到相应页面。6、返回HTTP响应到客户端浏览器。
****************************************************************************************************
一个请求在Struts2框架中的处理大概分为以下几个步骤:
1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;
2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);
3、接着FilterDispatcher被调用,FilterDispatcher询问ActionMapper来决定这个请求是否需要调用某个Action;
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;
5、ActionProxy通过ConfigurationManager询问框架的配置文件,找到需要调用的Action类;
6、ActionProxy创建一个ActionInvocation的实例。
7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2框架中继承的标签。在这个过程中需要涉及到ActionMapper。
****************************************************************************************************
Struts 2框架本身大致可以分为3个部分:核心控制器FilterDispatcher、业务控制器Action和用户实现的企业业务逻辑组件。核心控制器 FilterDispatcher是Struts 2框架的基础,包含了框架内部的控制流程和处理机制。业务控制器Action和业务逻辑组件是需要用户来自己实现的。用户在开发Action和业务逻辑组件的同时,还需要编写相关的配置文件,供核心控制器FilterDispatcher来使用。
Struts2的体系与Struts1体系的差别非常大,因为Struts2使用了WebWork的设计核心,而不是Struts1的设计核心。Struts2中大量使用拦截器来处理用户的请求,从而允许用户的业务逻辑控制器与Servlet API分离。
Struts2框架的大概处理流程如下:
1、加载类(FilterDispatcher)
2、读取配置(struts配置文件中的Action)
3、派发请求(客户端发送请求)
4、调用Action(FilterDispatcher从struts配置文件中读取与之相对应的Action)
5、启用拦截器(WebWork拦截器链自动对请求应用通用功能,如验证)
6、处理业务(回调Action的execute()方法)
7、返回响应(通过execute方法将信息返回到FilterDispatcher)
8、查找响应(FilterDispatcher根据配置查找响应的是什么信息如:SUCCESS、ERROR,将跳转到哪个jsp页面)
9、响应用户(jsp--->客户浏览器端显示)
10、struts2标签库(相比struts1的标签库,struts2是大大加强了,对数据的操作功能很强大)
****************************************************************************************************
答:在struts.xml中就只需要配置一个action,在一个Action类中分发Action请求调用不同的方法。
****************************************************************************************************
ActionServlet继承自javax.servlet.http.HttpServlet类,其在Strutsframework中扮演的角色是中心控制器。它提供一个中心位置来处理全部的终端请求。控制器ActionServlet主要负责将HTTP的客户请求信息组装后,根据配置文件的指定描述,转发到适当的处理器。
按照Servelt的标准,所有得Servlet必须在web配置文件(web.xml)声明。同样,ActoinServlet必须在WebApplication配置文件(web.xml)中描述,有关配置信息如下。
全部的请求URI以*.do的模式存在并映射到这个servlet,其配置如下:
一个该模式的请求URI符合如下格式:http://www.my_site_name.com/mycontext/actionName.do
当用户向服务器端提交请求的时候,实际上信息是首先发送到控制器ActionServlet,一旦控制器获得了请求,其就会将请求信息传交给一些辅助类(help classes)处理。这些辅助类知道如何去处理与请求信息所对应的业务操作。在Struts中,这个辅助类就是org.apache.struts.action.Action。通常开发者需要自己继承Aciton类,从而实现自己的Action实例。
答:上传,重复提交,数字转换,表单认证,国际化,等等
****************************************************************************************************
答:1.首先要实现Interceptor(拦截器)接口 2.在配置文件中定义自己的拦截器。 3.最后还要把系统提供的默认拦截器加上,否则系统默认拦截器不起作用
****************************************************************************************************
答:实现TypeCoverter接口,或者继承DefaultTypeConverter实现类(该类实现了TypeCoverter接口),通过继承该实现类来实现自己的类型转换器。重写convertValue方法即可。
1.都是MVC的WEB框架,
2struts1的老牌框架,应用很广泛,有很好的群众基础,使用它开发风险很小,成本更低!struts2虽然基于这个框架,但是应用群众并多,相对不成熟,未知的风险和变化很多,开发人员相对不好招,使用它开发项目的风险系数更大,用人成本更高!
3.struts2毕竟是站在前辈的基础设计出来,它会改善和完善struts1中的一些缺陷,struts1中一些悬而未决问题在struts2得到了解决。
4.struts1的前端控制器是一个Servlet,名称为ActionServlet,struts2的前端控制器是一个filter,在struts2.0中叫FilterDispatcher,在struts2.1中叫StrutsPrepareAndExecuteFilter。
5.struts1的action需要继承Action类,struts2的action可以不继承任何类;struts1对同一个路径的所有请求共享一个Action实例,struts2对同一个路径的每个请求分别使用一个独立Action实例对象,所有对于struts2的Action不用考虑线程安全问题。
6.在struts1中使用formbean封装请求参数,在struts2中直接使用action的属性来封装请求参数。
7.struts1中的多个业务方法放在一个Action中时(即继承DispatchAction时),要么都校验,要么都不校验;对于struts2,可以指定只对某个方法进行校验,当一个Action继承了ActionSupport且在这个类中只编写了validateXxx()方法,那么则只对Xxx()方法进行校验。
(一个请求来了的执行流程进行分析,struts2是自动支持分模块开发,并可以不同模块设置不同的url前缀,这是通过package的namespace来实现的;struts2是支持多种类型的视图;struts2的视图地址可以是动态的,即视图的名称是支持变量方式的,举例,论坛发帖失败后回来还要传递boardid。视图内容显示方面:它的标签用ognl,要el强大很多,在国际化方面支持分模块管理,两个模块用到同样的key,对应不同的消息;)
与Struts1不同,Struts2对用户的每一次请求都会创建一个Action,所以Struts2中的Action是线程安全的。
给我印象最深刻的是:struts配置文件中的redirect视图的url不能接受参数,而struts2配置文件中的redirect视图可以接受参数。
答:1.使用向导添加struts2的组件;
2.使用向导添加spring2.5的组件;
struts.properties --指定有Struts2的action由spring容器管理
web.xml --指定spring容器配置文件
globalMessages.properties --非必须有
struts.xml --class属性配置改成spring容器内bean的ID
applicationContext.xml --action的作用域需要是原型
****************************************************************************************************
****************************************************************************************************
spring是一个集成了许多第三方框架的大杂烩,其核心技术是IOC(控制反转,也称依赖注入)和AOP(面向切面编程)
****************************************************************************************************
1.Spring实现了工厂模式的工厂类(在这里有必要解释清楚什么是工厂模式),这个类名为BeanFactory(实际上是一个接口),在程序中通常BeanFactory的子类ApplicationContext。Spring相当于一个大的工厂类,在其配置文件中通过
2.Spring提供了对IOC(控制反转)良好支持,IOC是一种编程思想,是一种架构艺术,利用这种思想可以很好地实现模块之间的解耦。IOC也称为DI(Depency Injection),什么叫依赖注入呢?
譬如,Class Programmer
{
Computercomputer = null;
publicvoid code()
{
//Computercomputer = new IBMComputer();
//Computercomputer = beanfacotry.getComputer();
computer.write();
}
publicvoid setComputer(Computer computer)
{
this.computer= computer;
}
}
另外两种方式都由依赖,第一个直接依赖于目标类,第二个把依赖转移到工厂上,第三个彻底与目标和工厂解耦了。在spring的配置文件中配置片段如下:
3. Spring提供了对AOP技术的良好封装, AOP称为面向切面编程,就是系统中有很多各不相干的类的方法,在这些众多方法中要加入某种系统功能的代码,例如,加入日志,加入权限判断,加入异常处理,这种应用称为AOP。实现AOP功能采用的是代理技术,客户端程序不再调用目标,而调用代理类,代理类与目标类对外具有相同的方法声明,有两种方式可以实现相同的方法声明,一是实现相同的接口,二是作为目标的子类在,JDK中采用Proxy类产生动态代理的方式为某个接口生成实现类,如果要为某个类生成子类,则可以用CGLI B。在生成的代理类的方法中加入系统功能和调用目标类的相应方法,系统功能的代理以Advice对象进行提供,显然要创建出代理对象,至少需要目标类和Advice类。spring提供了这种支持,只需要在spring配置文件中配置这两个元素即可实现代理和aop功能
请求的分发
请求首先到达DispatcherServlet,应用服务器会根据Web应用中web.xml文件定义的url映射将相应的请求分发到DispatcherServlet中
请求的处理
DispatcherServlet会查找相应的HandlerMapping接口的实现类,调用其中的方法:HandlerExecutionChaingetHandler(HttpServletRequest request) throws Exception,该方法会返回一个HandlerExecutionChain。返回的HandlerExecutionChain中包含了零个或者是多个Interceptor和一个处理请求的Handler。DispatcherServlet会调用Interceptor中的preHandle() 方法。然后处理Handler,这个Handler相当于Struts中Action,在SpringMVC中默认的实现是Controller接口,是具体处理请求的代码所驻留的地方。事实上HandlerExecutionChain中的getHandler()返回的是一个Object类型。DispatcherServlet不会直接调用getHandler()返回对象中的方法,DispatcherServlet会查找相应的HandlerAdapter,然后具体通过HandlerAdapter来调用getHandler()返回的handler对象中的方法。就是说我们可以实现自己的HandlerAdapter然后通过IoC注入到DispatcherServlet中,从而可以实现一套自定义的控制器。随后DispatcherServlet会调用Interceptor中的postHandle()方法。
答:beanfactory(容器),applicationContext(应用上下文)
答:AOP是面向方面,相当于拦截器,可以在需要的地方插入逻辑,所谓的面向方面就是把相同的功能抽象出来,
****************************************************************************************************
1.通知(Advice):
通知定义了切面是什么以及何时使用。描述了切面要完成的工作和何时需要执行这个工作。
2.连接点(Joinpoint):
程序能够应用通知的一个“时机”,这些“时机”就是连接点,例如方法被调用时、异常被抛出时等等。
3.切入点(Pointcut)
通知定义了切面要发生的“故事”和时间,那么切入点就定义了“故事”发生的地点,例如某个类或方法的名称,Spring中允许我们方便的用正则表达式来指定
4.切面(Aspect)
通知和切入点共同组成了切面:时间、地点和要发生的“故事”
5.引入(Introduction)
引入允许我们向现有的类添加新的方法和属性(Spring提供了一个方法注入的功能)
6.目标(Target)
即被通知的对象,如果没有AOP,那么它的逻辑将要交叉别的事务逻辑,有了AOP之后它可以只关注自己要做的事(AOP让他做爱做的事)
7.代理(proxy)
应用通知的对象,详细内容参见设计模式里面的代理模式
8.织入(Weaving)
把切面应用到目标对象来创建新的代理对象的过程,织入一般发生在如下几个时机:
(1)编译时:当一个类文件被编译时进行织入,这需要特殊的编译器才可以做的到,例如AspectJ的织入编译器
(2)类加载时:使用特殊的ClassLoader在目标类被加载到程序之前增强类的字节代码
(3)运行时:切面在运行的某个时刻被织入,SpringAOP就是以这种方式织入切面的,原理应该是使用了JDK的动态代理技术
****************************************************************************************************
答:有setter注入(即类中设置一个全局属性,并对属性有setter方法,以供容器注入),接口注入和 构造器注入
依赖注入是Spring IOC的主要作用,依赖就是指属性,意思就是说将属性利用Spring注入到程序中,而非new。
答: 有两种方式:基于XML配置文件的方式;另一个是在业务方法上进行@Transactional注解,将事务规则应用到业务逻辑中。
****************************************************************************************************
答:singleton(单例) ,prototype(原型) ,request,session,globalsession
****************************************************************************************************
原理: 1.读取并解析配置文件 2.读取并解析映射信息,创建SessionFactory 3.打开Session 4.创建事务Transation 5.持久化操作 6.提交事务 7.关闭Session 8.关闭SesstionFactory
为什么要用: 1. 对JDBC访问数据库的代码做了封装,大大简化了数据访问层繁琐的重复性代码。 2.hibernate是一个基于JDBC的主流持久化框架,是一个优秀的ORM(对象关系映射)实现。他很大程度的简化DAO层的编码工作 3. hibernate使用Java反射机制,而不是字节码增强程序来实现透明性。 4. hibernate的性能非常好,因为它是个轻量级框架。映射的灵活性很出色。它支持各种关系数据库,从一对一到多对多的各种复杂关系。
.使用Hibernate时,先要配置hibernate.cfg.xml文件,其中配置数据库连接信息和方言等,还要为每个实体配置相应的hbm.xml文件,hibernate.cfg.xml文件中需要登记每个hbm.xml文件。
配置Configuration对象、产生SessionFactory、创建session对象,启动事务,完成CRUD操作,提交事务,关闭session。
答:sql是关系数据库查询语言,面对的数据库;而hql是Hibernate这样的数据库持久化框架提供的内置查询语言,虽然他们的目的都是为了从数据库查询需要的数据,但sql操作的是数据库表和字段,而作为面向对象的hql操作的则是持久化类及其属性,
****************************************************************************************************
答:Hibernate三种状态的区分,以及save,update,saveOrUpdHibernate的对象有3种状态,
分别为:瞬时态(Transient)、持久态(Persistent)、脱管态(Detached)。
1.瞬时对象在内存孤立存在,它是携带信息的载体,不和数据库的数据有任何关联关系,在Hibernate中,可通过session的save()或saveOrUpdate()方法将瞬时对象与数据库相关联,并将数据对应的插入数据库中,此时该瞬时对象转变成持久化对象。
2.持久对象具有如下特点: 1. 和session实例关联; 2. 在数据库中有与之关联的记录。 3. 比瞬时对象多了一个数据库记录标识值。
3.托管态,也叫游离态等,持久化对象脱离了Session的对象。如Session缓存被清空的对象。特点:已经持久化,但不在Session缓存中。处于此状态的对象叫游离对象。
****************************************************************************************************
答:延迟加载机制是为了避免一些无谓的性能开销而提出来的,所谓延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。在Hibernate中提供了对实体对象的延迟加载以及对集合的延迟加载,另外在Hibernate3中还提供了对属性的延迟加载。
解决方案一,按照Object[]数据取出数据,然后自己组bean
解决方案二,对每个表的bean写构造函数,比如表一要查出field1,field2两个字段,那么有一个构造函数就是Bean(type1filed1,type2
field2),然后在hql里面就可以直接生成这个bean了。
第一个:查询出来的list 转换为一个数组
也就是说 Object[] objects=(Object[]) list -- 你查询出来的list 集合
for(object item :objects ){}进行自行封装处理
第二个说白了就是写个构造函数让他自己封装省了自己处理过程了
但是如果是查询出来的多张表数据 还是得按第一种方式自己处理的。。。
(1)缓存就是把以前从数据库中查询出来和使用过的对象保存在内存中(一个数据结构中),这个数据结构通常是或类似Hashmap,当以后要使用某个对象时,先查询缓存中是否有这个对象,如果有则使用缓存中的对象,如果没有则去查询数据库,并将查询出来的对象保存在缓存中,以便下次使用。
(2)Hibernate的Session就是一种缓存,我们通常将之称为Hibernate的一级缓存,当想使用session从数据库中查询出一个对象时,Session也是先从自己内部查看是否存在这个对象,存在则直接返回,不存在才去访问数据库,并将查询的结果保存在自己内部。由于Session代表一次会话过程,一个Session与一个数据库连接相关连,所以Session最好不要长时间保持打开,通常仅用于一个事务当中,在事务结束时就应关闭。并且Session是线程不安全的,被多个线程共享时容易出现问题。通常只有那种全局意义上的缓存才是真正的缓存应用,才有较大的缓存价值,因此,Hibernate的Session这一级缓存的缓存作用并不明显,应用价值不大。Hibernate的二级缓存就是要为Hibernate配置一种全局缓存,让多个线程和多个事务都可以共享这个缓存。我们希望的是一个人使用过,其他人也可以使用,session没有这种效果。
(3)二级缓存是独立于Hibernate的软件部件,属于第三方的产品,多个厂商和组织都提供有缓存产品,例如,EHCache和OSCache等等。在Hibernate中使用二级缓存,首先就要在hibernate.cfg.xml配置文件中配置使用哪个厂家的缓存产品,接着需要配置该缓存产品自己的配置文件,最后要配置Hibernate中的哪些实体对象要纳入到二级缓存的管理中。明白了二级缓存原理和有了这个思路后,很容易配置起Hibernate的二级缓存。扩展知识:一个SessionFactory可以关联一个二级缓存,也即一个二级缓存只能负责缓存一个数据库中的数据,当使用Hibernate的二级缓存后,注意不要有其他的应用或SessionFactory来更改当前数据库中的数据,这样缓存的数据就会与数据库中的实际数据不一致。
Hibernate就是封装了JDBC,他可以写一条hql语句,可以再不同数据库中使用,不用修改hql语句,但是关联查询效率低。JDBC是基础的链接数据库的框架,效率高,但是mysql、oracle、sql service等不同的数据库要写不同的sql语句。one to many比如Class和Student吧就是一个班级对应多个学生在Class类中追加集合属性 Setstudents;在Class的配置文件中追加(Class.hbm.xml) 将Studnet中的classId属性去掉换成 Class class;在Student的配置文件中(Student.hbm.xml)
struts 在 SSH 框架中起控制的作用 , 其核心是 (控制器)Controller, 即ActionServlet, 而 ActionServlet 的核心就是 Struts-config.xml. 主要控制逻辑关系的处理 . hibernate 是数据持久化层 , 是一种新的对象、关系的映射工具 , 提供了从 Java 类到数据表的映射,也提供了数据查询和恢复等机制 , 大大减少数据访问的复杂度。把对数据库的直接操作 , 转换为对持久对象的操作 .
spring 是一个轻量级的控制反转 (IoC) 和面向切面 (AOP) 的容器框架 , 面向接口的编程 , 由容器控制程序之间的(依赖)关系,而非传统实现中,由程序代码直接操控。这也就是所谓“ 控制反转 ” 的概念所在:(依赖)控制权由应用代码中转到了外部容器,控制权的转移,是所谓反转。依赖注入,即组件之间的依赖关系由容器在运行期决定,形象的来说,即由容器动态的将某种依赖关系注入到组件之中 起到的主要作用是解耦
****************************************************************************************************
把hibernate的配置写到spring的配置中,用spring管理和调用hibernate的工厂和session等。
struts的话,通常有2中。一种是用spring中的一个工厂类代替struts的工厂类去生成action,并且用spring管理。 另一种是,struts 用自己的工厂生成action,但是由spring管理。这样耦合低一些。
大概就是这样,hibernate负责它最擅长的数据库管理。 struts页面的请求处理调用相应的底层数据库等。spring负责管理他们两个。
****************************************************************************************************
整合SSH的三种方法
方法一:直接获得ApplicationContext对象
这种方法通过spring获取与Hibernate相关联的对象。
关键是在Action类中获得ApplicationContext对象,然后通过getBean方法获得JavaBean对象。
(1)在hibernate.cfg.xml文件中配置session-factory
jdbc:MySQL://localhost/test?characterEncoding=UTF8
org.hibernate.dialect.MySQLDialect
com.mysql.jdbc.Driver
(2)在applicationContext.xml文件中对hibernate相关联的对象使用依赖注入,使用Spring的HibernateTemplate类来对Hibernate的Session进行操作
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
class="org.springframework.transaction.interceptor.TransactionInterceptor">
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
(3)配置struts.xml文件
(4)在Action中获取userService并使用
ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(request.getSession().getServletContext());
UserService userService = (UserServiceImpl) applicationContext.getBean("userService");
userService.addUser(user);
第一种方法的缺点:将Action类鱼Spring的装配JavaBean绑定的过紧,增加了其耦合度,不适合装配文件有变化的情况。
方法二:由Spring创建Action类的对象
在方法一的基础之上需要做如下几步修改
(1)在Action方法中增加setUserService的方法,把UserService交给spring处理
private UserService userService;
public void setUserService(UserServiceuserService) {
this.userService = userService;
}
(2)在application.xml文件中对action进行配置
(3)在struts.xml文件中配置action
第二种方法的缺点:虽然使用了Spring的IOC特性,对Action和ApplicationContext对象进行了解耦,但配置一个Action需要同时维护两份文件:struts.xml和application.xml,比较难维护。
方法三:直接使用Spring的自动装配JavaBean的特性进行整合
在方法一的配置基础上进行如下修改
(1)编写UserAction类,在UserAction中添加setUserService方法,让spring自动装配
private UserService userService;
public void setUserService(UserServiceuserService) {
this.userService = userService;
}
(2)直接在Struts中配置LoginAction
由于已经在application.xml文件中装配了userService属性,这样spring就可以自动装配LoginAction中的UserService了。
*****************************************************************************************************************************
总共23种,分为三大类:创建型,结构型,行为型
我只记得其中常用的6、7种,分别是:
创建型(工厂、工厂方法、抽象工厂、单例)
结构型(包装、适配器,组合,代理)
行为(观察者,模版,策略)
然后再针对你熟悉的模式谈谈你的理解即可。
工厂模式是一种经常被使用到的模式,根据工厂模式实现的类可以根据提供的数据生成一组类中某一个类的实例,通常这一组类有一个公共的抽象父类并且实现了相同的方法,但是这些方法针对不同的数据进行了不同的操作。首先需要定义一个基类,该类的子类通过不同的方法实现了基类中的方法。然后需要定义一个工厂类,工厂类可以根据条件生成不同的子类实例。当得到子类的实例后,开发人员可以调用基类中的方法而不必考虑到底返回的是哪一个子类的实例。
在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow