python web -- flask
Flask是一个简洁的 Python_web 框架.
零. virtualenv 虚拟环境配置.
$ easy_install pip
$ pip install virtualenv
$ virtualenv venv # 创建名称为 venv 的虚拟环境
$ source venv/bin/active # 进入 venv 虚拟环境
(venv) $ pip install flask # 在虚拟环境中安装 flask 包
(venv) $ deactivate # 从虚拟环境退出
一. 基本配置
1. 创建并启动程序实例
示例 :
from flask import Flask
app = Flask(__name__) # Flask 类的构造函数只有一个必须制定的参数, 即程序主模块或包的名字.
# route here
if __name__ == "__main__": # 程序启动后进入轮训, 等待并处理请求.
app.run(debug=True) # 启动服务器,并启用调试模式, 激活调试器和重载程序.
2. 配置
app.config 字典用来存储框架, 扩展和程序本身的配置变量. 使用标准的字典语法就可把配置值添加到 app.config 对象中.
示例代码 :
from flask import Flask
app = Flask(app)
app.config["SECRET_KEY"] = "hard to guess string"
该对象还提供了一些其他方法, 可以从文件或环境中导入配置值.
3. 路由 = 装饰器 + 视图函数.
处理URL 和 视图函数 之间映射关系的程序成为路由.
1.生成路由映射
1. app.route 装饰器
最简单方式是使用 app.route 装饰器.
示例代码 :
@app.route("/", methods=["GET", "POST"]) # methods 为请求方法.
def index(): # 视图函数.
return "<h1>Hello World</h1>"
@app.route("/user/<name>") # 尖括号中的就是动态内容, 任何能匹配静态部分的 URL 都会映射到这个路由上. 调用视图函数时, Flaks 会将动态部分作为参数传入函数.
def user(name):
return "<h1>Hello , %s</h1>" % name
动态视图函数类型定义 :
int : 匹配动态片id 为整数的 URL. `/user/<ind:id>`
float : 浮点数
path : path 类型也是字符串, 但不把斜线视作分隔符, 而将其视为动态片段的一部分. `/user/<path:dir_path>`
2. app.add_url_role()
3. app.errorhandler() 错误页面
示例代码 :
@app.errorhandler(404)
def page_not_found(e):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_server_error(e):
return render_template('500.html'), 500
2.查看路由映射
>> from hello import app
>> app.url_map
Map([<Rule '/' (HEAD, POST, OPTIONS, GET) -> main.index>,
<Rule '/static/bootstrap/<filename>' (HEAD, OPTIONS, GET) -> bootstrap.static>,
<Rule '/static/<filename>' (HEAD, OPTIONS, GET) -> static>])
二. 请求-响应模型.
HTTP 协议的基本模型即 请求 <---> 响应.
1. 上下文
Flask 使用上下文, 让特定的变量在一个线程中全局可访问, 而不会干扰其他线程.
Flask 中的上下文包含 程序上下文 和 请求上下文 .
Flask 在分发请求之前激活(或推送)程序和请求上下文, 请求处理完成后将其删除.
程序上下文本推送后, 就可以在线程中使用 current_app 和 g 变量. 请求上下文本推送后, 就可以使用 request 和 session 变量.
如果使用这些变量时, 没有激活程序上下文或请求上下文, 就会导致错误.
很多 Flask 扩展都假设已经存在激活的程序上下文和请求上下文, 所以 使用不同线程执行 Flask 扩展时, 需要手动激活上下文.
1. 程序上下文
变量名称 | 上下文 | 说明 |
---|---|---|
current_app | 程序上下文 | 当前激活程序的程序实例 |
g | 程序上下文 | 处理请求时做临时存储的对象. 每次请求都会重设这个变量 |
2. 请求上下文
变量名称 | 上下文 | 说明 |
---|---|---|
request | 请求上下文 | 请求对象, 封装了客户端发出的 HTTP 请求中的内容 |
session | 请求上下文 | 用户会话, 用于存储请求之间需要 "记住" 的值的字典 |
示例 : 在视图函数中使用上下文
from flask import request
@app.route('/')
def index():
user_agent = request.headers.get("User-Agent")
return "<p>Your browser is %s</p>" % user_agent
示例 : 在 shell 中使用上下文(手动推送上下文).
>> from hello import app # hello 为程序主文件
>> from flask import current_app
>> current_app.name # 未激活程序上下文就调用 current_app 导致错误.
Traceback (most recent call last):
...
RuntimeError : working outside of application context.
>> app_ctx = app.app_context() # 获取程序上下文
>> app_ctx.push() # 推送上下文
>> current_app.name
"hello"
2. 请求钩子(注册通用函数)
Flask 提供了注册通用函数的功能, 注册的函数可在请求被分发到视图函数之前或之后调用.
请求钩子函数 | 说明 |
---|---|
before_first_request | 注册一个函数, 在处理第一个请求之前运行. |
before_request | 注册一个函数, 在每次请求之前运行. |
after_request | 注册一个函数, 如果没有未处理的异常抛出, 在每次请求之后运行. |
teardown_request | 注册一个函数, 即使有未处理的异常抛出, 在每次请求之后运行. |
在请求钩子函数和视图函数之间共享数据, 一般使用上下文全局变量 g .
before_request 钩子在应用到蓝本时, 只能对应到针对蓝本的请求上; 如想在蓝本中使用针对程序全局请求的钩子, 必须使用 before_app_request .
示例代码 :
@auth.before_app_request
def before_request():
if current_user.is_authenticated() and not current_user.confirmed and \
request.endpoint[:5] != 'auth.' and \
request.endpoint != 'static':
return redirect(url_for("auth.unconfirmed"))
3. 响应 : 响应内容, 响应码, 响应首部.
Flask 返回响应有 4 种方式:
return : 在视图函数中返回响应.
return "响应内容或模板", 响应码, {响应首部字典}
Response 对象 : flaks.make_response()
make_response() 函数接受 1个, 2个, 或 3个参数, 并返回一个 Response 对象.
示例代码 :
from flask import make_response
@app.route("/")
def index():
response = make_response("<p>This Document carries a cookie !</p>")
response.set_cookie("answer", "42")
return response
重定向 .
重定向是一种特殊的响应类型, 这种响应没有页面文档, 只会告诉浏览器一个新地址, 浏览器在得到新地址之后, 自动重新请求加载新地址.
重定向可以使用 3个 值形式的返回值生成, 可在 Response 对象中设定. 但是 Flask 提供了 redirect() 辅助函数, 用于生成这种响应.
示例代码 :
from flask import redirect
@app.route("/")
def index():
return redirect("http://www.example.com")
abort() :
abort() 处理错误.
示例代码 :
from flask import abort
@app.route("/user/<id>")
def get_user(id):
user = load_user(id)
if not user:
abort(404)
return "<h1>Hello , %s !</h1>" % user.name
注意 : abort 不会把控制权交给调用它的函数, 而是抛出异常把控制权交给 web 服务器.
三. 大型程序组织结构
Flaks 并不强制要求大型项目使用特定的组织方式, 程序的结构组织方式完全有开发者自己决定.
1. 项目结构示例
├── app
│ ├── __init__.py
│ ├── email.py
│ ├── models.py
│ ├── main /
│ │ ├── __init__.py
│ │ ├── errors.py
│ │ ├── forms.py
│ │ ├── views.py
│ ├── static /
│ └── templates /
├── config.py
├── manage.py
├── venv /
├── migrations /
├── requirements.txt
└── tests /
├── __init__.py
├── test_*.py
文件夹类 :
app : Flask 程序
migrations : 数据库迁移脚本
tests : 单元测试脚本
venv : 虚拟环境
文件类 :
requirements.txt : 程序依赖包
config.py : 存储配置
manage.py : 启动脚本及程序任务.
2. 配置选项 : 层次的配置类.
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
FLASKY_MAIL_SENDER = 'Flasky Admin <flasky@example.com>'
FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
MAIL_SERVER = 'smtp.googlemail.com'
MAIL_PORT = 587
MAIL_USE_TLS = True
MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD')
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data.sqlite')
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
基类 Config 中包含通用配置, 子类分别定义专用的配置. 如果需要还可以添加其他的配置类.
配置类可以定义 init_app() 类方法, 其参数是程序实例. 在这个方法中, 可以执行对当前环境的配置初始化. 现在 , 基类 Confifg 中的 init_app() 方法为空.
config 字典中注册了不同的配置环境, 而且还注册了一个默认配置(开发环境).
3. 程序包
程序包用来保存程序的所有代码,模板和静态文件.
1) 程序工厂函数
在单个文件中开发程序很方便, 但却有个很大的缺点, 因为程序在全局作用域中创建, 所以无法动态修改配置. 运行脚本时, 程序实例已经创建完毕, 再修改配置为时已晚. 这一点对单元测试尤其重要, 因为有事为了提高测试覆盖度, 必须在不同的配置环境中运行程序.
解决办法就是 : 延迟创建程序实例, 把创建过程移到可现实调用的工厂函数中. 这种方法不仅可以给脚本流出配置程序的时间, 还能够创建多个程序实例, 这些实例有时在测试中非常有用.
构造文件示例 :
# app/__init__.py
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_moment import Moment
from flask_mail import Mail
from flask_sqlalchemy import SQLAlchemy
from config import config
bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
db = SQLAlchemy()
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
bootstrap.init_app(app)
mail.init_app(app)
moment.init_app(app)
db.init_app(app)
# 附加路由和自定义的错误页面
return app
构造文件导入了大多数正在使用的 Flask 扩展. 由于尚未初始化所需的程序实例, 所以没有初始化扩展, 创建扩展类时, 没有向构造函数传入参数.
create_app() 函数就是程序的工厂函数, 接受一个 参数, 就是程序使用的配置名称. 配置类在 config.py 文件中定义, 其中保存的配置可以使用 Flaks app.config 配置对象提供的 from_object() 方法直接导入程序. 至于配置对象, 则可以通过名字从 config 字典中选择. 程序创建并配置好后, 就能初始化扩展了. 在之前创建的扩展对象上调用 init_app() 可以完成初始化过程.
工厂函数返回创建的程序示例, 不过,现在工厂函数创建的程序还不完整, 因为没有路由和自定义的错误页面处理程序.
2) 蓝本
转换成程序工厂函数的操作让定义路由变复杂了. 在当脚本程序中, 程序实例存在于全局作用域中, 路由可以直接使用 app.route 装饰器定义. 但现在程序在运行时创建, 只有调用 create_app() 之后才能使用 app.route 装饰器, 这是定义路由就太晚了. 错误页面处理程序使用 app.errorhandler 装饰器, 也面临同样的困难.
解决方法 : 蓝本
创建蓝本
蓝本中可以定义路由, 不同的是, 在蓝本中定义的路由处于休眠状态, 直到蓝本注册到程序上后, 路由才真正成为程序的一部分. 使用位于全局作用域中的蓝本时, 定义路由的方法几乎和单脚本程序一样.
蓝本可以在单个文件中定义, 也可使用更结构化的方式在包中的多个模块中创建.
为了获取更大的灵活性, 程序包中创建了一个子包, 用于保存蓝本.
示例 :
# app/main/__init__.py
from flask import Blueprint
main = Blueprint('main', __name__) # 通过实例化一个 Blueprint 类对象创建蓝本, 该构造函数必须制定两个参数 : 蓝本名称 , 蓝本所在的包或模块(__name__即可).
from . import views, errors # views 模块保存程序路由, errors 模块保存页面错误处理程序. 导入这两个模块,可以路由和错误处理程序与蓝本关联起来.
** views 和 errors 在 __init__.py 脚本的末尾导入, 是为了避免循环导入依赖 ,因为在 两个模块中, 还有导入 蓝本 main.
注册蓝本
蓝本在 工厂函数 create_app() 中注册到程序上.
示例 :
# app/__init__.py
#...
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
from .auth import auth as auth_blueprint
app.register_blueprint(auth_blueprint, url_prefix="/auth") # url_prefix 使得蓝本中定义的路由都会加上指定前缀.
return app
蓝本中的错误处理程序
在蓝本中编写错误处理程序, 如果使用 errorhandler 装饰器, 那么只有蓝本中的错误才能触发处理程序; 要想注册程序全局的错误处理程序, 必须使用 app_errorhandler .
示例 :
# app/main/errors.py
from flask import render_template
from . import main
@main.app_errorhandler(404)
def page_not_fount(e):
return render_template('404.html'), 404
@main.app_errorhandler(500)
def page_not_fount(e):
return render_template('500.html'), 500
蓝本中的路由程序
蓝本中的视图函数,与单脚本中的视图函数有两点不同:
- 路由装饰器由蓝本提供.
- url_for() 的用法不同. 需要加上蓝本的名字. 如下 :
url_for('main.index') # 全局通用
url_for('.index') # 简写形式, 只在当前蓝本中使用.
flask 为蓝本中的全部断点加上了一个命名空间, 这样在不同蓝本中使用相同的端点名称定义视图函数, 就不会产生冲突了. 命令空间的名称就是蓝本的名称. 这也是 url_for() 写法不同的原因.
示例 :
# app/main/views.pys
from datetime import datetime
from flask import render_template, session, redirect, url_for
from . import main
from .forms import NameForm
from .. import db
from ..models import User
@main.route("/", methods=["GET", "POST"])
def index():
form = NameForm()
if form.validate_on_submit():
# ...
return redirect(url_for(".index"))
return render_template("index.html",
current_time=datetime.utcnow(),
form=form,
name=session.get("name"),
known=session.get("known"))
4. 启动脚本
顶级文件夹中的 manage.py 用于启动程序.
示例 :
# manage.py
import os
from app import create_app, db
from app.models import User, Role
from flask_script import Manager, Shell
from flask_migrate import Migrate, MigrateCommand
app = create_app(os.getenv("FLASK_CONFIG" or "default")) # 创建程序.
manager = Manager(app) # 初始化 Flask-Script
migrate = Migrate(app, db) # 初始化 Flask-Migrate
def make_shell_context():
return dict(app=app, db=db, Role=Role, User=User)
manager.add_command("shell", Shell(make_context=make_shell_context))
manager.add_command("db", MigrateCommand)
if __name__ == "__main__":
manager.run()
5. 需求文件
生成需求文件 :
$ pip freeze > requirements.txt
依据需求文件创建新的虚拟环境 :
$ pip install -r requirements.txt
配置文件的导入包含一般在开发环境中, 可以将 requirements.txt 文件替换为 requirements 文件夹, 其中包含不同环境的配置文件 :
common.txt # 基础包
prod.txt # 生产专用包
demo.txt # 测试专用包
其中 prod.txt 和 demo.txt 可以从 common.txt 中导入, 而无需重复包名, 格式如下:
$ cat demo.txt
-r common.txt
ForgerPy==0.1
6. 创建数据库
要在新数据库中创建数据表。如果使用 Flask-Migrate 跟踪迁移,可使用如下命令创建数据表或者升级到最新修订版本:
$ python manage.py shell
> db.create_all()
$ python manage.py db upgrade
四. Flask 模板
1. Jinja2
2. Mako
五. Flask 其他
1. url_for()
url_for() 辅助函数 : 使用程序 URL 映射中保存的信息, 生成 URL. 可用于 视图函数中, 或者 Jinja2 模板中.
相对地址
url_for(view_func)
示例 :
url_for('index')
绝对地址
url_for(view_func, exterual=True)
动态地址
url_for(view_func, key=value)
动态参数
url_for(view_func, page=2)
静态文件
url_for('static', filename="filename")
在 蓝本 中
url_for(Blueprint_Name.view_func)
2. Flash 消息
消息是 Flask 的核心特性.
1) 在视图函数中使用消息
示例代码 :
from flask import flash
@app.route("/", methods=["GET", "POST"])
def index():
form = NameForm()
if form.validate_on_submit():
old_name = session.get("name")
if old_name is not None and old_name != form.name.data :
flash("Looks lick you have changed your name !")
session["name"] = form.name.data
return redirect(url_for('index'))
return render_template('index.html', form=form, name=session.get("name"))
2) 在模板中渲染消息.
Flask 把 get_flashed_messages() 函数开放给模板, 用来获取并渲染消息.
仅在 视图函数中调用 flash() 函数并不能把消息显示出来, 程序使用的模板需要渲染这些消息. 最好在基模板中渲染 Flash 消息, 因为这样所有的页面都能使用这些消息.
示例代码 :
{% block content %}
<div class="container">
{% for message in get_flashed_messages() %}
<div class="alert alert-warning">
<button type="button" class="close" data-dismiss="alert" >×</button>
{{ message }}
</div>
{% endfor %}
{% block page_content %}{% endblock %}
</div>
{% endblock %}
在模板中使用循环是因为在之前的请求循环中每次调用 flash() 函数都会生成一个消息, 所以可能有很多消息在排队等待显示.
get_flash_messages() 函数获取的消息在下次调用时不会再次返回, 因此 Flash 消息只显示一次, 然后就消失了.
六. Flask 扩展
Flask 被设计为可扩展模式, 故而没有提供一些重要的功能, 如数据库和用户认证, 所以开发者可以自由选择最合适程序的包, 或者自行开发.
专为 Flask 开发的扩展都暴露在 flask.ext 命名空间下.
Flask 扩展的通用初始化方法 : 把程序实例作为参数传给构造函数, 初始化主类的实例. 创建的对象可以在各个扩展中使用.
- werkzeug : WSGI 工具集
- flask-script
- flask-moment
- flask-wtf : 表单处理
- flask-mail : 邮件发送
- flask-sqlalchemy : SQL ORM
- flask-migrate : 数据库迁移
- flask-login : 登录用户管理
- flask-pagedown : Markdown 支持
- flask-HTTPAuth : HTTP 认证
- Flask-Babel : 提供国际化和本地化支持。
- FLask-RESTful : 开发 REST API 的工具。
- Celery : 处理后台作业的任务队列。
- Frozen-Flask : 把 Flask 程序转换成静态网站。
- Flask-Debug Toolbar : 在浏览器中使用的调试工具。
- Flask-Assets : 用于合并、压缩、编译 CSS 和 Java Script 静态资源文件。
- Flask-OAuth : 使用 OAuth 服务进行认证。
- Flask-Open ID : 使用 Open ID 服务进行认证。
- Flask-Whoosh Alchemy : 使用 Whoosh 实现 Flask-SQLAlchemy 模型的全文搜索。
- Flask-KVsession : 使用服务器端存储实现的另一种用户会话。
七. Flask 信号
pip search blinker
八. 参考链接
Flask web 开发 : 基于Python的Web应用开发实战
python web -- flask的更多相关文章
- Python Web Flask源码解读(一)——启动流程
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- Python Web Flask源码解读(二)——路由原理
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- Python Web Flask源码解读(三)——模板渲染过程
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- Python Web Flask源码解读(四)——全局变量
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- virtualenv 环境下 Nginx + Flask + Gunicorn+ Supervisor 搭建 Python Web
在这篇文章里,我们将搭建一个简单的 Web 应用,在虚拟环境中基于 Flask 框架,用 Gunicorn 做 wsgi 容器,用 Supervisor 管理进程,然后使用 Python 探针来监测应 ...
- 通过uwsgi+nginx启动flask的python web程序
通过uwsgi+nginx启动flask的python web程序 一般我们启动python web程序的时候都是通过python直接启动主文件,测试的时候是可以的,当访问量大的时候就会出问题pyth ...
- python三大web框架Django,Flask,Flask,Python几种主流框架,13个Python web框架比较,2018年Python web五大主流框架
Python几种主流框架 从GitHub中整理出的15个最受欢迎的Python开源框架.这些框架包括事件I/O,OLAP,Web开发,高性能网络通信,测试,爬虫等. Django: Python We ...
- 选择一个 Python Web 框架:Django vs Flask vs Pyramid
Pyramid, Django, 和 Flask都是优秀的框架,为项目选择其中的哪一个都是伤脑筋的事.我们将会用三种框架实现相同功能的应用来更容易的对比三者.也可以直接跳到框架实战(Framework ...
- 浅谈Python Web 框架:Django, Twisted, Tornado, Flask, Cyclone 和 Pyramid
Django Django 是一个高级的 Python Web 框架,支持快速开发,简洁.实用的设计.如果你正在建一个和电子商务网站相似的应用,那你应该选择用 Django 框架.它能使你快速完成工作 ...
随机推荐
- linux 内核的futex - requeue 以及 requeue-pi
futex为更好支持pthread_cond的实现(,最主要是broadcast),设计了requeue功能,并以futex系统调用提供操作接口,包括一对配对的操作 futex_wait_requeu ...
- ④JavaScript格式化时间戳
这部分代码是按照网上的教程写的,忘记在哪里看到了
- 利用DNS AAAA记录和IPv6地址传输后门
0x00 前言 在本文中,我想解释如何在DNS流量中利用IPv6地址(AAAA)记录传输Payload.在我之前的文章中,我解释了如何利用DNS和PTR记录,现在我们将讨论AAAA记录. 本文分为两部 ...
- RabbitMQ集群和失败处理
RabbitMQ内建集群的设计用于完成两个目标:允许消费者和生产者在RabbitMQ节点在奔溃的情况下继续运行,以及通过添加更多的节点来线性扩展消息通信的吞吐量.当失去一个RabbitMQ节点时客户端 ...
- (HTTPS)-强制 SSL (HTTPS)Filter
汗,无知真可怕,Servlert规范中已经有自动跳转到保护页面(Http - Https)的方法了: web.xml <security-constraint> ...
- [原创]MySQL数据库忘记root密码解决办法
MySQL数据库忘记root密码解决办法 1.在运行输入services.msc打开服务窗体,找到MYSQL服务.右键停止将其关闭.如图:
- 可视化Git版本管理工具SourceTree的使用
最近去了新公司,发现公司使用的团队版本管理工具是SourceTree,本人一直是SVN的热衷粉,很少使用git,所以从头学习git及可视化客户端SourceTree的使用,本贴只针对新手,大牛可以无视 ...
- kafka 0.10.2 部署失败后,重新部署
删除kafka各个节点log目录 删除zookeeper上kafka相关的目录 [root@m1 ~]# zkCli.sh Connecting to localhost: -- ::, [myid: ...
- Django初探——工程创建以及models数据库相关配置
Python的WEB框架有Django.Tornado.Flask 等多种,Django相较与其他WEB框架其优势为:大而全,框架本身集成了ORM.模型绑定.模板引擎.缓存.Session等诸多功能. ...
- Java的必备开发工具
身为一个Java开发的爱好者,你不得不承认需要安装以下软件 NO.1 Java开发工具包 Java JDK 可以说这个工具包不得不安装,因为这个工具包是开发Java程序的核心! Java JDK工具包 ...