在本文中将总结 Java Web 开发技术和相关框架的核心知识。因框架知识体系比较庞大,具体每个框架的使用我将放在 ../JavaWeb
这个目录下,包含 Spring、Strust2、Hibernate、Spring Boot 等框架。
在面试指南中将列举面试中常见的考点,包含Servlet、JSP、Spring、中间件等常考Java Web框架知识
参考资料:
Servlet 是在服务器上运行的小程序。一个 servlet 就是一个 Java 类,并且可以通过 “请求—响应” 编程模式来访问的这个驻留在服务器内存里的 servlet 程序。
类的继承关系如下:
Servlet三种实现方式:
实现javax.servlet.Servlet接口
继承javax.servlet.GenericServlet类
继承javax.servlet.http.HttpServlet类
通常会去继承HttpServlet类来完成Servlet。
Tomcat的容器分为4个等级,Servlet的容器管理Context容器,一个Context对应一个Web工程。
主要描述了从浏览器到服务器,再从服务器到浏览器的整个执行过程
浏览器向服务器请求时,服务器不会直接执行我们的类,而是到 web.xml 里寻找路径名 ① 浏览器输入访问路径后,携带了请求行,头,体 ② 根据访问路径找到已注册的 servlet 名称 ③ 根据映射找到对应的 servlet 名 ④ 根据根据 servlet 名找到我们全限定类名,既我们自己写的类
① 服务器找到全限定类名后,通过反射创建对象,同时也创建了 servletConfig,里面存放了一些初始化信息(注意服务器只会创建一次 servlet 对象,所以 servletConfig 也只有一个)
① 对象创建好之后,首先要执行 init 方法,但是我们发现我们自定义类下没有 init 方法,所以程序会到其父类 HttpServlet 里找 ② 我们发现 HttpServlet 里也没有 init 方法,所以继续向上找,既向其父类 GenericServlet 中继续寻找,在 GenericServlet 中我们发现了 init 方法,则执行 init 方法(对接口 Servlet 中的 init 方法进行了重写)
注意: 在 GenericServlet 中执行 public void init(ServletConfig config) 方法的时候,又调用了自己无参无方法体的 init() 方法,其目的是为了方便开发者,如果开发者在初始化的过程中需要实现一些功能,可以重写此方法。
接着,服务器会先创建两个对象:ServletRequest 请求对象和 ServletResponse 响应对象,用来封装浏览器的请求数据和封装向浏览器的响应数据 ① 接着服务器会默认在我们写的类里寻找 service(ServletRequest req, ServletResponse res) 方法,但是 DemoServlet 中不存在,那么会到其父类中寻找 ② 到父类 HttpServlet 中发现有此方法,则直接调用此方法,并将之前创建好的两个对象传入 ③ 然后将传入的两个参数强转,并调用 HttpServlet 下的另外个 service 方法 ④ 接着执行 service(HttpServletRequest req, HttpServletResponse resp)
方法,在此方法内部进行了判断请求方式,并执行doGet和doPost,但是doGet和doPost方法已经被我们自己重写了,所以会执行我们重写的方法 看到这里,你或许有疑问:为什么我们不直接重写service方法? 因为如果重写service方法的话,我们需要将强转,以及一系列的安全保护判断重新写一遍,会存在安全隐患
void init(ServletConfig servletConfig)
:Servlet对象创建之后马上执行的初始化方法,只执行一次;void service(ServletRequest servletRequest, ServletResponse servletResponse)
:每次处理请求都是在调用这个方法,它会被调用多次;void destroy()
:在Servlet被销毁之前调用,负责释放 Servlet 对象占用的资源的方法;特性:
Servlet 类由自己编写,但对象由服务器来创建,并由服务器来调用相应的方法
服务器启动时 ( web.xml中配置load-on-startup=1
,默认为0 ) 或者第一次请求该 servlet 时,就会初始化一个 Servlet 对象,也就是会执行初始化方法 init(ServletConfig conf)
该 servlet 对象去处理所有客户端请求,在 service(ServletRequest req,ServletResponse res)
方法中执行
最后服务器关闭时,才会销毁这个 servlet 对象,执行 destroy() 方法。
总结(面试会问):
1)Servlet何时创建
默认第一次访问servlet时创建该对象(调用init()方法)
2)Servlet何时销毁
服务器关闭servlet就销毁了(调用destroy()方法)
3)每次访问必须执行的方法
public void service(ServletRequest arg0, ServletResponse arg1)
<servlet></servlet>
之间添加以下代码:<load-on-startup>1</load-on-startup>
其中,数字越小表示优先级越高。
例如:我们在 web.xml 中设置 TestServlet2 的优先级为 1,而 TestServlet1 的优先级为 2,启动和关闭Tomcat:优先级高的先启动也先关闭。
客户端首次向某个Servlet发送请求
Servlet 类被修改后,Tomcat 容器会重新装载 Servlet。
本节参考:《Java程序员面试笔试宝典》P172
在设计 Web 应用程序时,经常需要把一个系统进行结构化设计,即按照模块进行划分,让不同的 Servlet 来实现不同的功能,例如可以让其中一个 Servlet 接收用户的请求,另外一个 Servlet 来处理用户的请求。为了实现这种程序的模块化,就需要保证在不同的 Servlet 之间可以相互跳转,而 Servlet 中主要有两种实现跳转的方式:forward 与 redirect 方式。
forward 是服务器内部的重定向,服务器直接访问目标地址的 URL,把那个 URL 的响应内容读取过来,而客户端并不知道,因此在客户端浏览器的地址栏中不会显示转向后的地址,还是原来的地址。由于在整个定向的过程中用的是同一个 Request,因此 forward 会将 Request 的信息带到被定向的 JSP 或 Servlet 中使用。
redirect 则是客户端的重定向,是完全的跳转,即客户端浏览器会获取到跳转后的地址,然后重新发送请求,因此浏览器中会显示跳转后的地址。同事,由于这种方式比 forward 方式多了一次网络请求,因此其效率要低于 forward 方式。需要注意的是,客户端的重定向可以通过设置特定的 HTTP 头或改写 JavaScript 脚本实现。
下图可以更好的说明二者的区别:
鉴于以上的区别,一般当 forward 方式可以满足需求时,尽可能地使用 forward 方式。但在有些情况下,例如,需要跳转到下一个其他服务器上的资源,则必须使用 redirect 方式。
引申:filter的作用是什么?主要实现什么方法?
filter 使用户可以改变一个 request 并且修改一个 response。filter 不是一个 Servlet,它不能产生一个 response,但它能够在一个 request 到达 Servlet 之前预处理 request,也可以在离开 Servlet 时处理 response。filter 其实是一个 “Servlet Chaining” (Servler 链)。
一个 filter 的作用包括以下几个方面:
1)在 Servlet 被调用之前截获
2)在 Servlet 被调用之前检查 Servlet Request
3)根据需要修改 Request 头和 Request 数据
4)根据需要修改 Response 头和 Response 数据
5)在 Servlet 被调用之后截获
1、不同之处在哪?
2、各自的特点
3、通过MVC双剑合璧
既然 JSP 和 Servlet 都有自身的适用环境,那么能否扬长避短,让它们发挥各自的优势呢?答案是肯定的——MVC(Model-View-Controller)模式非常适合解决这一问题。
MVC模式(Model-View-Controller)是软件工程中的一种软件架构模式,把软件系统分为三个基本部分:模型(Model)、视图(View)和控制器(Controller):
在 JSP/Servlet 开发的软件系统中,这三个部分的描述如下所示:
MVC 模式在 Web 开发中的好处是非常明显,它规避了 JSP 与 Servlet 各自的短板,Servlet 只负责业务逻辑而不会通过 out.append() 动态生成 HTML 代码;JSP 中也不会充斥着大量的业务代码。这大大提高了代码的可读性和可维护性。
Tomcat是Web应用服务器,是一个Servlet/JSP容器。Tomcat 作为 Servlet 容器,负责处理客户请求,把请求传送给Servlet,并将Servlet的响应传送回给客户。而 Servlet 是一种运行在支持 Java 语言的服务器上的组件。Servlet最常见的用途是扩展 Java Web 服务器功能,提供非常安全的,可移植的,易于使用的CGI替代品。
从 http 协议中的请求和响应可以得知,浏览器发出的请求是一个请求文本,而浏览器接收到的也应该是一个响应文本。但是在上面这个图中,并不知道是如何转变的,只知道浏览器发送过来的请求也就是 request,我们响应回去的就用 response。忽略了其中的细节,现在就来探究一下。
① Tomcat 将 http 请求文本接收并解析,然后封装成 HttpServletRequest 类型的 request 对象,所有的 HTTP 头数据读可以通过 request 对象调用对应的方法查询到。
② Tomcat 同时会要响应的信息封装为 HttpServletResponse 类型的 response 对象,通过设置 response 属性就可以控制要输出到浏览器的内容,然后将 response 交给 tomcat,tomcat 就会将其变成响应文本的格式发送给浏览器
Java Servlet API 是 Servlet 容器(tomcat) 和 servlet 之间的接口,它定义了 serlvet 的各种方法,还定义了 Servlet 容器传送给 Servlet 的对象类,其中最重要的就是 ServletRequest 和 ServletResponse。所以说我们在编写 servlet 时,需要实现 Servlet 接口,按照其规范进行操作。
类似这种面试题,实际上都属于“开放性”问题,你扯到哪里都可以。不过如果我是面试官的话,我还是希望对方能做到一点——不要混淆 session 和 session 实现。
本来 session 是一个抽象概念,开发者为了实现中断和继续等操作,将 user agent 和 server 之间一对一的交互,抽象为“会话”,进而衍生出“会话状态”,也就是 session 的概念。
而 cookie 是一个实际存在的东西,http 协议中定义在 header 中的字段。可以认为是 session 的一种后端无状态实现。
而我们今天常说的 “session”,是为了绕开 cookie 的各种限制,通常借助 cookie 本身和后端存储实现的,一种更高级的会话状态实现。
所以 cookie 和 session,你可以认为是同一层次的概念,也可以认为是不同层次的概念。具体到实现,session 因为 session id 的存在,通常要借助 cookie 实现,但这并非必要,只能说是通用性较好的一种实现方案。
引申
由于 HTTP 协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是 Session。典型的场景比如购物车,当你点击下单按钮时,由于 HTTP 协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的 Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个 Session 是保存在服务端的,有一个唯一标识。在服务端保存Session 的方法很多,内存、数据库、文件都有。集群的时候也要考虑 Session 的转移,在大型的网站,一般会有专门的 Session 服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如 Memcached 之类的来放 Session。
思考一下服务端如何识别特定的客户?
这个时候 Cookie 就登场了。每次 HTTP 请求的时候,客户端都会发送相应的 Cookie 信息到服务端。实际上大多数的应用都是用 Cookie 来实现 Session 跟踪的,第一次创建 Session 的时候,服务端会在 HTTP 协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话 ID 发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次 HTTP 交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
Cookie 其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到 Cookie 里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是 Cookie 名称的由来,给用户的一点甜头。
所以,总结一下:
做企业应用开发时,经常采用三层架构分层:表示层、业务层、持久层。表示层负责接收用户请求、转发请求、显示数据等;业务层负责组织业务逻辑;持久层负责持久化业务对象。
这三个分层,每一层都有不同的模式,就是架构模式。表示层最常用的架构模式就是MVC。
因此,MVC 是三层架构中表示层最常用的架构模式。
MVC 是客户端的一种设计模式,所以他天然就不考虑数据如何存储的问题。作为客户端,只需要解决用户界面、交互和业务逻辑就好了。在 MVC 模式中,View 负责的是用户界面,Controller 负责交互,Model 负责业务逻辑。至于数据如何存储和读取,当然是由 Model 调用服务端的接口来完成。
在三层架构中,并没有客户端/服务端的概念,所以表示层、业务层的任务其实和 MVC 没什么区别,而持久层在 MVC 里面是没有的。
各层次的关系:表现层的控制->服务层->数据持久化层。
参考资料:
可以总结为一句话:REST 是所有 Web 应用都应该遵守的架构设计指导原则。 Representational State Transfer,翻译是”表现层状态转化”。 面向资源是 REST 最明显的特征,对于同一个资源的一组不同的操作。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。REST要求,必须通过统一的接口来对资源执行各种操作。对于每个资源只能执行一组有限的操作。(7个HTTP方法:GET/POST/PUT/DELETE/PATCH/HEAD/OPTIONS)
符合REST架构设计的API。
以豆瓣网为例
应该尽量将 API 部署在专用域名之下 http://api.douban.com
/v2/user/1000001?apikey=XXX
应该将 API 的版本号放入URL http://api.douban.com/v2
/user/1000001?apikey=XXX
在 RESTful 架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词
,而且所用的名词往往与数据库的表格名对应
。一般来说,数据库中的表都是同种记录的”集合”(collection),所以 API 中的名词也应该使用复数。 http://api.douban.com/v2/book
/:id (获取图书信息) http://api.douban.com/v2/movie
/subject/:id (电影条目信息) http://api.douban.com/v2/music
/:id (获取音乐信息) http://api.douban.com/v2/event
/:id (获取同城活动)
对于资源的具体操作类型,由HTTP动词表示。常用的HTTP动词有下面四个(对应增/删/改/查
)。 GET(select
):从服务器取出资源(一项或多项)。 eg. 获取图书信息 GET
http://api.douban.com/v2/book/:id\
POST(create
):在服务器新建一个资源。 eg. 用户收藏某本图书 POST
http://api.douban.com/v2/book/:id/collection
PUT(update
):在服务器更新资源(客户端提供改变后的完整资源)。 eg. 用户修改对某本图书的收藏 PUT
http://api.douban.com/v2/book/:id/collection
DELETE(delete
):从服务器删除资源。 eg. 用户删除某篇笔记 DELETE
http://api.douban.com/v2/book/annotation/:id
如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果
?limit=10:指定返回记录的数量 eg. 获取图书信息 GET
http://api.douban.com/v2/book/:id?limit=10
服务器向用户返回的状态码和提示信息 每个状态码代表不同意思, 就像代号一样
2系 代表正常返回
4系 代表数据异常
5系 代表服务器异常
Spring的IoC容器是Spring的核心,Spring AOP是spring框架的重要组成部分
我的理解
Spring IOC实现原理
优点
我的理解
Spring AOP实现原理
动态代理(利用反射和动态编译将代理模式变成动态的)
JDK的动态代理
cglib动态代理
优点
DI(依赖注入)
IOC容器的初始化分为三个过程实现:
更详细说明请阅读:2 IOC容器初始化过程 - CSDN博客
参考资料:
AOP(Aspect Oriented Programming )称为面向切面编程,扩展功能不是修改源代码实现,在程序开发中主要用来解决一些系统层面上的问题,比如日志,事务,权限等待,Struts2的拦截器设计就是基于AOP的思想,是个比较经典的例子。
Struts2拦截器浅析-慕课网 https://www.imooc.com/learn/450
事务管理可以帮助我们保证数据的一致性,对应企业的实际应用很重要。
Spring的事务机制包括声明式事务和编程式事务。
声明式事务管理使用了AOP面向切面编程实现的,本质就是在目标方法执行前后进行拦截。在目标方法执行前加入或创建一个事务,在执行方法执行后,根据实际情况选择提交或是回滚事务。
Spring事务管理主要包括3个接口,Spring的事务主要是由它们(PlatformTransactionManager,TransactionDefinition,TransactionStatus)三个共同完成的。
1. PlatformTransactionManager:事务管理器–主要用于平台相关事务的管理
主要有三个方法:
2. TransactionDefinition:事务定义信息–用来定义事务相关的属性,给事务管理器PlatformTransactionManager使用
这个接口有下面四个主要方法:
事务管理器能够根据这个返回值进行优化,这些事务的配置信息,都可以通过配置文件进行配置。
3. TransactionStatus:事务具体运行状态–事务管理过程中,每个时间点事务的状态信息。
例如它的几个方法:
声明式事务的优缺点:
http://blog.csdn.net/jie_liang/article/details/77600742
【Spring】详解Spring中Bean的加载 - weknow619 - 博客园 https://www.cnblogs.com/weknow619/p/6673667.html
在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。
相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。正确理解Spring bean的生命周期非常重要,因为你或许要利用Spring提供的扩展点来自定义bean的创建过程。下图展示了bean装载到Spring应用上下文中的一个典型的生命周期过程。
上图bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制
正如你所见,在bean准备就绪之前,bean工厂执行了若干启动步骤。我们对上图进行详细描述:
Spring 对 Bean 进行实例化;
Spring 将值和 Bean 的引用注入进 Bean 对应的属性中;
如果Bean实现了 BeanNameAware 接口,Spring 将 Bean 的 ID 传递给setBeanName()方法
如果Bean实现了BeanFactoryAware接口,Spring将调用setBeanDactory(BeanFactory bf)方法并把BeanFactory容器实例作为参数传入。
如果Bean实现了ApplicationContextAwaer接口,Spring容器将调用setApplicationContext(ApplicationContext ctx)方法,将bean所在的应用上下文的引用传入进来
如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessBeforeInitialization(预初始化)方法
如果Bean实现了InitializingBean接口,Spring将调用它们的afterPropertiesSet方法,作用与在配置文件中对Bean使用init-method声明初始化的作用一样,都是在Bean的全部属性设置成功后执行的初始化方法。
如果Bean实现了BeanPostProcess接口,Spring将调用它们的postProcessAfterInitialization(后初始化)方法
经过以上的工作后,Bean将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁
如果Bean实现了DispostbleBean接口,Spring将调用它的destory方法,作用与在配置文件中对Bean使用destory-method属性的作用一样,都是在Bean实例销毁前执行的方法。
是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。
BeanFactory 延迟实例化的优点:
应用启动的时候占用资源很少,对资源要求较高的应用,比较有优势;
缺点:速度会相对来说慢一些。而且有可能会出现空指针异常的错误,而且通过bean工厂创建的bean生命周期会简单一些
ApplicationContext 不延迟实例化的优点:
缺点:把费时的操作放到系统启动中完成,所有的对象都可以预加载,缺点就是消耗服务器的内存
除了提供BeanFactory所支持的所有功能外,ApplicationContext还有额外的功能
由于ApplicationContext会预先初始化所有的Singleton Bean,于是在系统创建前期会有较大的系统开销,但一旦ApplicationContext初始化完成,程序后面获取Singleton Bean实例时候将有较好的性能。
也可以为bean设置lazy-init属性为true,即Spring容器将不会预先初始化该bean。
一般拦截器都是实现HandlerInterceptor,其中有三个方法preHandle、postHandle、afterCompletion
不同项目使用不同分模块策略,spring配置文件分为
PS:可以借鉴Servlet的生命周期,实例化、初始init、接收请求service、销毁destroy;
Spring上下文中的Bean也类似,【Spring上下文的生命周期】
注意:以上工作完成以后就可以用这个Bean了,那这个Bean是一个single的,所以一般情况下我们调用同一个ID的Bean会是在内容地址相同的实例
以上10步骤可以作为面试或者笔试的模板,另外这里描述的是应用Spring上下文Bean的生命周期,如果应用Spring的工厂也就是BeanFactory的话去掉第5步就Ok了;
@Resource和@Autowired都是做bean的注入时使用,其实@Resource并不是Spring的注解,它的包是javax.annotation.Resource,需要导入,但是Spring支持该注解的注入。
1、共同点
两者都可以写在字段和setter方法上。两者如果都写在字段上,那么就不需要再写setter方法。
2、不同点
(1)@Autowired
@Autowired为Spring提供的注解,需要导入包org.springframework.beans.factory.annotation.Autowired;只按照byType注入。
public class TestServiceImpl {
// 下面两种@Autowired只要使用一种即可
@Autowired
private UserDao userDao; // 用于字段上
@Autowired
public void setUserDao(UserDao userDao) { // 用于属性的方法上
this.userDao = userDao;
}
}
@Autowired注解是按照类型(byType)装配依赖对象,默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果我们想使用按照名称(byName)来装配,可以结合@Qualifier注解一起使用。如下:
public class TestServiceImpl {
@Autowired
@Qualifier("userDao")
private UserDao userDao;
}
(2)@Resource
@Resource默认按照ByName自动注入,由J2EE提供,需要导入包javax.annotation.Resource。@Resource有两个重要的属性:name和type,而Spring将@Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不制定name也不制定type属性,这时将通过反射机制使用byName自动注入策略。
public class TestServiceImpl {
// 下面两种@Resource只要使用一种即可
@Resource(name="userDao")
private UserDao userDao; // 用于字段上
@Resource(name="userDao")
public void setUserDao(UserDao userDao) { // 用于属性的setter方法上
this.userDao = userDao;
}
}
注:最好是将@Resource放在setter方法上,因为这样更符合面向对象的思想,通过set、get去操作属性,而不是直接去操作属性。
@Resource装配顺序:
如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常。
如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配。
@Resource的作用相当于@Autowired,只不过@Autowired按照byType自动注入。
思考:spring怎么知道应该哪些Java类当初bean类处理?
答案:使用配置文件或者注解的方式进行标识需要处理的java类!
@Component :标准一个普通的spring Bean类。 @Repository:标注一个DAO组件类。 @Service:标注一个业务逻辑组件类。 @Controller:标注一个控制器组件类。
这些都是注解在平时的开发过程中出镜率极高,@Component、@Repository、@Service、@Controller实质上属于同一类注解,用法相同,功能相同,区别在于标识组件的类型。@Component可以代替@Repository、@Service、@Controller,因为这三个注解是被@Component标注的。如下代码
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {
String value() default "";
}
举例:
(1)当一个组件代表数据访问层(DAO)的时候,我们使用@Repository进行注解,如下
@Repository
public class HappyDaoImpl implements HappyDao{
private final static Logger LOGGER = LoggerFactory.getLogger(HappyDaoImpl .class);
public void club(){
//do something ,like drinking and singing
}
}1234567
(2)当一个组件代表业务层时,我们使用@Service进行注解,如下
@Service(value="goodClubService")
//使用@Service注解不加value ,默认名称是clubService
public class ClubServiceImpl implements ClubService {
@Autowired
private ClubDao clubDao;
public void doHappy(){
//do some Happy
}
}12345678910
(3)当一个组件作为前端交互的控制层,使用@Controller进行注解,如下
@Controller
public class HappyController {
@Autowired //下面进行讲解
private ClubService clubService;
// Control the people entering the Club
// do something
}
/*Controller相关的注解下面进行详细讲解,这里简单引入@Controller*/
3、总结注意点
<!-- 自动扫描指定包及其子包下的所有Bean类 -->
<context:component-scan base-package="org.springframework.*"/>
@Autowired:属于Spring 的org.springframework.beans.factory.annotation包下,可用于为类的属性、构造器、方法进行注值 @Resource:不属于spring的注解,而是来自于JSR-250位于java.annotation包下,使用该annotation为目标bean指定协作者Bean。
...
更详细请转向:Spring常用注解介绍【经典总结】 - CSDN博客
Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的:
Spring容器就是实例化和管理Bean的工厂
工厂模式隐藏了创建类的细节,返回值必定是接口或者抽象类,而不是具体的某个对象,工厂类根据条件生成不同的子类实例。当得到子类的实例后,就可以调用基类中的方法,不必考虑返回的是哪一个子类的实例。
这个很明显,在各种BeanFactory以及ApplicationContext创建中都用到了;
Spring通过配置文件,就可以管理所有的bean,而这些bean就是Spring工厂能产生的实例,因此,首先我们在Spring配置文件中对两个实例进行配置。
Spring默认将所有的Bean设置成 单例模式,即对所有的相同id的Bean的请求,都将返回同一个共享的Bean实例。这样就可以大大降低Java创建对象和销毁时的系统开销。
使用Spring将Bean设置称为单例行为,则无需自己完成单例模式。
| 可以通过singleton=“true | false” 或者 scope=“?”来指定 |
在Spring的Aop中,使用的Advice(通知)来增强被代理类的功能。Spring实现这一AOP功能的原理就使用代理模式(1、JDK动态代理。2、CGLib字节码生成技术代理。)对类进行方法级别的切面增强,即,生成被代理类的代理类, 并在代理类的方法前,设置拦截器,通过执行拦截器重的内容增强了代理方法的功能,实现的面向切面编程。
Spring实现了一种能够通过额外的方法调用完成任务的设计模式 - 代理设计模式,比如JdkDynamicAopProxy和Cglib2AopProxy。
代理设计模式的一个很好的例子是org.springframework.aop.framework.ProxyFactoryBean。该工厂根据Spring bean构建AOP代理。该类实现了定义getObject()方法的FactoryBean接口。此方法用于将需求Bean的实例返回给bean factory。在这种情况下,它不是返回的实例,而是AOP代理。在执行代理对象的方法之前,可以通过调用补充方法来进一步“修饰”代理对象(其实所谓的静态代理不过是在装饰模式上加了个要不要你来干动作行为而已,而不是装饰模式什么也不做就加了件衣服,其他还得由你来全权完成)。
定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。spring中Observer模式常用的地方是listener的实现。如ApplicationListener。
补充面试题:Spring里面的工厂模式和代理模式,IO中的装饰者模式,挑几个最熟的能讲讲思路和伪代码实现?
用过spring的朋友都知道spring的强大和高深,都觉得深不可测,其实当你真正花些时间读一读源码就知道它的一些技术实现其实是建立在一些最基本的技术之上而已;例如AOP(面向方面编程)的实现是建立在CGLib提供的类代理和jdk提供的接口代理,IOC(控制反转)的实现建立在工厂模式、Java反射机制和jdk的操作XML的DOM解析方式.
Spring MVC 的工作原理如下图:
组件及其作用
前端控制器 (DispatcherServlet)
接收请求,响应结果,相当于转发器,中央处理器。负责调用系统的其他模块来真正处理用户的请求。
有了DispatcherServlet减少了其他组件之间的耦合度
处理器映射器 (HandlerMapping)
作用:根据请求的 url 查找 Handler
处理器 (Handler)
注意:编写 Handler 时按照 HandlerAdapter 的要求去做,这样适配器才可以去正确执行 Handler
处理器适配器 (HandlerAdapter)
作用:按照特定规则(HandlerAdapter要求的规则)执行Handler。
视图解析器 (ViewResolver)
作用:进行视图解析,根据逻辑视图解析成真正的视图 (View)
视图 (View)
View 是一个接口实现类支持不同的 View 类型(jsp,pdf等等)
注意:只需要程序员开发,处理器和视图。
参考资料:
参考面经: