大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全家桶1年46,售后保障稳定
Struts2部分
1. JavaEE软件三层结构和MVC的区别?
JavaEE软件三层机构是由sun公司提供JavaEE开发规范的:Web层(表现层)、业务逻辑层、数据持久层。【其中WEB层会使用前端控制器模式】
MVC是一种思想,是一种模式,将软件分为 Model模型、View视图、Controller控制器。【JavaEE开发更强调三层结构,web层开发更注重MVC】
Struts2 就是web层开发框架,符合MVC模式;struts1 、webwork 、jsf 、SpringMVC 都是MVC
2. Struts和struts2的区别有哪些?
Action类
Struts 1要求Action类要扩展自一个抽象基类。Struts 1的一个共有的问题是面向抽象类编程而不是面向接口编程。
Struts 2的Action类实现了一个Action接口,连同其他接口一起实现可选择和自定义的服务。Struts 2提供一个名叫ActionSupport的基类实现一般使用的接口。虽然,Action接口不是必须的。任何使用execute方法的POJO对象可以 被当作Struts 2的Action对象使用。
程模型
Struts 1 Action类是单例类,因只有一个示例控制所有的请求。单例类策略造成了一定的限制且给开发带来了额外的烦恼。Action资源必须是程安全或者同步 的。
Struts 2 Action对象每一个请求都实例化对象,所以没有程安全的问题。(实践中,servlet容器生许多丢的对象对于每一个请求,多于一个的对象并不影响垃 圾收集)
Servlet 依赖
Struts 1的Action类依赖于servlet API以HttpServletRequest和HttpServletResponse作参数传给execute方法当Action被调用时。
Struts 2的Action不和容器有关。Servlet上下文被表现简单的Maps,允许Action被独立的测试。Struts 2的Action可以访问最初的请求和相应,如果需要的话。然而,其他的架构元素少或者排除直接访问HttpServletRequest或者 HttpServletResponse的需要。
易测性
测试Struts 1的主要障碍是execute方法暴露了Servlet API。第三方的扩展,Struts测试用例,提供Struts 1的集合对象。
Struts 2的Action可以通过实例化Action测试,设置属性,然后调用方法。依赖注入的支持也是测试变得更简单。
接受输入
Struts 1使用ActionForm对象捕获输入。象Action一样,所有的ActionForm必须扩展基类。因其他的JavaBean不能作 ActionForm使用,开发者经常创建多余的类捕获输入。DynaBeans可以被用来作替代ActionForm的类创建。但是开发者可以重新描述 已经存在的JavaBean。
Struts 2 Action属性作输入属性,排除第二个输入对象的需要。输入属性可能有丰富的对象类型这些类型有他们自己的属性。Action的属性可以通过标签库访 问。Struts 2也支持ActionForm形式。丰富的对象类型,包含业务或者域对象,可以被当作输入或者输出对象使用。馍型驱动特性简化标签对POJO输入对象的引 用。
表达式语言
Struts 1整和JSTL,所以它使用JSTL的表达式语言。表达式语言有基本的图形对象移动,但是相对很弱的集合和被索引的属性支持。
Struts 2使用JSTL,但是框架也支持更大和更灵活的表达式,叫做“对象图形符号语言”(OGNL)。
将值绑定要视图上
Struts 1使用标准JSP机制来绑定对象到页面上下文。
Struts 2使用“ValueStack”技术了标签库可以不用链接你的视图到对象的表现类型访问值。ValueStack策略允许重用视图。
类型转换
Struts 1的ActionForm属性经常都是String的。Struts 1使用Commons-Beanutils类型转换。转换每一个类,不是每一个实例配置。
Struts 2使用OGNL类型转换。框架包含转换器基本的和共同的对象类型和原始类型。
验证
Struts 1支持手动验证凭借ActionForm的validate方法,或者通过扩展的公用验证器。类可以有不同的验证上下文未相同的类,但是不能不能包括验证 子对象。
Struts 2支持手动验证凭借validate方法和XWork验证框架。Xwork验证框架支持一连串的验证子属性使用的验证了属性类的类型和严正上下文而定义。
Action执行的控制
Struts 1支持独立的请求处理器对于每一个模型,但是所有在模型中的Action必须共享同一个生命周期。
Struts 2支持在每一个Action基础上凭借拦截栈创建不同的生命周期。自定义栈可以被创建且使用不同的所需 的Action。
3. 简要说说Struts2的处理流程?
Struts2框架的大致处理流程如下:
1、加载类(FilterDispatcher)
2、读取配置(struts配置文件中的Action)
3、派发请求(客户端发送请求)
4、调用Action(FilterDispatcher从struts配置文件中读取与之相对应的Action )
5、启用拦截器(WebWork拦截器链自动对请求应用通用功能,如验证)
6、处理业务(回调Action的execute()方法)
7、返回响应(通过execute方法将信息返回到FilterDispatcher)
8、查找响应(FilterDispatcher根据配置查找响应的是什么信息如:SUCCESS、ERROER,将跳转到哪个jsp页面)
9、响应用户(jsp—>客户浏览器端显示)
10、struts2标签库(相比struts1的标签库,struts2是大大加强了,对数据的操作功能很强大)
请求(.action)—->经过StrutsPrepareAndExecuteFilter 核心控制器—->进入到Struts2的拦截器Interceptor(实现代码功能)—–>通过action的名称找对应的Action类—–>执行Action类的execute方法—–>通过execute方法中返回的字符串,在Struts.xml中找对应的结果页面(result)【在action执行之前,执行了defaultStack拦截器栈】
* 拦截器 在 struts-default.xml定义 【它位于sruts2-core-xxx.jar目录下】
* 执行拦截器 是 defaultStack 中引用拦截器
4、Struts2配置文件加载顺序
通过查看StrutsPrepareAndExecuteFilter源码可以得到答案!
此处,我们以及清晰了看到了该类加载配置文件的顺序,我们依次围绕标号查看对应方法产生的文件即可。
对应产生文件依次如下:
init_DefaultProperties(); // [1]—- org/apache/struts2/default.properties
init_TraditionalXmlConfigurations(); // [2] — struts-default.xml,struts-plugin.xml,struts.xml
init_LegacyStrutsProperties(); // [3] — 自定义struts.properties
init_CustomConfigurationProviders(); // [5] —– 自定义配置提供
init_FilterInitParameters() ; // [6] —– web.xml
init_AliasStandardObjects() ; // [7] —- Bean加载
结论 :【前三个是默认的,不用关注,后面三个需要注意】
① default.properties 该文件保存在 struts2-core-2.3.7.jar 中 org.apache.struts2包里面 (常量的默认值)
② struts-default.xml 该文件保存在 struts2-core-2.3.7.jar (Bean、拦截器、结果类型 )
③ struts-plugin.xml 该文件保存在struts-Xxx-2.3.7.jar (在插件包中存在 ,配置插件信息 )struts-config-browser-plugin-2.3.7.jar里面有
④ struts.xml 该文件是web应用默认的struts配置文件 (实际开发中,通常写struts.xml )
⑤ struts.properties 该文件是Struts的默认配置文件 (配置常量 )
⑥ web.xml 该文件是Web应用的配置文件 (配置常量 )
后加载配置文件中修改的常量的值会覆盖前面配置文件修改的常量的值!
5、我们在书写Action的时候有哪几种方式?他们有什么区别?
有三种方式:
①普通POJO(简单Java对象),这种方式我们不需要继承任何父类,实现任何接口。Struts2框架读取struts.xml文件,获得完整的action类名。
- obj = Class.forName(“完整类名”).newInstance();
- Method m = Class.forName(“完整类名”).getMethod(“execute”);
- m.invoke(obj); //通过反射 执行execute()方法
②编写Action实现Action接口
- Action接口中,定义默认五种 逻辑视图名称
- // 数据处理成功 (成功页面)
- public static final String SUCCESS = “success”;
- // 页面不跳转 return null; 效果一样
- public static final String NONE = “none”;
- // 数据处理发送错误 (错误页面)
- public static final String ERROR = “error”;
- // 用户输入数据有误,通常用于表单数据校验 (输入页面)
- public static final String INPUT = “input”;
- // 主要权限认证 (登陆页面)
- public static final String LOGIN = “login”;
③编写Action继承ActionSupport(推荐)
在Action中使用表单校验、错误信息设置、读取国际化信息三个功能
- 代理模式,控制目标对象访问
- /hello.action 请求时 StrutsPrepareAndExecuteFilter的 doFilter一定执行
- //判断配置文件中有没有对应Action
- ActionMapping mapping = prepare.findActionMapping(request, response, true);
- //根据配置创建代理对象
- ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(namespace, name, method, extraContext, true, false);
- 执行时:先执行interceptor的intercept拦截方法, 最后指向action的execute
6、Action是如何接受请求参数的?
属性驱动和模型驱动
Struts2内部提供了参数封装功能,不需要使用BeanUtils进行封装。Struts2大部分内置功能都是拦截器实现的。
当点击登录提交表单时,就会被下面的拦截器进行封装,进行set注入值,实现参数封装。
<interceptor name=”params” class=”com.opensymphony.xwork2.interceptor.ParametersInterceptor”/>
第一种 :Action 本身作为model对象,通过成员setter封装 (属性驱动 )
主要用于参数较少的封装,如果分层,不利于将数据传递到业务层。
页面:
用户名:<input type=”text” name=”username” /> <br/>
Action:
public class RegistAction1 extends ActionSupport {
private String username;
public void setUsername(String username) {
this.username = username;
}
}
Struts2 action是多实例,不会有线程安全问题,使用这种数据封装方式,数据封装到action属性中,不能将action对象传递给业务层,我们需要再单独定义javabean,将action属性封装到javabean。
第二种 :创建独立model对象,页面通过ognl表达式封装 (属性驱动)
具体封装流程如下:传递username,调用setUsername,把username注入User中,将会新建一个User对象,当第二个参数password传递过来时,struts框架将首先调用getUser方法询问User是否为空,如果为空,将会新建一个User,否则不会新建,直接注入值。如果只有一个Set方法,那么每set一次数据,就会新建一个User对象,那么就是把username、password封装在两个不同的User对象中了。这样封装失败。
<!–基于OGNL表达式的写法–>
用户名:<input type=”text” name=”user.username” /> <br/>
密 码:<input type=”password” name=”user.password” /> <br/>
model(User):
public class User {
private String username;
private String password;
由params的拦截器完成参数的封装
<interceptor name=”params” class=”com.opensymphony.xwork2.interceptor.ParametersInterceptor”/>
第三种 :使用ModelDriven接口,对请求数据进行封装 (模型驱动 ) —– 企业开发的主流(模型驱动有很多特性)
<interceptor name=”modelDriven” class=”com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor”/> 为模型驱动提供了更多特性
页面:
用户名:<input type=”text” name=”username” /> <br/>
model(User):
Action :
public class RegistAction3 extends ActionSupport implements ModelDriven<User> {
// 模型对象必须手动实例化
private User user = new User();
public User getModel() {
return user;
}
}
对比第二种、第三种 : 第三种只能在Action中指定一个model对象(返回一个model对象),第二种可以在Action中定义多个model对象
<input type=”text” name=”user.username” />
<input type=”text” name=”product.info” />
7、Action的相关配置?
1)必须要为<action>元素 配置<package>元素 (struts2 围绕package进行Action的相关配置 )
配置package 三个常用属性
<package name=”default” namespace=”/” extends=”struts-default”>
①name包名称,在struts2的配置文件中,包名不能重复,name并不是真正包名,只是为了管理Action
②namespace和 <action>的name属性,决定 Action的访问路径 (以/开始 )
namespace=”” :默认的名称空间
namespace=”/” :根名称空间
namespace=”/aa/” :带有名称空间的路径
③extends继承哪个包,通常开发中继承struts-default包 (struts-default包在 struts-default.xml中定义 )【可以使用包中默认的拦截器和结果集】
2)Action是通过<action>元素配置
<action name=”hello” class=”cn.itcast.struts2.demo1.HelloAction” method=”execute”>
①<action>的name和 <package>的namespace属性共同决定 Action的访问路径
②class:类全路径
③method:执行的方法,默认为execute()方法
例如:
<package name=”default” namespace=”/user” extends=”struts-default”>
<action name=”hello” class=”cn.itcast.struts2.demo1.HelloAction”>
<result name=”success”>/demo1/success.jsp</result>
</action>
</package>
此时的访问路径 http://localhost:8080/Struts2/demo1/user/hello.action
①result中的name:结果页面逻辑视图名称,默认为success
②type:结果类型(后面会做详细介绍,默认为转发)
3) <action> 元素配置默认值
<package> 的namespace 默认值 /
<action> 的class 默认值 ActionSupport 类 <default-class-ref class=”com.opensymphony.xwork2.ActionSupport” />
<result> 的 name 默认值 success
8、Action访问Servlet API有哪几种方式,简单的介绍一下
①.方式一:使用ActionContext对象(在Action中解耦合方式间接访问Servlet API)
在struts2中Action API已经与Servlet API 解耦合(没有依赖关系),开发简单,便于测试。
Servlet API 常见操作 : 表单提交请求参数获取,向request、session、application三个范围存取数据
ActionContext.getContext().getSession()
②.方式二:使用接口注入的方式操作Servlet API(藕合)
通过Aware接口,在构造Action时,自动注入需要操作Servlet对象(需要哪个对象就实现哪个Aware接口)
③.方式三:在Action中直接通过 ServletActionContext 获得Servlet API
静态方法返回request,不会有线程问题(使用了ThreadLocal来实现的)
总结:理论来说,第一种方式最好,实现了解耦和,但是第三种我们使用最为简单,企业中没有很大的限制,自己熟悉哪种就使用哪种。
ServletActionContext.getServletContext().getRequest()
9. 说说Struts2的输入校验流程
Struts2校验框架进行校验时,将执行以下流程:
A:类型转换器负责对字符串的请求参数执行类型转换,并将这些值设置成Action的属性值
B:在执行类型转换过程中可能出现异常,如果出现异常,将异常信息保存到ActionContext中,convertionError拦截器将负责将其封装到fieldError里,如果没有异常,直接进入第3步
C:调用Struts2的内置校验规则进行输入校验
D:通过反射调用validateXXX()方法
E:调用Action类中的validate()方法
F:如果上面的几步中没有出FiledError,就调用Acton中的逻辑处理方法,如果有,则进入input视图
所以,在进行校验时,别忘记在Action中的配置名为input的结果如:<result name=“input”>validate.jsp</result>
10. Struts2 form标签数据为什么可以回显?
11. 什么是值栈?值栈的内部结构?
ValueStack 是 struts2 提供一个接口,实现类 OgnlValueStack —- 值栈对象 (OGNL是从值栈中获取数据的 )每个Action实例都有一个ValueStack对象 (一个请求对应 一个ValueStack对象 )在其中保存当前Action 对象和其他相关对象 (值栈中是有Action引用的 )Struts 框架把 ValueStack 对象保存在名为“struts.valueStack” 的请求属性中,request中(值栈对象是request一个属性)
值栈由两部分组成,ObjectStack和ContextMap
ObjectStack: Struts 把动作和相关对象压入 ObjectStack 中–List
ContextMap: Struts 把各种各样的映射关系(一些 Map 类型的对象) 压入ContextMap中,Struts 会把下面这些映射压入 ContextMap 中
parameters: 该 Map 中包含当前请求的请求参数
request: 该 Map 中包含当前 request 对象中的所有属性
session: 该 Map 中包含当前 session 对象中的所有属性
application:该 Map 中包含当前 application 对象中的所有属性
attr: 该 Map 按如下顺序来检索某个属性: request, session, application
ValueStack中 存在root属性 (CompoundRoot) 、 context 属性 (OgnlContext )
* CompoundRoot 就是ArrayList
* OgnlContext 就是 Map
cncontext 对应Map 引入 root对象
* context中还存在 request、 session、application、 attr、 parameters 对象引用
* OGNL表达式,访问root中数据时 不需要 #, 访问 request、 session、application、 attr、 parameters 对象数据 必须写 #
* 操作值栈 默认指 操作 root 元素
12. 值栈对象的创建 ,ValueStack 和 ActionContext 是什么关系 ?
值栈对象是请求时创建的
doFilter中 prepare.createActionContext(request, response);
* 创建ActionContext对象过程中,创建值栈对象ValueStack
* ActionContext对象对ValueStack对象有引用的(在程序中通过 ActionContext 获得值栈对象 )
Dispatcher类 serviceAction 方法中将值栈对象保存到request范围
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
(ActionContext是Action上下文
可以得到request session application
ValueStack是值栈 存放表单中的值
Stack Context 栈上下文 也是用来存值的)
13.如何获得值栈对象?
获得值栈对象 有两种方法
ValueStack valueStack = (ValueStack) ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
ValueStack valueStack2 = ActionContext.getContext().getValueStack();
14.如何向值栈中保存数据?如何在jsp页面中获取值栈的数据?
两种方式
// 将数据保存root的索引0位置,放置到第一个元素 ArrayList add(0,element);
valueStack.push(“itcast”);
// 在值栈创建参数map, 将数据保存到map中
valueStack.set(“company”, “aaa”);
在jsp中 通过 <s:debug /> 查看值栈的内容
获取值栈数据时,如果访问root中数据不需要# ,访问其它对象数据加 #
通过下标获取root中对象
<s:property value=”[0].top”/> //取值栈顶对象
直接在root中查找对象属性 (自上而下自动查找)
valueStack:<s:property value=”username”/>
在OgnlContext中获取数据
request:<s:property value=”#request.username”/>
session:<s:property value=”#session.username”/>
application:<s:property value=”#application.username”/>
attr:<s:property value=”#attr.username”/>
parameters:<s:property value=”#parameters.cid[0]”/>
15.为什么EL也能访问值栈中的数据?
StrutsPreparedAndExecuteFilter的doFilter代码中 request = prepare.wrapRequest(request);
* 对Request对象进行了包装 ,StrutsRequestWrapper
* 重写request的 getAttribute
Object attribute = super.getAttribute(s);
if (attribute == null) {
attribute = stack.findValue(s);
}
访问request范围的数据时,如果数据找不到,去值栈中找
16.你在开发中,值栈主要有哪些应用?
值栈主要解决Action向JSP传递数据问题
Action 向JSP 传递数据处理结果 ,结果数据有两种形式
1)消息 String类型数据
this.addFieldError(“msg”, “字段错误信息”);
this.addActionError(“Action全局错误信息”);
this.addActionMessage(“Action的消息信息”);
* fieldError 针对某一个字段错误信息 (常用于表单校验)、actionError (普通错误信息,不针对某一个字段 登陆失败)、 actionMessage 通用消息
在jsp中使用 struts2提供标签 显示消息信息
<s:fielderror fieldName=”msg”/>
<s:actionerror/>
<s:actionmessage/>
2)数据 (复杂类型数据)
使用值栈 valueStack.push(products);
哪些数据默认会放入到值栈 ???
1)每次请求,访问Action对象 会被压入值栈 ——- DefaultActionInvocation 的 init方法 stack.push(action);
* Action如果想传递数据给 JSP,只有将数据保存到成员变量,并且提供get方法就可以了
2)ModelDriven 接口有一个单独拦截器
<interceptor name=”modelDriven” class=”com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor”/>
在拦截器中,将model对象压入了值栈 stack.push(model);
* 如果Action 实现ModelDriven接口,值栈默认栈顶对象就是model对象
17.如何防止表单重复提交?
哪些情况会导致重复提交?
服务器处理服务后,转发页面,客户端点击刷新(重定向)
客户端网络过慢,按钮连续点击(按钮点击一次后,禁用按钮)
使用令牌机制
18.简单的说一下ActionProxy的运行原理?
Struts2的拦截器的本质就是AOP思想,而AOP就是基于代理模式。
19.拦截器和过滤器有什么区别?
1、①拦截器是基于java的反射机制的,而过滤器是基于函数回调
2、②过滤器依赖与servlet容器,而拦截器不依赖与servlet容器
3、③拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用
4、④拦截器可以访问action上下文、值栈里的对象,而过滤器不能
5、⑤在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次
拦 拦截器 :是在面向切面编程的就是在你的service或者一个方法,前调用一个方法,或者在方法后调用一个方法比如动态代理就是拦截器的简单实现,在你调用方法前打印出字符串(或者做其它业务逻辑的操作),也可以在你调用方法后打印出字符串,甚至在你抛出异常的时候做业务逻辑的操作。
20.Struts2详细运行流程
请求首先通过Filter chain,Filter主要包括ActionContextCleanUp,它主要清理当前线程的ActionContext和Dispatcher;
FilterDispatcher主要通过AcionMapper来决定需要调用哪个Action。
ActionMapper取得了ActionMapping后,在Dispatcher的serviceAction方法里创建ActionProxy,ActionProxy创建ActionInvocation,然后ActionInvocation调用Interceptors,执行Action本身,创建Result并返回,当然,如果要在返回之前做些什么,可以实现PreResultListener。
1、客户端初始化一个指向Servlet容器(例如Tomcat)的请求;
2、这个请求经过一系列的过滤器(Filter)(这些过滤器中有一个叫做ActionContextCleanUp的可选过滤器,这个过滤器对于Struts2和其他框架的集成很有帮助,例如:SiteMesh Plugin);
3、接着StrutsPrepareAndExecuteFilter被调用,StrutsPrepareAndExecuteFilter询问ActionMapper来决定这个请求是否需要调用某个Action;
4、如果ActionMapper决定需要调用某个Action,FilterDispatcher把请求的处理交给ActionProxy;
5、ActionProxy通过Configuration Manager询问框架的配置文件,找到需要调用的Action类;
6、ActionProxy创建一个ActionInvocation的实例
7、ActionInvocation实例使用命名模式来调用,在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用。
8、一旦Action执行完毕,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果。返回结果通常是(但不总是,也可能是另外的一个Action链)一个需要被表示的JSP或者FreeMarker的模版。在表示的过程中可以使用Struts2 框架中继承的标签。在这个过程中需要涉及到ActionMapper。
部分类介绍
①ActionMapper
ActionMapper其实是HttpServletRequest和Action调用请求的一个映射,它屏蔽了Action对于Request等java Servlet类的依赖。Struts2中它的默认实现类是DefaultActionMapper,ActionMapper很大的用处可以根据自己的需要来设计url格式,它自己也有Restful的实现,具体可以参考文档的docs\actionmapper.html。
②ActionProxy&ActionInvocation
Action的一个代理,由ActionProxyFactory创建,它本身不包括Action实例,默认实现DefaultActionProxy是由ActionInvocation持有Action实例。ActionProxy作用是如何取得Action,无论是本地还是远程。而ActionInvocation的作用是如何执行Action,拦截器的功能就是在ActionInvocation中实现的。
③ConfigurationProvider&Configuration
ConfigurationProvider就是Struts2中配置文件的解析器,Struts2中的配置文件主要是尤其实现类XmlConfigurationProvider及其子类StrutsXmlConfigurationProvider来解析。
Struts2请求流程
1、客户端发送请求
2、请求先通过ActionContextCleanUp–>FilterDispatcher (一系列的过滤器)
3、FilterDispatcher通过ActionMapper来决定这个Request需要调用哪个Action
4、如果ActionMapper决定调用某个Action,FilterDispatcher把请求的处理交给ActionProxy,这儿已经转到它的Delegate–Dispatcher来执行
5、ActionProxy根据ActionMapping和ConfigurationManager找到需要调用的Action类
6、ActionProxy创建一个ActionInvocation的实例
7、ActionInvocation调用真正的Action,当然这涉及到相关拦截器的调用
8、Action执行完毕,ActionInvocation创建Result并返回,当然,如果要在返回之前做些什么,可以实现PreResultListener。添加PreResultListener可以在Interceptor中实现。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/213463.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...