Python超级明星WEB框架Flask
Flask简介Flask是一个相对于Django而言轻量级的Web框架。和Django大包大揽不同,Flask建立于一系列的开源软件包之上,这其中 最主要的是WSGI应用开发库Werkzeug和模板引擎Jinja:策略 :werkzeug和Jinja这两个库和Flask一样,都是pocoo团队开发的。这 或许体现了pocoo与Django竞争时关于生态的一种策略,这种策略的自然 延伸是Flask框架中没有包含数据库方面的构件,无论ORM还是其他。关注点 :Flask是一个WSGI应用框架,这意味着我们进行Flask开发时,不需要 关注网络方面的操作,Flask应用的入口是封装过的网络请求包,出口是 网络响应,我们仅需要关注这个阶段内的处理逻辑。WSGI服务器 :Flask虽然内置了简单的WSGI服务器,但其性能仅仅适用于开发期的调试。 Flask官网推荐了多种WSGI服务器,实现方式从多进程到多线程到协程, 这方面的选择我们在本课程中将不涉及。REST适应性 :虽然Flask和Django一样,最初的出发点都是服务端的动态网页应用。但 Flask的设计使之也相当适用于面向资源的REST架构,在越来越移动化 并且单页应用越来越重要的WEB开发领域,这是Flask相对于Django相当 大的优势。Hello Flask编写一个基于Flask的hello world相当容易:1、导入Flask类from flask import FlaskFlask类是Flask框架的核心类,它实现了WSGI应用规范。2、创建Flask实例app = Flask(__name__)Flask构造函数的第一个参数指定一个引入名/importname。Flask框架 使用这个名字进行静态资源、模板、错误信息的定位。除非你清楚的理解它的 作用,通常情况下,我们总应该使用特殊变量_name。Flask实例是可调用的(具有call方法),这个实例可以直接对接 WSGI服务器。3、注册路由@route('/')
def index():
return 'Hello,Flask!'注册路由就是建立URL规则和处理函数之间的关联。Flask框架依赖于路由 完成HTTP请求的分发。路由中的函数被称为视图函数,其返回值将作为HTTP响应的正文内容。4、对接并启动WSGI服务器Flask封装了一个简单的开发用WSGI服务器,我们可以通过调用run() 启动服务器运行:app.run(host='0.0.0.0',port=80)概述路由是MVC架构的Web框架中相当重要的一个概念,也是本节课程的重点。顾名思意,路由就是在迷茫中找出一条路的意思。在Flask框架中,路由就表示为用户请求的URL找出其对应的处理函数之意。在本节课程,我们将主要从以下几个方面讲解Flask框架中的路由:如何为应用注册路由? 如何为路由指定其支持的HTTP方法? 如何匹配动态URL? 如何对URL中的变量类型进行过滤? 如何理解访问点/endpoint? 如何为应用设定静态路由? 如何避免硬编码指向其他视图的URL?注册路由在Flask应用中,路由是指用户请求的URL与视图函数之间的映射。Flask框架 根据HTTP请求的URL在路由表中匹配预定义的URL规则,找到对应的视图函数, 并将视图函数的执行结果返回WSGI服务器:可见路由表在Flask应用中处于相当核心的位置。路由表的内容是由应用开发者填充。route装饰器 :可以使用Flask应用实例的route装饰器将一个URL规则绑定到 一个视图函数上。例如,下面的示例将URL规则/test绑定到视图函数test()上:@app.route('/test')
def test():
return 'this is response'如果这个应用部署在主机ezhost.com的根目录下,那么当用户访问:http://pythontab.com/tesetFlask框架就会调用我们的test()函数,其返回结果就传递给WSGI服务器发送给访问者。add_url_rule() :另一种等价的写法是使用Flask应用实例的add_url_route()方法。 下面的示例注册了一个与前例相同的路由:def test():
return 'this is response'
app.add_url_route('/test',view_func=test)其实,route装饰器内部也是通过调用add_url_route()方法实现的路由注册。 但是显然,使用装饰器使代码看起来更优雅一些。为路由指定HTTP方法默认情况下,Flask路由仅支持HTTP的GET请求。可以使用methods关键字参数,在注册 路由时显式地声明视图方法支持的HTTP方法。例如,下面的示例将URL规则/auth绑定到视图函数v_auth(),这个路由仅支持POST方法:@app.route('/auth',methods=['POST'])
def v_auth():pass指定多种HTTP方法支持关键字参数methods的类型为list,因此可以同时指定多种HTTP方法。下面的示例中,使URL规则/user同时支持POST方法和GET方法:@app.route('/user',methods=['POST','GET'])
def v_users():
if request.method == 'GET':
return ... # 返回用户列表
if request.method == 'POST'
return ... #创建新用户这个特性使Flask非常易于开发REST架构的后台服务,而不仅仅局限于传统的动态网页。匹配动态URL有时我们需要将同一类URL映射到同一个视图函数处理,比如,使用同一个视图函数 来显示不同用户的个人档案。我们希望以下的URL都可以分发到同一个视图函数:在Flask中,可以将URL中的可变部分使用一对小括号<>声明为变量, 并为视图函数声明同名的参数:@app.route('/user/')
def v_user(uname):
return '%s\'s Profile' % uname在上面的示例中,URL规则中的表示这部分是可变的,Flask将提取用户请求的 URL中这部分的内容,并作为视图函数v_user()的uname参数进行调用。URL变量类型过滤考虑下面的示例,我们希望通过HTTP共享文件夹/var/readonly中的文件:/var /readonly /a.txt /b.txt /repo /c.txt /d.txt简单思考一下就有答案了。我们可以构造URL规则/file/,然后直接 读取文件内容返回给用户。注册如下的路由:@app.route('/file/')
def v_file(fname):
fullname = os.path.join('/var/readonly',fname)
f = open(fullname)
cnt = f.read()
f.close()
return cnt测试结果表明,/file/a.txt和/file/b.txt都没有问题,但是/file/repo/c.txt和 /file/repo/d.txt却会失败。这是因为,默认情况下,在URL规则中的变量被视为不包含/的字符串。/file/repo/c.txt 是没有办法匹配URL规则/file/的。可以使用内置的path转换器告诉Flask框架改变这一默认行为。path转换器允许 规则匹配包含/的字符串:@app.route('/file/')在Flask中,转换器/converter用来对从URL中提取的变量进行预处理,这个过程 发生在调用视图函数之前。Flask预置了四种转换器:string - 匹配不包含/的字符串,这是默认的转换器path - 匹配包含/的字符串int - 只有当URL中的变量是整型值时才匹配,并将变量转换为整型float - 只有当URL中的变量是浮点值时才匹配,并将变量转换为浮点型访问点/endpoint我们一直强调,路由的作用是根据请求的URL,找到对应的视图函数。这没错,但是在 Flask框架中,请求任务的分发并不是直接从用户请求的URL一步定位到视图函数, 两者之间隔着一个访问点/endpoint。以下面的代码为例,我们看Flask怎样实现请求的分发:@app.route('/home')
def home():pass在Flask内部使用两张表维护路由:url_map :维护URL规则和endpoint的映射view_functions :维护endpoint和视图函数的映射。以用户访问URL/home为例,Flask将首先利用url_map找到所请求URL对应的 endpoint,即访问点home,然后再利用view_functions表查找home这个访问点 对应的视图函数,最终匹配到函数home():默认访问点 :当我们使用route装饰器注册路由时,默认使用被装饰函数的 函数名(name)作为访问点,因此,你看到上面的表中,路由中的访问点为home。自定义访问点 :可以在使用route装饰器或调用add_url_rule()方法注册路由时,使用 endpoint关键字参数改变这一默认行为:@app.route('/home',endpoint='whocare')
def home():pass此时的两张路由表将变成这样:静态目录路由当创建应用实例时,Flask将自动添加一条静态目录路由,其访问点 始终被设置为static,URL规则默认被设置为/static,本地路径默认被 设置为应用文件夹下的static子文件夹:+------------------------------------------------------------+ | url rule | endpoint | view_function | | /static | static | Flask.send_static_file | +------------------------------------------------------------+ 如果你的应用目录如下:/app /web.py /static /main.css /jquery.min.js 那么启动应用后就可以通过URL/static/main.css访问static文件夹下的main.css了。除了访问点被固定为static,静态目录的URL规则和本地目录都是可以根据应用情况进行调整。改变默认的本地路径 :可以在创建应用对象时使用关键字参数static_folder改变 默认的静态文件夹。例如,你的静态文件都存放在应用下的assets目录下, 那么可以按如下的方式创建应用对象:app = Flask(name,static_folder='assets') 也可以使用一个绝对路径:app = Flask(name,static_folder='/var/www/static') 改变默认的本地路径并不会对路由表产生影响。改变默认的URL规则 : 如果不喜欢静态目录URL/static,也可以在创建应用 对象时使用关键字参数static_url_path换一个别的名字。下面的示例中,将应用下的assets文件夹注册为静态目录/assets:app = Flask(name,static_folder='assets',static_url_path='/assets') 当应用运行后,通过URL/assets/main.css就可以访问assets文件夹下的 main.css文件了。这时的路由表变化为:+------------------------------------------------------------+ | url | endpoint | view_function | | /assets | static | Flask.send_static_file | +------------------------------------------------------------+构造URL在一个实用的视图中,不可避免地存在指向其他视图的链接。在之前的课程示例中,我们 都是在视图函数中这样硬编码这些链接URL的:@app.route('/')
def v_index():
return 'tech'
@app.route('/tech')
def v_tech():pass大部分情况下这种硬编码URL是可以工作的。但如果这个应用被挂在WSGI服务器的一个 子路径下,比如:/app1,那么用户访问URL/tech是不会成功的,这时应当访问/app1/tech 才可以正确地路由到视图函数v_tech()。我们应当使用访问点让Flask框架帮我们计算链接URL。简单地给url_for()函数传入 一个访问点,它返回将是一个可靠的URL地址:@app.route('/')
def v_index():
print url_for('v_contacts') # /contact
return 'see console output!'
@app.route('/contact')
def v_contacts():pass
添加查询参数 : 使用关键字参数,可以在构造的URL中生成查询串。下面的调用将生成 /contact?format=json
@app.route('/')
def v_index():
print url_for('v_contacts',format='json')
return ''
@app.route('/contact')
def v_contacts():pass
添加URL变量 : 如果指定访问点对应的视图函数接收参数,那么关键字参数将生成对应的参数URL。下面的 示例将生成/contact/Julia?format=html:@app.route('/')
def v_index():
print url_for('v_contact',name='Julia',format='html')
return ''
@app.route('/contact/')
def v_contact(name):pass添加锚点 :使用_anchor关键字可以为生成的URL添加锚点。下面的示例将生成URL /contact#part2@app.route('/')
def v_index():
print url_for('v_contacts',_anchor='part2')
@app.route('/contact')
def v_contacts():pass外部URL : 默认情况下,url_for()生成站内URL,可以设置关键字参数_external 为True,生成包含站点地址的外部URL。下面的示例将生成URLhttp:///contacts:@app.route('/')
def v_index():
print url_for('v_contacts',_external=True)
@app.route('/contact')
def v_contacts():pass
为什么需要API,什么是api
什么是API接口?API是指应用程序编程接口,我们通过API接口可以实现特定的功能,而不需要了解其内部实现细节。API的标准说法是ApplicationProgrammingInterface,即应用程序编程接口。POSIX标准是由IEEE和ISO/IEC共同开发的标准系统。API是什么?API(Application Programming Interface,应用程序编程接口)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。分类Windows API操作系统的用户接口API函数包含在Windows系统目录下的动态连接库文件中。Windows API是一套用来控制Windows的各个部件的外观和行为的预先定义的Windows函数。用户的每个动作都会引发一个或几个函数的运行以告诉Windows发生了什么。这在某种程度上很像Windows的天然代码。而其他的语言只是提供一种能自动而且更容易的访问API的方法。当你点击窗体上的一个按钮时,Windows会发送一个消息给窗体,VB获取这个调用并经过分析后生成一个特定事件。更易理解来说:Windows系统除了协调应用程序的执行、内存的分配、系统资源的管理外,同时他也是一个很大的服务中心。调用这个服务中心的各种服务(每一种服务就是一个函数)可以帮助应用程序达到开启视窗、描绘图形和使用周边设备等目的,由于这些函数服务的对象是应用程序,所以称之为Application Programming Interface,简称API 函数。WIN32 API也就是MicrosoftWindows 32位平台的应用程序编程接口。凡是在 Windows工作环境底下执行的应用程序,都可以调用Windows API。linux API在linux中,用户编程接口API遵循了UNIX中最流行的应用编程界面标准---POSIX标准。POSIX标准是由IEEE和ISO/IEC共同开发的标准系统。该标准基于当时现有的UNIX实践和经验,描述了操作系统的系统调用编程接口API,用于保证应用程序可以在源程序一级上在多种操作系统上移植运行。这些系统调用编程接口主要是通过C库(LIBC)来实现的。简单的说,就是一个接口。现在很多企业都会有一些自己的开源项目或者开放的API,让开发者可以基于这些接口自主开发,目前大火的机器学习领域也有很多开放接口供开发者尝新。API接口有什么作用?我们常在软件开发或合作中遇到“API”这个词,比如API接口、API调用、API接口函数等等,那么到底什么是API呢?API用通俗的话说,就相当于大家经常看的科幻电影中的空间站或宇宙飞船与其他航天设备对接的那个开口。别人的航天飞机要跟你对接时,你必须告诉你的空间站的对外接口标准规格,比如接口名称、接口尺寸、接口程序等等,才能保证最终两者顺利通过这个接口建立联系,完成彼此内容的交换。API的标准说法是Application Programming Interface,即应用程序编程接口。API是一些预先定义函数,目的是用来提供应用程序与开发人员基于某软件或者某硬件得以访问一组例程的能力,并且无需访问源码或无需理解内部工作机制细节。这个也很好理解,一个标准软件,比如今日头条APP,就可以开放API接口给别人,比如字节跳动公司需要随时了解今日头条APP的用户使用状态,他们就可以让今日头条开发一个名字叫“用户状态/User Status”的API接口,然后字节跳动母公司就通过自己统计软件获取这个“User Status”函数的内容,这样今日头条用户总量、新增用户量、用户活跃状态、热点数据就会在字节跳动的统计软件屏幕上显示出来,而该统计软件不需要知道哪个用户具体发布了哪些内容,如果需要,也需要另外做API接口去调用。什么是API接口,具体是什么意思?作为软件应用而言,很多资源和数据不一定就是由其自身提供的,某些功能还是需要调用第三方提供的服务,这其中就涉及到API接口的调用。什么是API接口?API是指应用程序编程接口,我们通过API接口可以实现特定的功能,而不需要了解其内部实现细节。可以把API接口理解为是特定服务的一种封装,将服务封装起来提供给他人调用,这样一来很多功能不需要从新开发。举个例子,我们想要知道一周天气如何,如果由自己来实现这个功能很难,因为天气数据只有气象部门才知道。那该如何知道天气信息呢?气象部分提供数据API给我们使用,我们只要输入地区就会知道该地区一周的天气情况,但我们并不需要了解这天气预报是如何实现的。另外,不同系统和编程语言之间的数据通讯往往也采用API形式进行数据交接。常见的API形式有哪些?上面我们说到了,API其实就是一类服务的封装。我们可以使用不同的编程语言编写API,开发习惯和编程语言的不同导致API风格也存在差异。常见的API有以下几种形式:1、HTTP类型接口基于HTTP协议提供的API,这类API常常以“网址”形式提供的,像现在主流的RESTful就属于这类接口。2、RPC接口RPC它是指远程过程调用,将一部分代码逻辑放在远程服务器上部署,然后在需要的地方调用即可(调用远程方法就像调用本地方法一样),本质上是Client/Server模式,而且支持多种协议和数据传输方式。3、Web Service 接口Web Service并不具象地指某种API,我们将以WEB形式提供的服务都称之为Web Service,像RESTful也属于Web Service。
需要了解api接口的哪些东西
一、需要了解API接口的以下方面:1.功能:API接口的功能是指它提供的业务功能,包括数据查询、修改、增加、删除、计算等等,根据产品的需求确定需要调用哪些API接口。2.请求方式和传参:API接口的请求方式包括GET、POST、PUT、DELETE等,根据功能需要选择对应的请求方式。传参指的是使用API接口时需要传递的参数,包括必填参数、可选参数等。3.接口文档:接口文档是产品经理了解API的一个重要途径,文档应该包括接口的基本信息、请求参数、响应格式、错误码等内容。4.接口权限:接口权限指的是对接口的访问权限限制,包括是否需要账号和密码或者API秘钥来访问接口,以及访问该API接口的权限级别等。5.接口安全性:接口安全性包括接口传输的数据安全、用户身份验证、安全码保护等措施。6.接口性能:接口性能包括接口的响应时间、并发数等指标,根据产品需求做出相应的设计规划。同时,还需要关注接口的稳定性,确保接口运行的稳定性和可靠性。7.接口集成:接口集成是指将多个API接口整合到一起形成一个完整的业务流程,包括数据传输、数据同步、数据转换等方面。8.接口日志:接口日志记录了接口的访问情况、响应时间、错误信息等重要信息,对监控接口运行情况和问题处理非常有帮助。以上是产品经理需要了解API接口的一些重要方面。在了解这些方面的同时,产品经理还需要掌握一定的技术知识,理解API接口的工作原理,以便更好地协调开发、测试、运维等部门的工作,确保API接口的高效、稳定、安全运行。二、我可以为客户介绍API接口的操作流程,具体步骤如下:1.确定需求:首先,需要明确需要调用API接口实现哪些业务功能,并明确API接口的使用场景。2.查找API接口:在确定需求后,需要查找合适的API接口,可以通过搜索引擎、开发者社区、API平台等渠道查找合适的API接口。3.注册API接口:在找到合适的API接口后,需要在对应的平台上注册账号并获取API密钥,获取API接口的访问权限。4.测试接口:拿到API接口的密钥后,可以使用POSTMAN等工具测试API接口,确保API能够正常运行并返回正确的结果。5.开发文档:在API接口能够正常访问并返回结果后,需要查看API相关的开发文档,明确API所需的传参形式、数据格式等相关信息。6.调用API:根据API接口文档所定义的要求,将需要传递的参数传入API接口,调用API接口,并获取返回结果。7.处理返回结果:获取API接口的返回结果后,需要进行解析并进行相应的处理,例如显示在页面上或者存储到数据库中。8.监控API接口:在使用API接口时,需要时常监控API接口的访问情况和响应时间,并及时处理各类问题。以上就是API接口的操作流程,通过这一系列的步骤,可以实现对API接口的无缝调用,提高产品的整体效率和用户体验。
为什么要通过API接口来获取数据
API接口(应用编程接口 application/programming接口),准许应用程序通过定义的接口标准来访问另一个应用程序或服务的编程方式。简单来说,API就是两个软件或系统之间的通信语言或接口。在当今的互联网时代,数据无处不在,企业、政府等组织需要收集、存储和处理海量数据。API接口正是解决这一问题的重要手段之一。API接口提供了一种快速、简便、有效地获取数据的方式,以满足全球各地企业和开发人员的需求。一、那么,为什么要通过API接口来获取数据呢?下面就是让我们来探讨一下API接口的优势1.标准化数据获取方式:API接口提供了一套标准的数据获取方式,避免了直接访问数据库或者通过网页爬取数据所面临的格式不一、易受影响的问题。使用API接口获取数据可以大大简化开发难度,并且让数据的格式稳定可靠,方便后续的开发和维护。2.数据获取效率高:通过API接口获取数据可以达到高效的结果,因为API接口支持异步请求,数据的返回速度往往比直接访问数据库或者网页爬取要快得多。而对于高频获取数据的情况,这种方式可以大大降低服务器的负荷,提高整个系统的响应速度。3.可以获取更多更全面的数据:许多网站和电商平台在公开的网页中并没有展示全部的商品信息,而且一些数据项还有不公开的情况。但是通过API接口,可以获取到这些未公开的数据,从而提供更多更全面的帮助,并且让开发者能够更好地了解市场动态和商品趋势。4.数据处理更加简单:通过API接口获取到的数据经过相应的处理后,可以直接生成自己的数据,导入到自己的系统中,从而提高了IT部门的效率。同时,使用API接口可以让数据的更改和维护变得方便,这对于企业的长期运营是至关重要的。5.提高系统的可靠性:直接访问数据库和网页爬取存在一定的安全风险,可能会受到一些不可预测的攻,击,而通过API接口获取数据则可以更好地保护服务器的安全,让系统变得更加合规和可靠。综上所述,作为程序员,通过API接口来获取商品数据可以简化开发难度,提高数据获取效率,获取更多更全面的数据,同时提高系统的可靠性。这种方式已经成为现代软件开发的标配,并且正在得到越来越广泛的应用。二、程序员通过API接口获取的数据后如何进行筛选程序员可以使用编程语言(例如Python、Java、JavaScript等)对通过API接口获取的数据进行筛选,具体步骤如下:1.通过发送HTTP请求访问API接口并获取响应数据。一般来说,API响应是以JSON格式返回的,因此需要使用JSON解析器将响应数据解析为可操作的对象(例如Python中的字典或列表)。2.根据需要筛选数据。可以使用条件语句(例如if语句)、循环语句(例如for循环)和列表推导式等一系列技巧来筛选所需的数据。3.将筛选后的数据存储在变量或文件中,或通过API接口进行其他操作(例如修改或删除数据)。例如,以下代码演示了如何使用Python中的requests和json模块从OneWeather API接口中获取当前天气数据然后筛选出温度和湿度:import requests
import json
# 发送HTTP请求获取API响应
response = requests.get('https://api.openweathermap.org/data/2.5/weather?q=London&appid=API_KEY')
response_json = json.loads(response.text)
# 筛选所需的数据
temperature = response_json['main']['temp']
humidity = response_json['main']['humidity']
# 打印筛选后的数据
print('Temperature:', temperature)
print('Humidity:', humidity)当然,如果API接口提供了参数用于筛选数据,程序员也可以通过修改参数值来获取所需数据。具体的参数和取值需要参考API文档。
什么是API数据接口该怎么使用
API数据接口是指为提供服务的第三方应用程序和网站提供的一种标准化接囗,是连接不同应用程序和服务的桥梁。通过API数据接口,应用程序和服务可以利用第三方厂商提供的资源和功能,从而实现快速开发和增强自身的功能性。API的使用步骤一般如下:1.查找API数据接口:在开发之前,先确定需要什么样的API接口,可先查找API提供商的文档和网站资料,寻找符合需求的API接口。2.注册API账号:注册API账号并进行认证审核。3.测试API接口:开发者在本地进行数据调试,尝试发出API请求,查看返回的数据是否正确。4.API接口集成:将API接口集成到自己的应用程序中,在代码中引入API请求,和自己的应用程序相连,使API接口可以被调用并使用。5.部署应用程序:测试完毕并通过审核后,将自己的应用程序部署到服务器上并发布运行。6.运营并维护:在运行过程中,根据数据分析调整API请求的频率;对运行时接口出现故障或异常情况时进行问题诊断调试和解决API数据接口问题来优化运营。需要注意的是,API接口并非所有都是免费提供的,一些付费API需要购买后才可以使用。此外,使用API还需注意安全性和保护用户数据及隐私。在使用API接口的过程中,应该遵守API提供商的相关规定和限制条件,确保API接口的正常运行。
关于API数据接口的使用说明
API(Application Programming Interface)是一种让不同软件之间进行数据交换和通信的技术,使用API可以减少开发者的工作量,提高软件应用的效率和可靠性。本文将介绍API数据接口的使用方式和注意事项。使用API数据接口的方式:注册并获得API密钥要使用API接口,你通常需要通过注册网站或应用程序来获得API密钥。在注册过程中,你需要提供个人或企业信息,例如,你的姓名、邮件地址、电话等。有些API接口可能需要你填写详细的应用程序信息才能获得API密钥。了解API文档在开始使用API接口之前,你需要详细了解API文档。API文档通常包括接口的URL地址、请求参数、响应格式和错误信息。这些信息可以帮助你准确地构建API请求和解析API响应结果。构建API请求API请求是通过HTTP请求发送给API接口的。每个API接口都有其请求方式和请求格式。你需要使用API密钥和所需参数向API服务发送HTTP请求并获取API响应结果。解析API响应结果API响应结果可能是一个JSON、XML或其他格式的数据结构。你需要解析API响应结果并提取所需信息。在解析API响应结果时,你需要特别留意错误信息,以确保你的应用程序可以正确处理异常情况。注意事项:使用合法的API密钥在使用API接口时,一定要使用合法的API密钥。如果你使用的API密钥不合法,你将无法正常地使用API服务。不要频繁访问API接口一些API服务提供商会限制每个API密钥的请求访问次数,如果你的应用程序频繁访问API接口,你的API请求可能会被拒绝或者被暂时禁止访问。注意API接口的安全性API数据接口的安全性非常重要。在使用API接口时,你需要特别注意保护API密钥,以避免API密钥被黑客或不法分子窃取或滥用。总之,使用API接口可以方便快捷地获取所需的数据和信息,但在使用过程中,需要特别留意API接口的使用方式和注意事项。只有充分理解API接口的使用方式和注意事项,才能保证你的应用程序能够正常地使用API服务并获得所需的数据和信息。
关于API接口应用
随着互联网技术的发展,API接口已成为众多应用程序开发中的必备工具,它不仅方便了开发者进行应用程序开发,也为应用程序提供了更多的功能和服务。本文将介绍API接口的概念和应用,以及API接口的优势和未来趋势。一、什么是API接口API是Application Programming Interface,即应用程序接口。它是一组允许不同应用程序之间相互通信的协议集合。API提供了一种通用的方式,允许应用程序通过接口访问和操作另一个应用程序的功能和数据。API接口可以充当不同应用程序之间的沟通桥梁,以实现数据交换、应用程序整合等功能。二、API接口的应用API接口的应用非常广泛,包括软件开发、数据分析、机器学习等领域。下面列举几个常见的API接口应用:1.社交媒体:社交媒体(如Facebook、Twitter)的API接口可以为应用程序提供用户身份验证、发布状态更新、读取消息等功能。2.地图服务:谷歌地图API和百度地图API等地图服务API接口可以为应用程序提供地图显示和查询服务。3.电商平台:淘宝、天猫等电商平台的API接口可以为开发者提供商品查询、下单、支付等功能。4.支付服务:支付宝、微信支付等支付服务的API接口可以为应用程序提供电子支付、转账等服务。5.外语翻译:Google翻译、百度翻译等翻译接口API可以为应用程序提供多种语言的翻译功能。三、API接口的优势使用API接口有很多优势:1.高效:使用API接口可以提高开发效率,减少开发难度,因为API接口已经实现了很多功能,可以为开发者省去重复的工作。2.可定制:API接口可以根据应用程序的需求进行个性化定制,开发者可以通过API接口进行数据的查询、读取和更新。3.可伸缩:API接口的使用可以大大提高应用程序的可伸缩性,可以根据应用程序的不同需求和特点进行不同的扩展和增强。4.易于维护:API接口的使用可以降低软件的维护成本,减少代码重复,提高代码的可复用性。五、API接口的未来趋势API接口在未来的发展中,将会采用更多的云技术、自动化工具和安全技术以保障应用程序的稳定性和安全性。API接口的管理与安全将成为API接口设计者和使用者关注的主要问题。针对API接口发展趋势,下面简单列举一下:1.微服务接口:微服务的兴起将会推动API接口的发展,微服务的调用需要很多API接口联动,这也将会让API接口得以更加发挥作用。2.云技术集成:API接口与云技术将更加紧密地结合起来,这使得服务可以更快速、更高效地交互和融合。3.自动化API管理:自动化API管理将是API接口的未来。4.安全性保障:API接口的安全威胁是一种长期持续的问题,API接口设计者和使用者需要各行其职,共同推进API接口的安全性保障。综上所述,API接口在不断地推动着应用程序的发展,使得不同应用程序之间的交互更为便捷、高效和安全。API接口在未来的发展中将会采用更多的云技术、自动化工具和安全技术,推动着应用程序向更高的方向发展。
【C语言】标准库(头文件、静态库、动态库),windows与Linux平台下的常用C语言标准库(二)
5.2 windows常用头文件挑几个介绍吧。01. windows.h——窗口、进程、线程windows.h 是用于 Windows 平台的 C 和 C++ 程序开发的头文件。它包含了许多 Windows API 函数和宏定义,用于操作和管理 Windows 操作系统的各个方面。以下是 windows.h 中一些常用的内容:1.窗口和消息相关的函数和宏:CreateWindowEx:用于创建窗口。RegisterClassEx:用于注册窗口类。GetMessage 和 DispatchMessage:用于处理消息循环。SendMessage 和 PostMessage:用于发送消息到窗口过程。DefWindowProc:默认的窗口过程函数。2.文件和文件系统相关的函数和宏:CreateFile 和 CloseHandle:用于创建和关闭文件句柄。ReadFile 和 WriteFile:用于读取和写入文件内容。FindFirstFile 和 FindNextFile:用于查找文件和目录。SetFileAttributes 和 GetFileAttributes:用于设置和获取文件属性。3.进程和线程相关的函数和宏:CreateProcess 和 TerminateProcess:用于创建和终止进程。GetCurrentProcess 和 GetCurrentThread:用于获取当前进程和线程的句柄。CreateThread 和 ExitThread:用于创建和退出线程。Sleep 和 WaitForSingleObject:用于控制线程的等待和延迟。4.动态链接库(DLL)相关的函数和宏:LoadLibrary 和 FreeLibrary:用于加载和释放 DLL。GetProcAddress:用于获取 DLL 中的函数地址。此外,windows.h 还包含了许多其他用于操作窗口、图形、网络、注册表、安全性等方面的函数和宏定义。它是 Windows 平台上开发 Windows 应用程序和系统级软件的关键头文件之一。通过使用 windows.h,开发者可以访问 Windows API,并利用丰富的功能和特性来创建强大的 Windows 应用程序。02. winsock2.h——windows网络套接字winsock2.h 是 Windows 平台上用于网络编程的头文件。它提供了许多函数、结构体和常量,用于创建和管理网络套接字(sockets),实现网络通信。winsock2.h 是在 Windows Sockets 2(Winsock2)规范下开发网络应用程序的关键头文件。以下是 winsock2.h 中一些常用的内容:1.套接字相关的函数和结构体:socket:创建套接字。bind 和 connect:绑定或连接套接字。listen 和 accept:监听和接受连接。send 和 recv:发送和接收数据。closesocket:关闭套接字。2.地址和协议相关的结构体和常量:sockaddr 和 sockaddr_in:用于表示套接字地址信息。AF_INET 和 AF_INET6:表示 IPv4 和 IPv6 地址族。SOCK_STREAM 和 SOCK_DGRAM:表示 TCP 和 UDP 套接字类型。IPPROTO_TCP 和 IPPROTO_UDP:表示 TCP 和 UDP 协议。3.初始化和清理函数:WSAStartup 和 WSACleanup:初始化和清理 Winsock 库。通过使用 winsock2.h 中的函数、结构体和常量,开发者可以创建网络应用程序,实现数据的传输和通信。它提供了对套接字编程的支持,使您能够创建网络连接、发送和接收数据、处理网络事件等。winsock2.h 是开发 Windows 平台上网络应用程序的重要组成部分,并且与其他网络库(如 TCP/IP)集成使用,以构建功能丰富的网络应用。更高级的还有:mswsock.h03. shellapi.h——shell编程shellapi.h 是 Windows 平台上用于 Shell 编程的头文件。它提供了一些函数和结构体,用于与操作系统的 Shell 执行交互,包括文件操作、快捷方式管理、系统托盘图标等。以下是 shellapi.h 中一些常用的内容:1.文件和文件夹操作函数:ShellExecute:打开文件、文件夹或运行应用程序。SHGetFolderPath:获取特定文件夹路径。SHCreateDirectoryEx:创建目录。SHFileOperation:执行文件和文件夹的操作,如复制、移动、删除等。2.快捷方式和图标管理函数:SHGetSpecialFolderPath:获取特殊文件夹路径,如桌面、开始菜单等。SHGetFileInfo:获取文件或文件夹的图标和其他属性信息。SHCreateShortcut:创建快捷方式。SHChangeNotify:通知系统有关文件和文件夹的更改。3.系统托盘图标函数:Shell_NotifyIcon:在系统托盘中显示和管理图标。Shell_NotifyIconGetRect:获取系统托盘的位置和尺寸。通过使用 shellapi.h 中的函数和结构体,开发者可以与 Windows Shell 进行交互,实现文件操作、快捷方式管理、系统托盘图标等功能。它为应用程序提供了与操作系统桌面环境的集成,使您能够执行与文件、文件夹和系统界面相关的操作。shellapi.h 是开发 Windows 平台上与 Shell 相关功能的重要头文件之一,为开发者提供了方便而强大的 Shell 编程接口。5.3 Linux常用头文件01. unistd.h——系统相关(进程、文件等)unix standrad.unistd.h 用于提供对操作系统服务的访问和控制。尽管它是在类 Unix 系统上常见的头文件,但它并不是标准的 C 或 C++ 头文件,而是与操作系统相关的特定头文件。以下是 unistd.h 中一些常见的功能和函数:1.系统调用和进程控制:fork:创建一个子进程。exec 系列函数:在当前进程中执行新的程序。exit:终止当前进程的执行。getpid 和 getppid:获取当前进程和父进程的进程 ID。2.文件和目录操作:open 和 close:打开和关闭文件。read 和 write:从文件中读取和写入数据。lseek:移动文件指针。access:检查文件的访问权限。mkdir 和 rmdir:创建和删除目录。3.系统资源管理:sleep:使当前进程挂起指定的时间。getcwd:获取当前工作目录。chdir:改变当前工作目录。gethostname:获取主机名。getlogin:获取登录名。4.进程间通信(IPC):pipe:创建一个管道用于进程间通信。dup 和 dup2:复制文件描述符。fork 和 exec 系列函数:在进程间传递状态和数据。unistd.h 提供了对类 Unix 系统的许多常见功能的访问,使程序能够与操作系统进行交互,并进行进程管理、文件操作和系统资源管理。需要注意的是,unistd.h 在不同的类 Unix 系统上可能会有所不同,因此一些函数和功能在特定系统上可能不可用或有不同的行为。02. fcntl.h——文件操作fcntl.h 用于提供对文件描述符的控制和和操作。它定义了一些常量和函数,用于控制文件的属性、打开和关闭文件以及文件锁定等操作。以下是 fcntl.h 中一些常见的功能和函数:1.文件控制:open:打开文件并返回一个文件描述符。creat:创建一个新文件。close:关闭文件。2.文件状态标志(File Status Flags):O_RDONLY、O_WRONLY、O_RDWR:打开文件的读取、写入、读写模式。O_APPEND:追加写入文件。O_CREAT、O_EXCL:创建文件并验证文件不存在。O_TRUNC:将文件截断为零长度。3.文件控制操作:fcntl:对文件描述符进行各种控制操作。dup、dup2:复制文件描述符。4.文件锁定:flock:对文件进行简单的全局锁定。fcntl:使用 F_SETLK、F_SETLKW 等命令进行更复杂的文件锁定。fcntl.h 提供了一些用于文件描述符控制和操作的函数和常量,使程序能够在类 Unix 系统上对文件进行更精细的控制和操作。需要注意的是,fcntl.h 在不同的类 Unix 系统上可能会有所不同,因此一些函数和常量的行为可能会有所差异。03. sys/types.h——操作系统相关的数据类型定义sys/types.h 用于定义一些基本数据类型和数据结构。它包含了一些与操作系统相关的数据类型和宏定义,以便在编程中使用。以下是 sys/types.h 中一些常见的内容:1.数据类型定义:size_t:用于表示对象的大小。ssize_t:用于表示有符号的对象大小。time_t:用于表示时间值。off_t:用于表示文件偏移量。pid_t:用于表示进程 ID。uid_t 和 gid_t:用于表示用户 ID 和组 ID。2.文件类型和模式定义:mode_t:用于表示文件的权限模式。dev_t:用于表示设备的标识符。3.其他常见的数据类型和宏:int8_t、int16_t、int32_t、int64_t:用于表示固定大小的整数类型。intptr_t 和 uintptr_t:用于表示指针类型的整数值。NULL:表示空指针常量。sys/types.h 提供了一些与操作系统相关的数据类型定义,为程序提供了可移植性和代码可读性。通过包含 sys/types.h 头文件,可以使用其中定义的数据类型和宏,以便在类 Unix 系统上进行编程和开发。需要注意的是,sys/types.h 是与操作系统相关的头文件,在不同的操作系统上可能会有所不同。04. sys/stat.h——文件状态sys/stat.h 用于获取文件和文件系统的状态信息。它定义了一些常量和结构体,用于描述文件的属性、权限、时间戳等信息。以下是 sys/stat.h 中一些常见的内容:1.文件访问权限:S_IRUSR、S_IWUSR、S_IXUSR:用户(所有者)的读、写、执行权限。S_IRGRP、S_IWGRP、S_IXGRP:用户组的读、写、执行权限。S_IROTH、S_IWOTH、S_IXOTH:其他用户的读、写、执行权限。2.文件类型和模式:S_IFMT:用于提取文件类型的位掩码。S_IFREG、S_IFDIR、S_IFLNK 等:表示不同文件类型的位掩码。S_ISREG()、S_ISDIR()、S_ISLNK() 等:用于检查文件类型的宏。3.文件状态结构体:struct stat:用于描述文件的状态信息,包括文件大小、权限、时间戳等。4.文件属性获取函数:stat、lstat:获取文件的状态信息。fstat:获取打开文件的状态信息。sys/stat.h 提供了一些用于获取文件状态信息的常量、结构体和函数,使程序能够获取和操作文件的属性和权限等信息。通过包含 sys/stat.h 头文件,可以在类 Unix 系统上进行文件和文件系统相关的编程和开发。需要注意的是,sys/stat.h 是与操作系统相关的头文件,在不同的操作系统上可能会有所不同。05. sys/socket.h——套接字sys/socket.h 主要作用是定义了与套接字(socket)相关的各种数据类型、常量和函数,包含了许多网络编程所需的头文件。“sys/socket.h” 中包含的函数和常量如下:函数:socket():创建一个新的套接字bind():将套接字绑定到地址指定的端口号listen():监听套接字上的连接请求accept():接收客户端的连接请求connect():建立与远程服务器的连接recv():从套接字中接收数据send():将数据发送到套接字close():关闭一个套接字shutdown():关闭一个已建立的连接(这两个有区别,本文暂不不讲解)常量:AF_INET:IPv4 协议的地址族,又称为 Internet 地址族AF_UNIX:Unix 协议的地址族SOCK_STREAM:提供面向连接、可靠的数据传输服务的套接字类型SOCK_DGRAM:提供面向无连接、不可靠的数据传输服务的套接字类型IPPROTO_TCP:TCP 协议的协议号IPPROTO_UDP:UDP 协议的协议号除此之外,“sys/socket.h” 中还定义了一些结构体如 sockaddr、sockaddr_in 等,这些结构体用于套接字地址的表示和传输。使用这些结构体可以更方便地操作套接字地址。“sys/socket.h” 和 “winsock2.h” 都是用于进行套接字编程的头文件,但它们之间有几个重要的差异。1.操作系统平台不同“sys/socket.h” 是 UNIX 和 Linux 等类 UNIX 操作系统的头文件,而 “winsock2.h” 则是 Windows 操作系统的头文件。2.函数调用方式不同UNIX 系统上使用标准的函数调用方式来执行套接字操作,而 Windows 系统上则使用 Windows 套接字 API (也称为 Winsock)。3.数据类型不同“sys/socket.h” 和 “winsock2.h” 定义了不同的数据类型,例如 “struct sockaddr” 和"SOCKADDR" 表示套接字地址的结构体,在 UNIX 系统上使用前者,在 Windows 上使用后者。4.常量和枚举值不同“sys/socket.h” 和 “winsock2.h” 中定义的常量和枚举值也不同,例如在 UNIX 系统上使用 AF_INET 表示IPv4 地址族,在 Windows 系统上则使用 AF_INET 或 AF_INET6(表示 IPv6 地址族)。06. sys/time.h——处理时间sys/time.h 用于处理时间和时间相关的操作。它定义了一些与时间和时间操作相关的结构体、常量和函数。以下是 sys/time.h 中一些常见的内容:1.时间结构体:struct timeval:表示时间值,包括秒数和微秒数。2.时间获取函数:gettimeofday:获取当前时间。3.定时器操作函数:timeradd、timersub、timerclear:对时间进行加法、减法和清零操作。4.时间转换函数:gmtime、localtime:将时间转换为本地时间或协调世界时(UTC)时间。strftime:将时间格式化为字符串。sys/time.h 提供了一些用于处理时间和时间操作的结构体和函数,使程序能够获取、操作和转换时间。通过包含 sys/time.h 头文件,可以在类 Unix 系统上进行与时间相关的编程和开发。需要注意的是,sys/time.h 是与操作系统相关的头文件,在不同的操作系统上可能会有所不同。sys/time.h 和 time.h 都是用于处理时间和时间相关操作的头文件,但它们在功能和使用上有一些区别。sys/time.h 是一个系统头文件,在类 Unix系统中使用。它提供了对时间的更底层的处理,包括获取当前时间、定时器操作、时间结构体等。它通常用于需要更高精度或更底层的时间操作,比如微秒级的计时和定时器功能。time.h 是标准 C 库中的头文件,提供了与时间相关的基本函数和结构体。它包含了用于获取当前时间、时间格式化、时间转换等功能,如 time、localtime、strftime 等函数。它在标准 C 环境下使用,并且提供了对时间的常见操作的抽象。07. sys/wait.h——处理进程状态和等待sys/wait.h 用于处理进程的状态和等待相关的操作。它定义了一些常量和函数,使程序能够等待子进程的状态改变、获取子进程的退出状态等。以下是 sys/wait.h 中一些常见的内容:1.子进程状态常量:WIFEXITED(status):检查子进程是否正常退出。WEXITSTATUS(status):获取子进程的退出状态。WIFSIGNALED(status):检查子进程是否因信号而终止。WTERMSIG(status):获取导致子进程终止的信号编号。WIFSTOPPED(status):检查子进程是否暂停。WSTOPSIG(status):获取导致子进程暂停的信号编号。2.等待子进程的状态改变:wait:等待任意子进程的状态改变。waitpid:等待指定子进程的状态改变。sys/wait.h 提供了一些用于处理进程状态和等待子进程的函数和常量,使程序能够控制和监视子进程的执行状态。通过包含 sys/wait.h 头文件,可以在类 Unix 系统上进行与进程状态相关的编程和开发。需要注意的是,sys/wait.h 是与操作系统相关的头文件,在不同的操作系统上可能会有所不同。它通常与下面的头文件一起使用:08. pthread.h——POSIX线程编程pthread.h 用于支持 POSIX 线程(POSIX Threads)编程。它定义了一些数据类型、常量和函数,用于创建、管理和同步多线程程序。以下是 pthread.h 中一些常见的内容:1.线程管理函数:pthread_create:创建一个新线程。pthread_exit:终止当前线程。pthread_join:等待指定线程的结束。pthread_detach:将线程设置为可分离状态,使其在退出时自动释放资源。2.线程同步函数:pthread_mutex_init、pthread_mutex_destroy:初始化和销毁互斥锁。pthread_mutex_lock、pthread_mutex_unlock:对互斥锁进行加锁和解锁操作。pthread_cond_init、pthread_cond_destroy:初始化和销毁条件变量。pthread_cond_wait、pthread_cond_signal:等待和通知条件变量的变化。3.线程属性操作:pthread_attr_init、pthread_attr_destroy:初始化和销毁线程属性。pthread_attr_getdetachstate、pthread_attr_setdetachstate:获取和设置线程的分离状态。4.线程局部存储:pthread_key_create、pthread_key_delete:创建和删除线程局部存储键。pthread_setspecific、pthread_getspecific:设置和获取线程局部存储的值。pthread.h 提供了一些用于多线程编程的函数和常量,使程序能够创建、管理和同步多个并发执行的线程。通过包含 pthread.h 头文件,可以在类 Unix 系统上进行多线程编程和开发。需要注意的是,pthread.h 是与 POSIX 线程编程相关的头文件,在不同的操作系统上可能会有所不同。09. signal.h——信号处理signal.h 用于处理信号(Signals)和信号处理器(Signal Handlers)。信号是在计算机系统中用于通知进程发生了某些事件或异常的机制。以下是 signal.h 中一些常见的内容:1.信号处理函数:signal:设置信号处理器(Signal Handler)。raise:向当前进程发送指定信号。2.信号常量:SIGINT、SIGTERM、SIGQUIT:表示用户发送的中断、终止、退出信号。SIGSEGV、SIGILL、SIGFPE:表示发生的错误信号,如段错误、非法指令、浮点异常等。SIGUSR1、SIGUSR2:表示用户自定义的信号。3.信号处理器的行为:SIG_DFL:默认的信号处理器。SIG_IGN:忽略该信号。通过使用 signal.h 头文件,可以为程序中的特定信号注册信号处理器,并定义对这些信号的处理行为。当程序接收到注册的信号时,信号处理器将被调用,从而可以执行自定义的处理逻辑,比如优雅地关闭程序、处理异常情况等。信号处理是一个底层的机制,对于复杂的异步事件处理,通常会选择使用更高级的机制,如多线程或异步编程。然而,在某些情况下,信号处理可以提供一种简单有效的方式来处理一些特定类型的事件或异常。signal.h 头文件定义的信号处理函数和信号常量在标准 C 和 POSIX 标准中是通用的,不仅适用于 Linux,还适用于其他类 Unix 操作系统,例如 macOS、FreeBSD、Solaris 等。虽然信号处理机制是 POSIX 标准的一部分,但是不同的操作系统可能会有一些差异和特定的信号。因此,需要根据特定的操作系统来确定可用的信号和其对应的常量。此外,不同的操作系统可能对信号处理器的行为有一些特定规定或扩展。因此,在编写跨平台的代码时,需要谨慎处理信号处理的相关逻辑,并参考各个操作系统的文档以确保代码在不同平台上的正确性和可移植性。在 Windows 系统中,信号处理的机制与类 Unix 系统有所不同。Windows 平台使用的是事(Event)和消息(Message)机制来处理类似于信号的异步事件和通知。Windows 提供了一系列与异步事件处理相关的函数和机制,例如 SetEvent、WaitForSingleObject 、MsgWaitForMultipleObjects 等。这些函数可以用于等待和触发事件,并实现类似于信号处理的功能。相应地,Windows 平台使用不同于 signal.h 的头文件和函数来处理异步事件和通知。例如,Windows API 中的 SetConsoleCtrlHandler 函数可以用于设置控制台的控制事件处理器,以便捕获和处理控制台事件,如 Ctrl+C 或 Ctrl+Break。此外,Windows 程序通常使用消息循环和消息处理函数来处理窗口消息和用户交互事件。10. dlfcn.h——动态链接库dlfcn.h 用于动态链接库(Dynamic Linking)的加载和函数调用。它提供了一组函数和数据类型,使程序能够在运行时动态地加载共享库(也称为动态链接库或共享对象)并使用其中的函数。以下是 dlfcn.h 中一些常见的内容:1.动态库加载和卸载函数:dlopen:打开一个动态链接库。dlclose:关闭一个已打开的动态链接库。2.符号获取函数:dlsym:在动态链接库中查找指定名称的符号(函数、变量等)。3.错误处理函数:dlerror:获取最近一次动态链接库相关操作的错误信息。使用 dlfcn.h 头文件可以让程序在运行时加载和使用共享库,动态地调用其中的函数或获取变量的地址。这在需要在程序运行过程中动态加载和使用外部库的情况下非常有用,例如插件系统、动态加载的驱动程序等。,dlfcn.h 是一个非标准的头文件,它在类 Unix 系统上比较常见,如 Linux、macOS、FreeBSD 等,但在其他操作系统上可能不可用或存在差异。在使用 dlfcn.h 中的函数时,需要注意兼容性和平台特定的差异。
Spring高手之路2——深入理解注解驱动配置与XML配置的融合与区别
1. 配置类的编写与Bean的注册
XML配置中,我们通常采用ClassPathXmlApplicationContext,它能够加载类路径下的XML配置文件来初始化Spring应用上下文。然而,在注解驱动的配置中,我们则使用以Annotation开头和ApplicationContext结尾的类,如AnnotationConfigApplicationContext。AnnotationConfigApplicationContext是Spring容器的一种,它实现了ApplicationContext接口。
对比于 XML 文件作为驱动,注解驱动需要的是配置类。一个配置类就可以类似的理解为一个 XML 。配置类没有特殊的限制,只需要在类上标注一个 @Configuration 注解即可。
我们创建一个 Book 类:
public class Book {
private String title;
private String author;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
在 xml 中声明 Bean 是通过 <bean> 标签
<bean id="book" class="com.example.Book">
<property name="title" value="Java Programming"/>
<property name="author" value="Unknown"/>
</bean>
如果要在配置类中替换掉 <bean> 标签,需要使用 @Bean 注解
我们创建一个配置类来注册这个 Book bean:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LibraryConfiguration {
@Bean
public Book book() {
Book book = new Book();
book.setTitle("Java Programming");
book.setAuthor("Unknown");
return book;
}
}
在这个配置中,我们使用了 @Configuration 注解来表示这是一个配置类,类似于一个 XML 文件。我们在 book() 方法上使用了 @Bean 注解,这意味着这个方法将返回一个由 Spring 容器管理的对象。这个对象的类型就是 Book,bean 的名称id就是方法的名称,也就是 "book"。
类似于 XML 配置的 <bean> 标签,@Bean 注解负责注册一个 bean。你可以把 @Bean 注解看作是 <bean> 标签的替代品。
如果你想要更改这个 bean 的名称,你可以在 @Bean 注解中使用 name 属性:
@Bean(name="mybook")
public Book book() {
Book book = new Book();
book.setTitle("Java Programming");
book.setAuthor("Unknown");
return book;
}
这样,这个 Book bean 的名称就变成了 "mybook"。
启动并初始化注解驱动的IOC容器
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfiguration.class);
// 从容器中获取 Book bean
LibraryConfiguration libraryConfiguration = context.getBean(LibraryConfiguration.class);
System.out.println(libraryConfiguration.book().getTitle());
System.out.println(libraryConfiguration.book().getAuthor());
}
}
ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfiguration.class)这个语句创建了一个Spring的应用上下文,它是以配置类LibraryConfiguration.class作为输入的,这里明确指定配置类的Spring应用上下文,适用于更一般的Spring环境。
对比一下ApplicationContext context = SpringApplication.run(DemoApplication.class, args);这个语句则是Spring Boot应用的入口,启动一个Spring Boot应用。SpringApplication.run()方法会创建一个Spring Boot应用上下文(也就是一个SpringApplication对象),这个上下文包含了Spring Boot应用所有的Bean和配置类,还有大量的默认配置。这个方法之后,Spring Boot的自动配置就会起作用。你可以把SpringApplication.run()创建的Spring Boot上下文看作是更加功能丰富的Spring上下文。
打印结果:
Java Programming和Unknown被打印,执行成功。
注意:@SpringBootApplication是一个复合注解,它等效于同时使用了@Configuration,@EnableAutoConfiguration和@ComponentScan。这三个注解的作用是:
@Configuration:指明该类是一个配置类,它可能会有零个或多个@Bean注解,方法产生的实例由Spring容器管理。
@EnableAutoConfiguration:告诉Spring Boot根据添加的jar依赖自动配置你的Spring应用。
@ComponentScan:Spring Boot会自动扫描该类所在的包以及子包,查找所有的Spring组件,包括@Configuration类。
在非Spring Boot的传统Spring应用中,我们通常使用AnnotationConfigApplicationContext或者ClassPathXmlApplicationContext等来手动创建和初始化Spring的IOC容器。
"非Spring Boot的传统Spring应用"是指在Spring Boot项目出现之前的Spring项目,这些项目通常需要手动配置很多东西,例如数据库连接、事务管理、MVC控制器等。这种类型的Spring应用通常需要开发者对Spring框架有深入的了解,才能做出正确的配置。
Spring Boot是Spring项目的一个子项目,它旨在简化Spring应用的创建和配置过程。Spring Boot提供了一系列的"起步依赖",使得开发者只需要添加少量的依赖就可以快速开始项目的开发。此外,Spring Boot还提供了自动配置的特性,这使得开发者无需手动配置数据库连接、事务管理、MVC控制器等,Spring Boot会根据项目的依赖自动进行配置。
因此,"非Spring Boot的传统Spring应用"通常需要手动创建和初始化Spring的IOC容器,比如使用AnnotationConfigApplicationContext或ClassPathXmlApplicationContext等。在Spring Boot应用中,这个过程被自动化了,开发者只需要在main方法中调用SpringApplication.run方法,Spring Boot就会自动创建和初始化Spring的IOC容器。SpringApplication.run(Application.class, args);语句就是启动Spring Boot应用的关键。它会启动一个应用上下文,这个上下文会加载所有的Spring组件,并且也会启动Spring的IOC容器。在这个过程中,所有通过@Bean注解定义的bean都会被创建,并注册到IOC容器中。
有人说,那学习Spring Boot就好了,学什么Spring和Spring MVC啊,这不是落后了吗
Spring Boot并不是Spring框架的替代品,而是建立在Spring框架之上的一种工具,它内部仍然使用Spring框架的很多核心技术,包括Spring MVC。所以,当我们在使用Spring Boot时,我们实际上仍然在使用Spring MVC来处理Web层的事务。
简而言之,Spring MVC是一个用于构建Web应用程序的框架,而Spring Boot是一个用于简化Spring应用程序开发的工具,它内部仍然使用了Spring MVC。你在Spring Boot应用程序中使用的@Controller、@Service、@Autowired等注解,其实都是Spring框架提供的,所以,原理性的东西还是需要知道。
2. 注解驱动IOC的依赖注入与XML依赖注入对比
我们就以上面的例子来说,假设配置类注册了两个bean,并设置相关的属性:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class LibraryConfiguration {
@Bean
public Book book() {
Book book = new Book();
book.setTitle("Java Programming");
book.setAuthor("Unknown");
return book;
}
@Bean
public Library library() {
Library library = new Library();
library.setBook(book());
return library;
}
}
这里的方法有@Bean注解,这个注解告诉Spring,这个方法返回的对象需要被注册到Spring的IOC容器中。
如果不用注解,要实现相同功能的话,对应的XML配置如下:
<bean id="book" class="com.example.Book">
<property name="title" value="Java Programming"/>
<property name="author" value="Unknown"/>
</bean>
<bean id="library" class="com.example.Library">
<property name="book" ref="book"/>
</bean>
在这个XML配置中,我们定义了两个<bean>元素,分别用来创建Book对象和Library对象。在创建Book对象时,我们使用了<property>元素来设置title和author属性。在创建Library对象时,我们也使用了<property>元素,但是这次我们使用了ref属性来引用已经创建的Book对象,这就相当于将Book对象注入到Library对象中。
3. Spring中组件的概念
在Spring框架中,当我们说 "组件" 的时候,我们通常指的是被Spring管理的各种Java对象,这些对象在Spring的应用上下文中作为Bean存在。这些组件可能是服务层的类、数据访问层的类、控制器类、配置类等等。
@ComponentScan注解会扫描指定的包(及其子包)中的类,如果这些类上标注了@Component、@Controller、@Service、@Repository、@Configuration等注解,那么Spring就会为这些类创建Bean定义,并将这些Bean定义注册到Spring的应用上下文中。因此,我们通常说@ComponentScan进行了"组件扫描",因为它扫描的是标注了上述注解的类,这些类在Spring中都被视为组件。
而这些注解标记的类,最终在Spring的应用上下文中都会被创建为Bean,因此,你也可以理解@ComponentScan为"Bean扫描"。但是需要注意的是,@ComponentScan只负责扫描和注册Bean定义,Bean定义就是元数据描述,包括了如何创建Bean实例的信息。
总结一下,@ComponentScan注解会扫描并注册的"组件"包括:
标注了@Component注解的类
标注了@Controller注解的类(Spring MVC中的控制器组件)
标注了@Service注解的类(服务层组件)
标注了@Repository注解的类(数据访问层组件)
标注了@Configuration注解的类(配置类)
这些组件最终都会在Spring的应用上下文中以Bean的形式存在。
4. 组件注册
这里Library 标注 @Configuration 注解,即代表该类会被注册到 IOC 容器中作为一个 Bean。
@Component
public class Library {
}
相当于 xml 中的:
<bean id="library" class="com.example.demo.configuration.Library">
如果想指定 Bean 的名称,可以直接在 @Configuration 中声明 value 属性即可
@Component("libra")
public class Library {
}
@Component("libra")就将这个bean的名称改为了libra,如果不指定 Bean 的名称,它的默认规则是 “类名的首字母小写”(例如Library默认名称是 library )
5. 组件扫描
如果我们只写了@Component, @Configuration 这样的注解,IOC容器是找不到这些组件的。
5.1 使用@ComponentScan的组件扫描
忽略掉之前的例子,在这里我们需要运行的代码如下:
@Component
public class Book {
private String title = "Java Programming";
private String author = "Unknown";
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
@Component
public class Library {
@Resource
private Book book;
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
}
如果不写@ComponentScan,而且@Component注解标识的类不在当前包或者子包,那么就会报错。
难道@Component注解标识的类在当前包或者当前包的子包,主程序上就可以不写@ComponentScan了吗?
是的!前面说了,@SpringBootApplication 包含了 @ComponentScan,其实已经帮我们写了!只有组件和主程序不在一个共同的根包下,才需要显式地使用 @ComponentScan 注解。由于 Spring Boot 的设计原则是“约定优于配置”,所以推荐将主应用类放在根包下。
在应用中,我们的组件(带有 @Component、@Service、@Repository、@Controller 等注解的类)和主配置类位于不同的包中,并且主配置类或者启动类没有使用 @ComponentScan 指定扫描这些包,那么在运行时就会报错,因为Spring找不到这些组件。
主程序:
@SpringBootApplication
@ComponentScan(basePackages = "com.example")
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(DemoApplication.class, args);
Library library = context.getBean(Library.class);
System.out.println(library.getBook().getTitle());
System.out.println(library.getBook().getAuthor());
}
}
@ComponentScan 不一定非要写在主程序(通常是指 Spring Boot 的启动类)上,它可以写在任何配置类(标记有 @Configuration 注解的类)上。@ComponentScan 注解会告诉 Spring 从哪些包开始进行组件扫描。
为了简化配置,我们通常会将 @ComponentScan 放在主程序上,因为主程序一般会位于根包下,这样可以扫描到所有的子包。这里为了演示,并没有把主程序放在根目录。
我们上面说过,@ComponentScan只负责扫描和注册Bean定义,只有需要某个Bean时,这个Bean才会实例化。
那怎么才能知道是不是需要这个Bean呢?
我来给大家举例子,并且还会说明Bean的创建顺序问题,"需要某个Bean"通常体现在以下几个方面:
依赖注入(Dependency Injection): 如果一个BeanA的字段或者构造方法被标注为@Autowired或者@Resource,那么Spring就会尝试去寻找类型匹配的BeanB并注入到BeanA中。在这个过程中,如果BeanB还没有被创建,那么Spring就会先创建BeanB的实例。
@Component
public class BeanA {
@Autowired
private BeanB beanB;
}
@Component
public class BeanB {
}
BeanA依赖于BeanB。在这种情况下,当你尝试获取BeanA的实例时,Spring会首先创建BeanB的实例,然后把这个实例注入到BeanA中,最后创建BeanA的实例。在这个例子中,BeanB会先于BeanA被创建。
这种方式的一个主要优点是,我们不需要关心Bean的创建顺序,Spring会自动解决这个问题。这是Spring IoC容器的一个重要特性,也是为什么它能够使我们的代码更加简洁和易于维护的原因。
Spring框架调用: 有些情况下,Spring框架的一些组件或者模块可能需要用到你定义的Bean。比如,如果你定义了一个@Controller,那么在处理HTTP请求时,Spring MVC就会需要使用到这个@Controller Bean。如果这个时候Bean还没有被创建,那么Spring也会先创建它的实例。
假设我们有一个名为BookController的类,该类需要一个BookService对象来处理一些业务逻辑。
@Controller
public class BookController {
@Autowired
private BookService bookService;
// 其他的控制器方法
}
BookService类
@Service
public class BookService {
@Autowired
private BookMapper bookMapper;
// 一些业务逻辑方法
}
当Spring Boot应用程序启动时,以下步骤将会发生:
首先,Spring框架通过@ComponentScan注解扫描类路径,找到了BookController、BookService和BookMapper等类,并为它们创建Bean定义,注册到Spring的应用上下文中。
当一个请求到达并需要使用到BookController时,Spring框架会尝试创建一个BookController的Bean实例。
在创建BookController的Bean实例的过程中,Spring框架发现BookController类中需要一个BookService的Bean实例(通过@Autowired注解指定),于是Spring框架会先去创建一个BookService的Bean实例。
同样,在创建BookService的Bean实例的过程中,Spring框架发现BookService类中需要一个BookMapper的Bean实例(通过@Autowired注解指定),于是Spring框架会先去创建一个BookMapper的Bean实例。
在所有依赖的Bean都被创建并注入之后,BookController的Bean实例最终被创建完成,可以处理来自用户的请求了。
在这个过程中,BookController、BookService和BookMapper这三个Bean的创建顺序是有严格要求的,必须按照他们之间的依赖关系来创建。只有当一个Bean的所有依赖都已经被创建并注入后,这个Bean才能被创建。这就是Spring框架的IoC(控制反转)和DI(依赖注入)的机制。
手动获取: 如果你在代码中手动通过ApplicationContext.getBean()方法获取某个Bean,那么Spring也会在这个时候创建对应的Bean实例,如果还没有创建的话。
总的来说,"需要"一个Bean,是指在运行时有其他代码需要使用到这个Bean的实例,这个"需要"可能来源于其他Bean的依赖,也可能来源于框架的调用,或者你手动获取。在这种需要出现时,如果对应的Bean还没有被创建,那么Spring就会根据之前通过@ComponentScan等方式注册的Bean定义,创建对应的Bean实例。
5.2 xml中启用component-scan组件扫描
对应于 @ComponentScan 的 XML 配置是 <context:component-scan> 标签
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="com.example" />
</beans>
在这段 XML 配置中,<context:component-scan> 标签指定了 Spring 需要扫描 com.example 包及其子包下的所有类,这与 @ComponentScan 注解的功能是一样的。
注意:在使用 <context:component-scan> 标签时,需要在 XML 配置文件的顶部包含 context 命名空间和相应的 schema 位置(xsi:schemaLocation)。
5.3 不使用@ComponentScan的组件扫描
如果我们不写@ComponentScan注解,那么这里可以把主程序改为如下:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext("com.example");
Library library = context.getBean(Library.class);
System.out.println(library.getBook().getTitle());
System.out.println(library.getBook().getAuthor());
}
}
AnnotationConfigApplicationContext 的构造方法中有一个是填写basePackages路径的,可以接受一个或多个包的名字作为参数,然后扫描这些包及其子包。
运行结果如下:
在这个例子中,Spring 将会扫描 com.example 包及其所有子包,查找并注册所有的 Bean,达到和@ComponentScan注解一样的效果。
我们也可以手动创建一个配置类来注册bean,那么想要运行得到一样的效果,需要的代码如下:
@Component
public class Book {
private String title = "Java Programming";
private String author = "Unknown";
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
}
@Component
public class Library {
private Book book;
public Book getBook() {
return book;
}
public void setBook(Book book) {
this.book = book;
}
}
@Configuration
public class LibraryConfiguration {
@Bean
public Book book() {
Book book = new Book();
book.setTitle("Java Programming");
book.setAuthor("Unknown");
return book;
}
@Bean
public Library library() {
Library library = new Library();
library.setBook(book());
return library;
}
}
主程序:
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(LibraryConfiguration.class);
Library library = context.getBean(Library.class);
System.out.println(library.getBook().getTitle());
System.out.println(library.getBook().getAuthor());
}
}
我们创建了一个配置类LibraryConfiguration,用于定义Book和Library这两个bean。然后以配置类LibraryConfiguration.class作为输入的来创建Spring的IOC容器(Spring应用上下文就是Spring IOC容器)。
运行结果和前面一样。
注意,在这个例子里,如果你写@ComponentScan,并且SpringApplication.run(Application.class, args);作为Spring上下文,那么这里运行配置类需要去掉Book和Library类的@Component注解,不然会报错A bean with that name has already been defined。这是因为如果同时在 Book 和Library 类上使用了 @Component 注解,而且配置类LibraryConfiguration上使用了@Configuration注解,这都会被 @ComponentScan 扫描到,那么 Book 和 Library的实例将会被创建并注册两次。正确的做法是,要么在配置类中通过 @Bean 注解的方法创建Book 和 Library的实例,要么在 Book 和 Library 类上写 @Component 注解。如果不是第三方库,我们一般选择后者。
为什么要有配置类出现?所有的Bean上面使用@Component,用@ComponentScan注解扫描不就能解决了吗?
我们在使用一些第三方库时,需要对这些库进行一些特定的配置。这些配置信息,我们可能无法直接通过注解或者XML来完成,或者通过这些方式完成起来非常麻烦。而配置类可以很好地解决这个问题。通过配置类,我们可以在Java代码中完成任何复杂的配置逻辑。
假设你正在使用 MyBatis,在这种情况下可能需要配置一个SqlSessionFactory,在大多数情况下,我们无法(也不应该)直接修改第三方库的代码,所以无法直接在SqlSessionFactory类或其他类上添加@Configuration、@Component等注解。为了能够在Spring中使用和配置这些第三方库,我们需要创建自己的配置类,并在其中定义@Bean方法来初始化和配置这些类的实例。这样就可以灵活地控制这些类的实例化过程,并且可以利用Spring的依赖注入功能。
下面是一个使用@Configuration和@Bean来配置MyBatis的例子:
@Configuration
@MapperScan("com.example.demo.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath*:com/example/demo/mapper/*Mapper.xml")
);
return factoryBean.getObject();
}
}
sqlSessionFactory方法创建一个SqlSessionFactoryBean对象,并使用DataSource(Spring Boot默认为你配置的一个Bean)进行初始化。然后,它指定MyBatis mapper XML文件的位置,最后返回SqlSessionFactory对象。
通过这种方式,你可以灵活地配置MyBatis,并将其整合到Spring应用中。这是一种比使用XML配置文件或仅仅依赖于自动配置更为灵活和强大的方式。
6. 组件注册的其他注解
@Controller, @Service, @Repository和@Component 一样的效果,它们都会被 Spring IoC 容器识别,并将类实例化为 Bean。让我们来看这些注解:
@Controller:这个注解通常标注在表示表现层(比如 Web 层)的类上,如Spring MVC 中的控制器。它们处理用户的 HTTP 请求并返回响应。虽然 @Controller 与 @Component 在功能上是类似的,但 @Controller 注解的使用表示了一种语义化的分层结构,使得控制层代码更加清晰。
@Service:这个注解通常用于标注业务层的类,这些类负责处理业务逻辑。使用 @Service 注解表明该类是业务处理的核心类,使得代码更具有语义化。
@Repository:这个注解用于标记数据访问层,也就是数据访问对象或DAO层的组件。在数据库操作的实现类上使用 @Repository 注解,这样Spring将自动处理与数据库相关的异常并将它们转化为Spring的DataAccessExceptions。
在实际开发中,几乎很少看到@Repository,而是利用 MyBatis 的 @Mapper 或 @MapperScan 实现数据访问,通常做法是,@MapperScan 注解用于扫描特定包及其子包下的接口,这些接口被称为 Mapper 接口。Mapper 接口方法定义了 SQL 查询语句的签名,而具体的 SQL 查询语句则通常在与接口同名的 XML 文件中定义。
@MapperScan("com.example.**.mapper") 会扫描 com.example 包及其所有子包下的名为 mapper 的包,以及 mapper 包的子包。 ** 是一个通配符,代表任意深度的子包。
举个例子,以下是一个 Mapper 接口的定义:
package com.example.demo.mapper;
public interface BookMapper {
Book findBookById(int id);
}
对应的 XML 文件(通常位于 resources 目录下,并且与接口在相同的包路径中)
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.BookMapper">
<select id="findBookById" parameterType="int" resultType="com.example.demo.Book">
SELECT title, author FROM book WHERE id = #{id}
</select>
</mapper>
注意:在 XML 文件中的 namespace 属性值必须与 Mapper 接口的全限定类名相同,<select> 标签的 id 属性值必须与接口方法名相同。
然后,在 Spring Boot 的主类上,我们使用 @MapperScan 注解指定要扫描的包:
@SpringBootApplication
@MapperScan("com.example.**.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
这样,MyBatis 就会自动为 UserMapper 接口创建一个实现类(实际上是一个代理对象),并将其注册到 Spring IOC 容器中,你就可以在你的服务类中直接注入 BookMapper 并使用它。
可能有小伙伴注意到了,这几个注解中都有这么一段代码
@AliasFor(
annotation = Component.class
)
String value() default "";
@AliasFor 是 Spring 框架的注解,它允许你在一个注解属性上声明别名。在 Spring 的许多核心注解中,@AliasFor 用于声明一个或多个别名属性。
举个例子,在 @Controller, @Service, @Repository注解中,value() 方法上的 @AliasFor 声明了一个别名属性,它的目标注解是 @Component,具体的别名属性是 value。也就是说,当我们在 @Controller, @Service, @Repository 注解上使用 value() 方法设置值时,实际上也就相当于在 @Component 注解上设置了 name 属性的值。同时,这也表明了 @Controller, @Service, @Repository注解本身就是一个特殊的 @Component。
7. 将注解驱动的配置与XML驱动的配置结合使用
有没有这么一种可能,一个旧的Spring项目,里面有很多旧的XML配置,现在你接手了,想要全部用注解驱动,不想再写XML配置了,那应该怎么兼容呢?
假设我们有一个旧的Spring XML配置文件 old-config.xml:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="oldBean" class="com.example.OldBean" />
</beans>
这个文件定义了一个名为 "oldBean" 的bean。
然后,我们编写一个新的注解驱动的配置类:
package com.example;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;
@Configuration
@ImportResource("classpath:old-config.xml")
public class NewConfig {
@Bean
public NewBean newBean() {
return new NewBean();
}
}
在这个新的配置类中,我们使用 @ImportResource 注解来引入旧的XML配置文件,并定义了一个新的bean "newBean"。@ImportResource("classpath:old-config.xml")告诉Spring在初始化AppConfig配置类时,去类路径下寻找old-config.xml文件,并加载其中的配置。
当我们启动应用程序时,Spring会创建一个 ApplicationContext,这个 ApplicationContext 会包含 old-config.xml 文件中定义的所有beans(例如 "oldBean"),以及 NewConfig 类中定义的所有beans(例如 "newBean")。
package com.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(NewConfig.class);
OldBean oldBean = context.getBean("oldBean", OldBean.class);
NewBean newBean = context.getBean("newBean", NewBean.class);
System.out.println(oldBean);
System.out.println(newBean);
}
}
在以上的main方法中,我们通过使用AnnotationConfigApplicationContext并传入NewConfig.class作为参数,初始化了一个Spring上下文。在这个上下文中,既包含了从old-config.xml导入的bean,也包含了在NewConfig配置类中使用@Bean注解定义的bean。
所以,通过使用 @ImportResource,可以在新的注解配置中引入旧的XML配置,这样就可以在不打断旧的XML配置的基础上逐步迁移至新的注解配置。
上面我们说到类路径,什么是类路径?
resources目录就是类路径(classpath)的一部分。所以当我们说"类路径下"的时候,实际上也包含了"resources"目录。JVM在运行时,会把"src/main/resources"目录下的所有文件和文件夹都添加到类路径中。
例如有一个XML文件位于"src/main/resources/config/some-context.xml",那么可以用以下方式来引用它:
@Configuration
@ImportResource("classpath:config/some-context.xml")
public class AppConfig {
//...
}
这里可以描述为在类路径下的'config'目录中查找'some-context.xml'文件。
为什么说JVM在运行时,会把"src/main/resources"目录下的所有文件和文件夹都添加到类路径中?
当你编译并运行一个Java项目时,JVM需要知道去哪里查找.class文件以及其他资源文件。这个查找的位置就是所谓的类路径(Classpath)。类路径可以包含文件系统上的目录,也可以包含jar文件。简单的说,类路径就是JVM查找类和资源的地方。
在一个标准的Maven项目结构中,Java源代码通常在src/main/java目录下,而像是配置文件、图片、静态网页等资源文件则放在src/main/resources目录下。
当你构建项目时,Maven(或者其他的构建工具,如Gradle)会把src/main/java目录下的.java文件编译成.class文件,并把它们和src/main/resources目录下的资源文件一起复制到项目的输出目录(通常是target/classes目录)。
然后当你运行程序时,JVM会把target/classes目录(即编译后的src/main/java和src/main/resources)添加到类路径中,这样JVM就可以找到程序运行所需的类和资源了。
如果有一个名为application.properties的文件在src/main/resources目录下,就可以使用类路径来访问它,就像这样:classpath:application.properties。在这里classpath:前缀告诉JVM这个路径是相对于类路径的,所以它会在类路径中查找application.properties文件。因为src/main/resources在运行时被添加到了类路径,所以JVM能找到这个文件。
8. 思考总结
8.1 为什么我们需要注册组件,这与Bean注册有什么区别?
在Spring框架中,Bean对象是由Spring IoC容器创建和管理的。通常Bean对象是应用程序中的业务逻辑组件,如数据访问对象(DAO)或其他服务类。
组件注册,或者说在Spring中通过@Component或者其派生注解(@Service, @Controller, @Repository等)标记的类,是告诉Spring框架这个类是一个组件,Spring需要创建它的实例并管理它的生命周期。这样当使用到这个类的时候,Spring就可以自动地创建这个类的实例并注入到需要的地方。
Bean注册和组件注册其实是非常类似的,都是为了让Spring知道它需要管理哪些类的实例。区别在于Bean注册通常发生在配置类中,使用@Bean注解来明确地定义每一个Bean,而组件注册则是通过在类上使用@Component或者其派生注解来告诉Spring,这个类是一个组件,Spring应该自动地为其创建实例。
8.2 什么是组件扫描,为什么我们需要它,它是如何工作的?
组件扫描是Spring的一种机制,用于自动发现应用程序中的Spring组件,并自动地为这些组件创建Bean定义,然后将它们注册到Spring的应用上下文中,我们可以通过使用@ComponentScan注解来启动组件扫描。
我们需要组件扫描是因为它可以大大简化配置过程,我们不再需要为应用程序中的每个类都显式地创建Bean。而是通过简单地在类上添加@Component或者其派生注解,并启动组件扫描,就可以让Spring自动地为我们的类创建Bean并管理它们。
组件扫描的工作过程如下:使用@ComponentScan注解并指定一个或多个包路径时,Spring会扫描这些包路径及其子包中的所有类。对于标记了@Component或者其派生注解的类,Spring会在应用上下文启动时为它们创建Bean,并将这些Bean定义注册到Spring的应用上下文中。当需要使用这些类的实例时,Spring就可以自动注入这些实例。
欢迎一键三连~有问题请留言,大家一起探讨学习----------------------Talk is cheap, show me the code-----------------------
Spring高手之路1——深入理解与实现IOC依赖查找与依赖注入
本文从xml开始讲解,注解后面给出1. 一个最基本的 IOC 依赖查找实例 首先,我们需要明白什么是IOC(控制反转)和依赖查找。在Spring Framework中,控制反转是一种设计模式,可以帮助我们解耦模块间的关系,这样我们就可以把注意力更多地集中在核心的业务逻辑上,而不是在对象的创建和管理上。 依赖查找(Dependency Lookup)是一种技术手段,使得我们的对象可以从一个管理它们的容器(例如Spring的ApplicationContext)中获得它所需要的资源或者依赖。这种查找过程通常是通过类型、名称或者其他的标识进行的。 我们来看一个简单的例子,通过这个例子,你可以理解在Spring中如何实现IOC依赖查找。这个例子的目标是创建一个简单的"Hello World"应用,通过依赖查找,我们将从Spring的容器中获取一个打印"Hello, World!"的Bean。 第一步,我们需要创建一个简单的Java类,我们将其命名为"HelloWorld":public class HelloWorld {
public void sayHello() {
System.out.println("Hello, World!");
}
} 接下来,我们需要在Spring的配置文件中定义这个Bean。这个配置文件通常命名为applicationContext.xml,并放在项目的src/main/resources目录下:<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义一个名为helloWorld的Bean -->
<bean id="helloWorld" class="com.example.demo.HelloWorld" />
</beans> 然后,我们就可以在我们的主程序中通过Spring的ApplicationContext来获取并使用这个Bean:import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String[] args) {
// 创建一个ApplicationContext,加载spring配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 通过id从ApplicationContext中获取Bean
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
// 调用方法
obj.sayHello();
}
}可以在控制台上看到"Hello, World!"的输出。2. IOC 的两种实现方式 首先,让我们开始了解什么是控制反转(IoC)。在传统的程序设计中,我们常常会在需要的地方创建对象,然后使用这些对象来完成一些工作。这种方式存在一个问题,那就是对象之间的耦合度太高。如果我们要改变对象的创建方式或者使用不同的对象来完成同样的工作,我们可能需要修改大量的代码。 为了解决这个问题,IoC的概念被引入。在IoC的设计模式中,对象的创建和维护不再由使用它们的代码来控制,而是由一个容器来控制。这个容器会负责对象的创建、配置和生命周期管理,使得我们的代码可以专注于核心的业务逻辑,而不需要关心对象是如何创建和管理的。这样,当我们需要改变对象的创建方式或者替换对象时,我们只需要修改容器的配置,而不需要修改使用对象的代码。 接下来,让我们来看看IoC的两种实现方式:依赖查找和依赖注入。2.1 依赖查找(Dependency Lookup) 在这种方式中,当一个对象需要一个依赖(比如另一个对象)时,它会主动从容器中查找这个依赖。通常,这个查找的过程是通过类型、名称或者其他的标识来进行的。根据名称查找 在这种方式中,你需要知道你要查找的bean的ID,然后使用ApplicationContext.getBean(String name)方法查找bean。 例如,假设我们有一个Printer类,它需要一个Ink对象来打印信息。在没有使用Spring框架的情况下,Printer可能会直接创建一个Ink对象:public class Printer {
private Ink ink = new Ink();
//...
} 但是在Spring框架中,我们可以将Ink对象的创建交给Spring的容器,然后在Printer需要Ink对象时,从容器中查找获取:public class Ink {
private String color;
public void useInk() {
System.out.println("Using ink...");
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}public class Printer {
private Ink ink;
public void setInk(Ink ink) {
this.ink = ink;
}
public void print() {
ink.useInk();
System.out.println("Printing...");
}
} <bean id="inkId" class="com.example.demo.Ink" />
<bean id="printer" class="com.example.demo.Printer">
<property name="ink" ref="inkId" />
</bean> 在这个配置中, 元素定义了一个bean,id属性给这个bean定义了一个唯一的名字,class属性则指定了这个bean的完全限定类名。这里“printer”这个bean依赖于"ink" bean。当Spring容器初始化“printer”这个bean的时候,它需要查找到正确名称的"ink" bean,并将它们注入到“printer” bean中。 这里ref按照名称inkId注入bean,当调用(Printer) context.getBean("printer")时,属于按名称依赖查找,Spring检查xml配置根据名称"printer"和"inkId"注入对象,关于注入,我们后面再说。 public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Printer printer = (Printer) context.getBean("printer");
} 依赖查找过程是隐含在Spring框架的工作机制中的,开发者在编写XML配置的时候,并不需要显式进行查找操作。根据类型查找 在这种方式中,你不需要知道bean的ID,只需要知道它的类型。然后,你可以使用ApplicationContext.getBean(Class requiredType)方法查找bean。 还是刚刚的Printer类和Ink类,还需要在XML配置文件中定义你的bean。以下面的方式定义Ink和Printer类:这里有多个相同类型的bean,如果按照类型注入,则编译器会报错,如下: 我们得按照名称注入,autowire="byType"会直接编译报错,就像有多个一样类型的bean,使用@Autowired注解就会报错。所以这里在printer bean里面指明ink bean <bean id="ink1" class="com.example.demo.Ink">
<property name="color" value="Black"/>
</bean>
<bean id="ink2" class="com.example.demo.Ink">
<property name="color" value="Blue"/>
</bean>
<bean id="printer" class="com.example.demo.Printer">
<property name="ink" ref="ink1"/>
</bean> 这里,我们定义了两个类型为Ink的bean,ink1和ink2,他们的颜色属性分别为“Black”和“Blue”。另外,我们还定义了一个类型为Printer的bean,注入ink1依赖。 然后在Java代码中,可以按照类型获取Ink类型的bean:ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
// 按类型获取bean
// 注意,这里存在多个Ink类型的bean,这行代码会抛出异常
// Ink ink = context.getBean(Ink.class);
// 如果存在多个Ink类型的bean,你可以这样获取所有的Ink bean
Map<String, Ink> beans = context.getBeansOfType(Ink.class);
for (String id : beans.keySet()) {
System.out.println("Found ink with id: " + id);
Ink inkBean = beans.get(id);
// do something with inkBean...
} 在这个例子中,context.getBean(Ink.class)会按照类型查找Ink的bean。但是这里有多个Ink类型的bean(如本例所示),这种方式会抛出异常。对于这种情况,你可以使用context.getBeansOfType(Ink.class)方法获取所有类型为Ink的bean,然后自行决定如何使用这些bean。2.2 依赖注入(Dependency Injection)通过类型进行依赖注入还是和之前一样的Printer和Ink类:public class Ink {
private String color;
public void useInk() {
System.out.println("Using ink...");
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
}public class Printer {
private Ink ink;
public void setInk(Ink ink) {
this.ink = ink;
}
public void print() {
ink.useInk();
System.out.println("Printing...");
}
} 在这个例子中,Printer类有一个Ink类型的属性ink,并且有一个setter方法setInk用于设置这个属性的值。这就是我们通常说的依赖注入的setter方法。 这里我们没有使用 Spring 的 @Autowired 注解,而是使用了 Java 的 setter 方法。你可以通过在 Spring 配置文件中使用 和 标签来配置这种依赖关系 在Spring的XML配置文件中,我们可以如下配置: <bean id="ink" class="com.example.demo.Ink">
<property name="color" value="Blue"/>
</bean>
<bean id="printer" class="com.example.demo.Printer" autowire="byType">
</bean> 注意,我们这里加上属性autowire="byType",这样Spring就会自动根据类型来进行依赖注入。虽然autowire="byType"会启用按类型的自动注入,但如果显式配置了ink属性的值,Spring会使用你的配置,而不是按类型自动注入。 当我们需要使用Printer对象时,我们可以从Spring容器中获取:ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Printer printer = (Printer) context.getBean("printer");
printer.print(); 当我们调用context.getBean("printer")时,会查找id为printer的bean对象,Printer对象会使用被Spring容器注入的Ink对象来打印信息。Using ink...
Printing...在这个例子中,依赖查找实际上应用于两个地方:当使用context.getBean("printer")时,实际上正在查找名为"printer"的Bean对象,即Printer对象,这是一个明显的依赖查找的例子。当Spring框架将Ink对象注入Printer对象时,Printer对象在内部是通过依赖查找方式来找到Ink对象的。在这种情况下,Spring框架控制了这个查找过程,开发者并没有直接进行依赖查找。在这个过程中,Spring框架根据配置文件中定义的依赖关系(这里是类型依赖),自动找到Ink对象,并将它注入到Printer对象中。这个查找过程是隐式的,对开发者是透明的。所以总的来说,依赖查找可以发生在我们手动从容器中获取Bean的时候,也可以发生在Spring自动注入依赖的过程中。 通过这种方式,我们不再需要在Printer类中直接创建Ink对象,而是让Spring容器来管理Ink对象的创建和注入,从而实现了Printer和Ink之间的解耦。通过名称进行依赖注入首先,我们修改Ink类,添加构造方法,使其可以有多种颜色:public class Ink {
private String color;
public Ink(String color) {
this.color = color;
}
public void useInk() {
System.out.println("Using " + color + " ink...");
}
}然后,在Spring的XML配置文件中,我们定义两种颜色的墨水,以及一个打印机:<bean id="blackInk" class="com.example.demo.Ink">
<constructor-arg value="black" />
</bean>
<bean id="blueInk" class="com.example.demo.Ink">
<constructor-arg value="blue" />
</bean>
<bean id="printer" class="com.example.demo.Printer">
<property name="ink" ref="blackInk" />
</bean> 在这个配置中,我们有两种颜色的墨水:黑色和蓝色。在printer bean的定义中,我们通过ref属性指定我们想要注入的墨水的id为"blackInk"。在 元素中,name 和 ref 属性有不同的作用: name 属性指的是要注入的目标 bean(这里是 "printer")的属性名。这个属性名应该在目标 bean 的类(在这个例子中是 com.example.demo.Printer 类)中存在,且应该有相应的 setter 方法。比如,如果 name="ink",那么 com.example.demo.Printer 类中需要有一个名为 ink 的属性,且有一个名为 setInk(...) 的方法。 ref 属性指的是要注入的源 bean 的 ID。这个 ID 应该在同一份 Spring 配置文件中定义过。比如,如果 ref="blackInk",那么应该在配置文件中有一个 的定义。 因此,当你看到 这样的配置时,你可以理解为:Spring 容器会找到 ID 为 "blackInk" 的 bean(在这个例子中是 com.example.demo.Ink 类的一个实例),然后调用 com.example.demo.Printer 类的 setInk(...) 方法,将这个 Ink 实例注入到 Printer 实例的 ink 属性中。最后,我们可以在一个Java类中获取printer bean并调用其print方法:ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
Printer printer = context.getBean("printer", Printer.class);
printer.print();这将输出以下结果:Using black ink...
Printing... 这里体现的通过名称进行依赖注入的含义是:我们通过指定bean的id(在这里是"blackInk"),来明确地告诉Spring我们想要注入哪一个bean。这是与通过类型进行依赖注入的一个主要区别:通过类型进行依赖注入时,Spring会自动选择一个与目标属性类型匹配的bean进行注入,而不需要我们明确指定bean的id。3. 在三层架构中的 service 层与 dao 层体会依赖查找与依赖注入的使用在三层架构中,我们通常会有以下三层:表示层(Presentation Layer):与用户进行交互的层次,如前端页面、命令行等,比如Controller类里的逻辑。业务逻辑层(Business Logic Layer),也叫作 Service 层:实现业务逻辑的地方。数据访问层(Data Access Layer),也叫作 DAO 层:直接操作数据库或者调用API来获取数据。在本例中,我们将只关注 Service 层与 DAO 层,并且会使用到 Spring 框架。首先,让我们定义一个业务对象,比如一个用户(User):public class User {
private String id;
private String name;
private String email;
// getters and setters omitted for brevity
} 接着,我们定义 DAO 层。在 DAO 层中,我们通常会有一些方法来操作数据库,如创建用户,获取用户,更新用户等。在本例中,我们简化为一个接口:public interface UserDao {
User getUser(String id);
} 在实际项目中,我们可能有多种 UserDao 的实现,比如 MySQLUserDao、MongoDBUserDao 等。在这里,我们简化为一个模拟实现:public class UserDaoImpl implements UserDao {
public User getUser(String id) {
// 为了简化,我们在这里直接返回一个静态的 User 对象
User user = new User();
user.setId(id);
user.setName("Test User");
user.setEmail("test@example.com");
return user;
}
} 然后,我们定义 Service 层。在 Service 层中,我们会调用 DAO 层的方法来完成业务逻辑:public class UserService {
private UserDao userDao;
public User getUser(String id) {
return userDao.getUser(id);
}
} 这里的问题是,UserService 需要一个 UserDao 的实例,但是 UserService 并不知道如何获取 UserDao 的实例。这就是我们要解决的问题,我们可以使用依赖查找或者依赖注入来解决。1.依赖查找:我们可以修改 UserService 类,让它从 Spring 容器中获取 UserDao 的实例:public class UserService {
private UserDao userDao;
public UserService() {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
this.userDao = (UserDao) context.getBean("userDao");
}
public User getUser(String id) {
return userDao.getUser(id);
}
}这样,当我们创建 UserService 的实例时,它会自动从 Spring 容器中获取 UserDao 的实例。2.依赖注入: 我们可以利用 Spring 框架的依赖注入特性,让 Spring 容器自动将 UserDao 的实例注入到 UserService 中。为此,我们需要在 UserService 中添加一个 setter 方法,并在 Spring 的配置文件中配置这个依赖关系:UserService 类:public class UserService {
private UserDao userDao;
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public User getUser(String id) {
return userDao.getUser(id);
}
}Spring 配置文件(applicationContext.xml):<bean id="userDao" class="com.example.UserDaoImpl" />
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao" />
</bean> 这样,当我们从 Spring 容器中获取 UserService 的实例时,Spring 容器会自动将 UserDao 的实例注入到 UserService 中:ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
User user = userService.getUser("1");如果你想在代码中获取这个bean实例,你需要做两件事情:创建一个 Spring 应用上下文(ApplicationContext)对象。这个对象会读取你的 Spring 配置文件,然后根据配置文件的内容创建和管理bean实例。在你的代码中,new ClassPathXmlApplicationContext("applicationContext.xml") 就是在创建一个 Spring 应用上下文对象。"applicationContext.xml" 是你的 Spring 配置文件的路径。调用 context.getBean("userDao") 来获取 "userDao" 这个bean的实例。Spring 会根据你的配置创建一个 UserDaoImpl 类的实例,并返回给你。4. 使用注解时,依赖查找在哪里查找?依赖注入在哪里注入?当我们全部用注解,不用xml来实现的时候,会写成如下形式:Ink.java@Component
public class Ink {
public void useInk() {
System.out.println("Using ink...");
}
}Printer.java@Component
public class Printer {
@Resource
private Ink ink;
public void print() {
ink.useInk();
System.out.println("Printing...");
}
} @Resource注解告诉Spring,请查找一个名为"ink"的Bean并注入到这个字段中。注意,@Resource的名称是大小写敏感的,因此"ink"和"Ink"是两个不同的名字。 @Resource 注解的 name 属性应与 @Component 注解中指定的名称相匹配,如果@Component 没有指定name属性,那么bean的名称默认是类名的小写形式。 如果省略了@Resource注解的name属性,则默认的注入规则是根据字段的名称进行推断,并尝试注入同名的资源,如果Ink类上注解为@Component("inkComponent"),那这个bean的名称就是inkComponent,只能写成如下形式@Resource(name = "inkComponent")
private Ink ink;
或者
@Resource
private Ink inkComponent; 为了使这些注解起作用,我们需要开启注解扫描@ComponentScan("com.example"):@Configuration
@ComponentScan("com.example")
public class AppConfig {
// 可以在这里定义其他的配置或进行其他的注解配置
} 然后,你可以在你的Java代码中通过AnnotationConfigApplicationContext类获取Printer的Bean并调用它的print方法:ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
Printer printer = context.getBean(Printer.class);
printer.print();输出以下结果:Using ink...
Printing... 这个例子中,我们使用了完全基于Java的配置,没有使用任何XML。通过@Configuration和@ComponentScan注解,我们告诉了Spring在哪里找到我们的Bean。这个例子中context.getBean(Printer.class);按照类型进行了依赖查找@Resource按照名称进行了依赖注入@Resource在按名称依赖注入的时候会隐式按名称依赖查找 有没有人发现这次Printer不用强制转换了?之前还是(Printer) context.getBean("printer");呢。这里把类型都传进去了,ApplicationContext 查找的时候当然按照这个类型查找啊 依赖注入中的按名称和按类型两种方式,主要体现在注入时如何选择合适的bean进行注入。按名称进行依赖注入: 是指在进行依赖注入时,根据名称来查找合适的bean。比如在Java代码中使用@Resource(name = "beanName")。这种方式的优点是明确指定了注入的bean。按类型进行依赖注入: 是指在进行依赖注入时,根据类型来查找合适的bean。比如在Java代码中使用@Autowired。缺点是当有多个相同类型的bean存在时,可能会导致选择错误的bean。 至于context.getBean()方法,这是依赖查找的方式,而不是依赖注入。它也分为按名称和按类型两种方式,与依赖注入的按名称和按类型是类似的。使用context.getBean("beanName"),是按名称进行依赖查找。使用context.getBean(ClassType)或者context.getBean("beanName", ClassType),是按类型进行依赖查找。如果有多个同类型的bean,则会抛出异常。 对于依赖查找,除了可以使用 context.getBean 进行显示的查找外,Spring 容器在实例化 Bean 的过程中也会进行隐式的依赖查找。此外,Spring 还支持使用 @Autowired、@Resource、@Inject 等注解在依赖注入之前隐式依赖查找。 对于依赖注入,通过 XML 配置文件中的 和 进行属性和构造器注入,以及通过 @Autowired 和 @Resource 注解进行注入。但还要注意,Spring 还支持通过 @Value 注解对简单类型的值进行注入,以及通过 @Qualifier 注解对同一类型的不同实例进行精确选择。5. @Autowired 进行自动注入时,如果存在多个同类型的 bean该如何解决? 在使用 @Autowired 进行自动装配时,如果存在多个同类型的 bean,Spring 会抛出一个 NoUniqueBeanDefinitionException 异常,表示无法确定要注入哪个 bean。为了避免这种错误,可以使用以下方法之一:使用 @Qualifier 注解: @Qualifier 注解可以与 @Autowired 一起使用,通过指定 bean 的名称来解决歧义。 下面的例子中创建了两个Ink Bean,分别是blueInk和redInk。然后在Printer类中,我们通过@Qualifier注解指定需要注入的Bean的名称。@Component
public class BlueInk implements Ink {
//...
}
@Component
public class RedInk implements Ink {
//...
}
@Component
public class Printer {
@Autowired
@Qualifier("blueInk")
private Ink ink;
public void setInk(Ink ink) {
this.ink = ink;
}
public void print() {
ink.useInk();
System.out.println("Printing...");
}
}注意:@Autowired和@Qualifier("blueInk")写在setInk方法上和写在ink字段定义上是一样的。使用 @Primary 注解: 可以使用@Primary注解来标识优先注入的Bean。如果在容器中存在多个同类型的Bean,Spring会优先注入被@Primary注解标记的Bean。@Component
@Primary
public class BlueInk implements Ink {
//...
}
@Component
public class RedInk implements Ink {
//...
}
@Component
public class Printer {
private Ink ink;
@Autowired
public void setInk(Ink ink) {
this.ink = ink;
}
public void print() {
ink.useInk();
System.out.println("Printing...");
}
} 在上面的例子中,我们使用@Primary注解标记了BlueInk,所以在注入Ink类型的Bean时,Spring会优先选择BlueInk。 需要注意的是,@Qualifier注解的优先级高于@Primary注解,也就是说如果同时使用了@Qualifier和@Primary注解,Spring会优先考虑@Qualifier注解。6. 【面试题】依赖查找与依赖注入的对比 依赖查找(Dependency Lookup)和依赖注入(Dependency Injection)都是在控制反转(Inversion of Control,IoC)的背景下,解决对象间依赖关系的方式,但它们在实现方式上有所不同。依赖查找(Dependency Lookup) 依赖查找是一种主动的依赖解决方式。在实际的编程中,我们需要显式地调用API(如ApplicationContext.getBean())来获取所需要的依赖。如果查找的对象里面还有其他对象,则会进行隐式依赖查找,也就是说,查找的对象里面的依赖也会被Spring自动注入。 依赖查找的一个缺点是它会使代码和Spring框架紧密耦合。这意味着如果我们想要改变依赖解决方式或者更换其他的IoC容器,我们可能需要改动大量的代码,比如改变要查找的bean名称。依赖注入(Dependency Injection) 依赖注入是一种被动的依赖解决方式。与依赖查找相比,依赖注入不需要我们显式地调用API,而是由Spring容器负责将依赖注入到需要它的Bean中。 依赖注入的主要优点是它能够减少代码和Spring框架的耦合度,使我们的代码更加易于测试和维护。同时,它也支持更加复杂的依赖关系,包括循环依赖和自动装配等。 依赖注入通常分为基于构造器的依赖注入和基于setter的依赖注入,也可以通过使用注解(如@Autowired, @Resource等)来实现。 总结来说,依赖查找和依赖注入各有优缺点。选择使用哪种方式,主要取决于实际的需求和场景。在实际的开发中,我们通常会更倾向于使用依赖注入,因为它能够提供更高的灵活性和可维护性。比如使用对象时全都是加上@Autowired或@Resource注解后直接使用,调用被注入对象的方法,不会再去用API查找对象。如果看完本篇还有疑问,下篇:Spring高手之路——深入理解注解驱动配置与XML配置的融合与区别欢迎一键三连~有问题请留言,大家一起探讨学习----------------------Talk is cheap, show me the code-----------------------