转:Struts 2 - Struts Configuration Elements

A web application uses a deployment descriptor to initialize resources like servlets and taglibs. The deployment descriptor is formatted as a XML document and named "web.xml".

Likewise, the framework uses a configuration file to initialize its own resources. These resources include

  • Interceptors that can preprocess and postprocess a request
  • Action classes that can call business logic and data access code
  • Results that can prepare views, like JavaServer Pages and FreeMarker templates
If the medium is the message, then the Struts Configuration is the application.

At runtime, there is a single configuration for an application. Prior to runtime, the configuration is defined through one or more XML documents, including the default struts.xml document. There are several elements that can be configured, including packages, namespaces, includes, actions, results, interceptors, and exceptions.

See struts.xml for a working example.

Administrative Elements 

1.Bean Configuration

Internally, the framework uses its own dependency injection container. The container loads key framework objects, so that any piece of the framework can be replaced, extended, or removed in a standard, consistent way. Plugins, in particular, leverage this capability to extend the framework to provide support for third-party libraries like Spring or Sitemesh.

Most applications won't need to extend the Bean Configuration.

Beans

The bean element has one required attribute, class, which specifies the Java class to be created or manipulated. A bean can either

  1. be created by the framework's container and injected into internal framework objects, or
  2. have values injected to its static methods

The first use, object injection, is generally accompanied by the type attribute, which tells the container which interface this object implements.

The second use, value injection, is good for allowing objects not created by the container to receive framework constants. Objects using value inject must define the the static attribute.

Attribute Required Description
class yes the name of the bean class
type no the primary Java interface this class implements
name no the unique name of this bean; must be unique among other beans that specify the same type
scope no the scope of the bean; must be either default, singleton, request, session, thread
static no whether to inject static methods or not; shouldn't be true when the type is specified
optional no whether the bean is optional or not

Sample usage

Bean Example (struts.xml)
<struts>

  <bean type="com.opensymphony.xwork2.ObjectFactory" name="myfactory" class="com.company.myapp.MyObjectFactory" />
  
  ... 

</struts>
2.Constant Configuration

   
   

Constants provide a simple way to customize a Struts application by defining key settings that modify framework and plugin behavior. There are two key roles for constants. First, they are used to override settings like the maximum file upload size or whether the Struts framework should be in "devMode" or not. Second, they specify which Bean, among multiple implementations of a given type, should be chosen.

Constants can be declared in multiple files. By default, constants are searched for in the following order, allowing for subsequent files to override previous ones:

  1. struts-default.xml
  2. struts-plugin.xml
  3. struts.xml
  4. struts.properties
  5. web.xml

The struts.properties file is provided for backward-compatiblity with WebWork.

Constant

In the various XML variants, the constant element has two required attributes: name and value.

Attribute Required Description
name yes the name of the constant
value *yes the value of the constant

In the struts.properties file, each entry is treated as a constant.

In the web.xml file, any FilterDispatcher initialization parameters are loaded as constants.

Sample usage

Constant Example (struts.xml)
<struts>

  <constant name="struts.devMode" value="true" />

  ... 

</struts>
Constant Example (struts.properties)
struts.devMode = true
Constant Example (web.xml)
<web-app id="WebApp_9" version="2.4" 
	xmlns="http://java.sun.com/xml/ns/j2ee" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <filter>
        <filter-name>struts</filter-name>
        <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
        <init-param>
        	<param-name>struts.devMode</param-name>
        	<param-value>true</param-value>
        </init-param>
    </filter>

    ...

</web-app>
3.Package Configuration
 

        
        

Packages are a way to group actions, results, result types, interceptors, and interceptor-stacks into a logical configuration unit. Conceptually, packages are similiar to objects in that they can be extended and have individual parts that can be overridden by "sub" packages.

Packages

The package element has one required attribute, name, which acts as the key for later reference to the package. The extends attribute is optional and allows one package to inherit the configuration of one or more previous packages - including all interceptor, interceptor-stack, and action configurations.

Note that the configuration file is processed sequentially down the document, so the package referenced by an "extends" should be defined above the package which extends it.

The optional abstract attribute creates a base package that can omit the action configuration.

Attribute Required Description
name yes key to for other packages to reference
extends no inherits package behavior of the package it extends
namespace no see Namespace Configuration
abstract no declares package to be abstract (no action configurations required in package)

Sample usage

Package Example (struts.xml)
<package name="employee" extends="default" namespace="/employee">
  <default-interceptor-ref name="crudStack"/>

  <action name="list" method="list"
    class="org.apache.struts2.showcase.action.EmployeeAction" >
      <result>/empmanager/listEmployees.jsp</result>
      <interceptor-ref name="basicStack"/>
  </action>
  <action name="edit-*" class="org.apache.struts2.showcase.action.EmployeeAction">
    <param name="empId">{1}</param>
    <result>/empmanager/editEmployee.jsp</result>
      <interceptor-ref name="crudStack">
        <param name="validation.excludeMethods">execute</param>
      </interceptor-ref>
    </action>
    <action name="save" method="save"
        class="org.apache.struts2.showcase.action.EmployeeAction" >
      <result name="input">/empmanager/editEmployee.jsp</result>
      <result type="redirect">edit-${currentEmployee.empId}.action</result>
    </action>
    <action name="delete" method="delete"
      class="org.apache.struts2.showcase.action.EmployeeAction" >
      <result name="error">/empmanager/editEmployee.jsp</result>
      <result type="redirect">edit-${currentEmployee.empId}.action</result>
    </action>
</package>

</struts>
 
4.Namespace Configuration

The namespace attribute subdivides action configurations into logical modules, each with its own identifying prefix. Namespaces avoid conflicts between action names. Each namespace can have it's own "menu" or "help" action, each with its own implementation. While the prefix appears in the browser URI, the tags are "namespace aware", so the namespace prefix does not need to be embedded in forms and links.

Namespaces are the equivalent of Struts Action 1 modules.

Default Namespace

The default namespace is "" - an empty string. The default namespace is used as a "catch-all" namespace. If an action configuration is not found in a specified namespace, the default namespace is also be searched. The local/global strategy allows an application to have global action configurations outside of the action element "extends" hierarchy.

The namespace prefix can be registered with Java declarative security, to ensure only authorized users can access the actions in a given namespace.

Root Namesapce

A root namespace ("/") is also supported. The root is the namespace when a request directly under the context path is received. As with other namespace, it will fall back to the default ("") namespace if a local action is not found.

Namespace Example

<package name="default">
    <action name="foo" class="mypackage.simpleAction>
        <result name="success" type="dispatcher">greeting.jsp</result>
    </action>
    <action name="bar" class="mypackage.simpleAction">
        <result name="success" type="dispatcher">bar1.jsp</result>
    </action>
</package>

<package name="mypackage1" namespace="/">
    <action name="moo" class="mypackage.simpleAction">
        <result name="success" type="dispatcher">moo.jsp</result>
    </action>
</package>

<package name="mypackage2" namespace="/barspace">
    <action name="bar" class="mypackage.simpleAction">
        <result name="success" type="dispatcher">bar2.jsp</result>
    </action>
</package>

How the Code Works

If a request for /barspace/bar.action is made, /barspace namespace is searched and if it is found the bar action is executed, else it will fall back to the default namespace. In the Namespace Example, the bar action does exists in the barspace namespace. The barspace.bar action will be executed, and if "success" is returned, the request will be forwarded to bar2.jsp.

Falling Back to Foo

If a request is made to /barspace/foo.action, the namespace /barspace will be checked for action foo. If a local action is not found, the default namespace is checked. In the Namespace Example, there is no action foo in the namespace /barspace, therefore the default will be checked and /foo.action will be executed.

In the Namespace Example, if a request for moo.action is made, the root namespace ('/') is searched for a moo action; if a root action is not found, the default namespace is checked. In this case, the moo action does exist and will be selected. Upon success, the request would be forwarded to bar2.jsp.

Getting to the Root
If a request is made for /foo.action, the root / namespace will be checked. If foo is found, the root action will be selected. Otherwise, the framework will check the default namespace. In the Namespace Example, foo action does not exist in the root namespace, so the default namespace is checked, and the default foo action is selected.
Namespaces are not a path!

Namespace are not hierarchical like a file system path. There is one namespace level. For example if the URL /barspace/myspace/bar.action is requested, the framework will first look for namespace /barspace/myspace. If the action does not exist at /barspace/myspace, the search will immediately fall back to the default namespace "". The framework will not parse the namespace into a series of "folders". In the Namespace Example, the bar action in the default namespace would be selected.

 

5.Include Configuration

 

A popular strategy is "divide and conquer". The framework lets you apply "divide and conquer" to configuration files using the include element.

<!DOCTYPE struts PUBLIC
  "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
  "http://struts.apache.org/dtds/struts-2.0.dtd">
<struts>
    <include file="Home.xml"/>
    <include file="Hello.xml"/>
    <include file="Simple.xml"/>
    <include file="/util/POJO.xml"/>
</struts>

Each included file must be in the same format as struts.xml, including the doctype. The include files can be placed anywhere on the classpath.

In a large-team environment, the include files can be used to organize different modules of the application that are being developed by different team members.

Request Handling Elements

1.Interceptor Configuration

Interceptors allow you to define code to be executed before and/or after the execution of an Action method. (The "Filter" pattern.) Interceptors can be a powerful tool when developing applications. There are many, many use cases for Interceptors, including validation, property population, security, logging, and profiling.

Validation Examine input for correctness
Property Population Transfer and convert input to object properties
Logging Journal details regarding each action
Profiling Time action throughput, looking for performance bottlenecks

Interceptors can be chained together to create an Interceptor "Stack". If an action neeeds to check the client's credentials, log the action, and time the action, all of these routines, and more, could be made part of the same Interceptor Stack.

Interceptors are implemented as Java classes, and so each Interceptor has a unique class name. To make it easier to reference Interceptors, each class can be registered with the framework and given a simpler name.

Registering Interceptors
<interceptors>
  <interceptor name="security" class="com.company.security.SecurityInterceptor"/>
  <interceptor-stack name="secureStack">
    <interceptor-ref name="security"/>
    <interceptor-ref name="defaultStack"/>
  </interceptor-stack>
</interceptors>

Individual Interceptors and Interceptors Stacks can be "mixed and matched" in any order when defining an Interceptor Stack. The framework will invoke each Interceptor on the stack in the order it is defined.

Most applications will define a default Interceptor Stack,

<default-interceptor-ref name="secureStack"/>

but each action can also define it's own local stack.

A local Interceptor Stack
<action name="VelocityCounter" class="org.apache.struts2.example.counter.SimpleCounter">
   <result name="success">...</result>
   <interceptor-ref name="defaultComponentStack"/>
</action>

The default configuration (struts-default.xml) sets up a default Interceptor Stack that will work well for most applications.

2.Action Configuration

The action mappings are the basic "unit-of-work" in the framework. Essentially, the action maps an identifier to a handler class. When a request matches the action's name, the framework uses the mapping to determine how to process the request.

Action Mappings

The action mapping can specify a set of result types, a set of exception handlers, and an interceptor stack. But, only the name attribute is required. The other attributes can also be provided at package scope.

A Logon Action
<action name="Logon" class="tutorial.Logon">
  <result type="redirect-action">Menu</result>
  <result name="input">/tutorial/Logon.jsp</result>
</action>

Action Names

In a web application, the name attribute is matched a part of the location requested by a browser (or other HTTP client). The framework will drop the host and application name and the extension, and match what's in the middle. So, a request for http://www.planetstruts.org/struts2-mailreader/Welcome.do will map to the Welcome action.

Within an application, the link to an action is usually generated by a Struts Tag. The tag can specify the action by name, and the framework will render the default extension and anything else that is needed.

A Hello Form
<s:form action="Hello">
    <s:textfield label="Please enter your name" name="name"/>
    <s:submit/>
</s:form>

Action Methods

The default entry method to the handler class is defined by the Action interface.

Action interface
public interface Action {
    public String execute() throws Exception;
}

Implementing the Action interface is optional. If Action is not implemented, the framework will use reflection to look for an execute method.

Sometimes, developers like to create more than one entry point to an Action. For example, in the case of of a data-access Action, a developer might want separate entry-points for create, retrieve, update, and delete. A different entry point can be specified by the method attribute.

<action name="delete" class="example.CrudAction" method="delete">

If there is no execute method, and no other method, specified in the configuration, the framework will throw an exception.

Wildcard Method

Many times, a set of action mappings will share a common pattern. For example, all your edit actions might start with the word "edit", and call the edit method on the Action class. The delete actions might use the same pattern, but call the delete method instead.

Rather than code a separate mapping for each action class that uses this pattern, you can write it once as a wildcard mapping.

<action name="*Crud" class="example.Crud" method="{1}">

Here, a reference to "editCrud" will call the edit method on an instance of the Crud Action class. Likewise, a reference to "deleteCrud" will call the delete method instead.

Another common approach is to postfix the method name and set it off with an exclamation point (aka "bang"), underscore, or other special character.

  • "action=Crud_input"
  • "action=Crud_delete"

To use a postfix wildcard, just move the asterisk and add an underscore.

<action name="Crud_*" class="example.Crud" method="{1}">

From the framework's perspective, a wildcard mapping creates a new "virtual" mapping with all the same attributes as a conventional, static mapping. As a result, you can use the expanded wildcard name as the name of validation, type conversion, and localization files, just as if it were an Action name (which it is!).

  • Crud_input-validation.xml
  • Crud_delete-conversion.xml

The postfix "!" notation is also available in WebWork 2, but it is implemented differently. To use the old implementation, set struts.enable.DynamicMethodInvocation=TRUE in the struts.properties file. To use wildcards instead (preferred), set struts.enable.DynamicMethodInvocation=FALSE.

ActionSupport Default

If the class attribute in an action mapping is left blank, the com.opensymphony.xwork.ActionSupport class is used as a default.

<action name="Hello">
   // ...
</action>

The ActionSupport class has execute and input methods that return "success".

Post-Back Default

A good practice is to link to actions rather than pages. Linking to actions encapsulates which server page renders, and ensures that an Action class can fire before a page renders.

Another common workflow stategy is to first render a page using an alternate method, like input and then have it submit back to the default execute method.

Using these two strategies together creates an opportunity to use a "post-back" form that doesn't specify an action. The form simply submits back to the action that created it.

Posting Back
<s:form>
    <s:textfield label="Please enter your name" name="name"/>
    <s:submit/>
</s:form>

Action Default

Usually, if an action is requested, and the framework can't map the request to an action name, the result will be the usual "404 - Page not found" error. But, if you would prefer that an ominbus action handle any unmatched requests, you can specify a default action. If no other action matches, the default action is used instead.

<package name="Hello" extends="action-default">

<default-action-ref name="UnderConstruction">

<action name="UnderConstruction">
  <result>/UnderConstruction.jsp</result>
</action>

There are no special requirements for the default action. Each package can have its own default action, but there should only be one default action per namespace.

One to a Namespace

The default action features should be setup so that there is only one default action per namespace. If you have multiple packages declaring a default action with the same namespace, there is no guarantee which action will be the default.

Wildcard Default

Using wildcards is another approach to default actions. A wildcard action at the end of the configuration can be used to catch unmatched references.

<action name="*" >
  <result>/{1}.jsp</result>
</action>

When a new action is needed, just add a stub page.

3.Result Configuration

When an Action class method completes, it returns a String. The value of the String is used to select a result element. An action mapping will often have a set of results representing different possible outcomes. A standard set of result tokens are defined by the ActionSupport base class.

Predefined result names
String SUCCESS = "success";
String NONE    = "none";
String ERROR   = "error";
String INPUT   = "input";
String LOGIN   = "login";

Of course, applications can define other result tokens to match specific cases.

Result Elements

The result element has two jobs. First, it provides a logical name. An Action can pass back a token like "success" or "error" without knowing any other implementation details. Second, the result element provides a Result Type. Most results simply forward to a server page or template, but other Result Types can be used to do more interesting things.

Intelligent Defaults

A default Result Type can be set as part of the configuration for each package. If one package extends another, the "child" package can set its own default result, or inherit one from the parent.

Setting a default Result Type
<result-types>
 <result-type name="dispatcher" class="org.apache.struts2.dispatcher.ServletDispatcherResult" 
  default="true"/>
</result-types>

If a type attribute is not specified, the framework will use the dispatcher. The default Result Type, dispatcher, forwards to another web resource. If the resource is a JavaServer Page, then the container will render it, using its JSP engine.

Likewise if the name attribute is not specified, the framework will give it the name "success".

Using these intelligent defaults, the most often used result types also become the simplest.

Result element without defaults
<result name="success" type="dispatcher">
    <param name="location">/ThankYou.jsp</param>
</result>
A Result element using some defaults
<result>
    <param name="location">/ThankYou.jsp</param>
</result>

The param tag sets a property on the Result object. The most commonly-set property is location, which usually specifies the path to a web resources. The param attribute is another intelligent default.

Result element using more defaults
<result>/ThankYou.jsp</result>

Mixing results with intelligent defaults with other results makes it easier to see the "critical path".

Multiple Results
<action name="Hello">
  <result>/hello/Result.jsp</result>
  <result name="error">/hello/Error.jsp</result>
  <result name="input">/hello/Input.jsp</result>
</action>

Global Results

Most often, results are nested with the action element. But some results apply to multiple actions. In a secure application, a client might try to access a page without being authorized, and many actions may need access to a "logon" result.

If actions need to share results, a set of global results can be defined for each package. The framework will first look for a local result nested in the action. If a local match is not found, then the global results are checked.

Defining global results
<global-results>
  <result name="error">/Error.jsp</result>
  <result name="invalid.token">/Error.jsp</result>
  <result name="login" type="redirect-action">Logon!input</result>
</global-results>
Error Handling

    
    

Exception mappings is a powerful feature for dealing with an Action class that throws an Exception. The core idea is that an Exception thrown during the Action method can be automatically caught and mapped to a predefined Result. This declarative strategy is especially useful for frameworks, like Hibernate and Acegi, that throw RuntimeExceptions.

As with many other parts of the framework, an Interceptor is needed to activate the exception mapping functionality. Below is a snippet from struts-default.xml which has the exception mapping already activated.

snippet of struts-default.xml
...
<interceptors>
    ...
    <interceptor name="exception" class="com.opensymphony.xwork.interceptor.ExceptionMappingInterceptor"/>
    ...
</interceptors>

<interceptor-stack name="defaultStack">
    <interceptor-ref name="exception"/>
    <interceptor-ref name="alias"/>
    <interceptor-ref name="servlet-config"/>
    <interceptor-ref name="prepare"/>
    <interceptor-ref name="i18n"/>
    <interceptor-ref name="chain"/>
    <interceptor-ref name="debugging"/>
    <interceptor-ref name="profiling"/>
    <interceptor-ref name="scoped-model-driven"/>
    <interceptor-ref name="model-driven"/>
    <interceptor-ref name="fileUpload"/>
    <interceptor-ref name="checkbox"/>
    <interceptor-ref name="static-params"/>
    <interceptor-ref name="params"/>
    <interceptor-ref name="conversionError"/>
    <interceptor-ref name="validation">
        <param name="excludeMethods">input,back,cancel,browse</param>
    </interceptor-ref>
    <interceptor-ref name="workflow">
        <param name="excludeMethods">input,back,cancel,browse</param>
    </interceptor-ref>
</interceptor-stack>
...

To use exception mapping, we simply need to map Exceptions to specific Results. The framework provides two ways to declare an exception mapping <exception-mapping/> - globally or for a specific action mapping. The exception mapping element takes two attributes, exception and result.

When declaring an exception mapping, the Interceptor will find the closest class inheritance match between the Exception thrown and the Exception declared. The Interceptor will examine all declared mappings applicable to the action mapping, first local and then global mappings. If a match is found, the Result is processed, just as if it had been returned by the Action.

This process follows the same rules as a Result returned from an Action. It first looks for the Result in the local action mapping, and if not found, it looks for a global Result.

Below is an example of global and local exception mappings.

snippet from struts.xml
<struts>
    <package name="default">
        ...
        <global-results>
            <result name="login" type="redirect">/Login.action</result>
            <result name="Exception">/Exception.jsp</result>
        </global-results>

        <global-exception-mappings>
            <exception-mapping exception="java.sql.SQLException" result="SQLException"/>
            <exception-mapping exception="java.lang.Exception" result="Exception"/>
        </global-exception-mappings>
        ...
        <action name="DataAccess" class="com.company.DataAccess">
            <exception-mapping exception="com.company.SecurityException" result="login"/>
            <result name="SQLException" type="chain">SQLExceptionAction</result>
            <result>/DataAccess.jsp</result>
        </action>
        ...
    </package>
</xwork>

In the example above, here is what happens based upon each Exception:

  • A java.sql.SQLException will chain to the SQLExceptionAction (action mapping not shown)
  • A com.company.SecurityException will redirect to Login.action
  • Any other exception that extends java.lang.Exception will return the /Exception.jsp page

Exception Values on the ValueStack

By default, the ExceptionMappingInterceptor adds the following values to the Value Stack:

exception The exception object itself
exceptionStack The value from the stack trace
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
192.168.85.1 - - [26/Jun/2022:06:07:07 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 24 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 24 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 24 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 200 12925 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 200 12925 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 200 12925 192.168.85.1 - - [26/Jun/2022:06:07:11 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 200 14 192.168.85.1 - - [26/Jun/2022:06:08:06 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 15 192.168.85.1 - - [26/Jun/2022:06:08:16 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 1227 192.168.85.1 - - [26/Jun/2022:06:10:15 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 79 192.168.85.1 - - [26/Jun/2022:06:13:25 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 404 752 192.168.85.1 - - [26/Jun/2022:06:16:42 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 35 192.168.85.1 - - [26/Jun/2022:06:16:57 -0400] "GET //struts2-showcase/hhh.jsp HTTP/1.1" 403 642 192.168.85.1 - - [26/Jun/2022:06:18:55 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 35 192.168.85.1 - - [26/Jun/2022:06:19:02 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 35 192.168.85.1 - - [26/Jun/2022:06:19:09 -0400] "GET //struts2-showcase/hhh1.jsp HTTP/1.1" 403 642 192.168.85.1 - - [26/Jun/2022:06:19:34 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 400 192.168.85.1 - - [26/Jun/2022:06:20:37 -0400] "POST /struts2-showcase/index.action HTTP/1.1" 500 5 192.168.85.1 - - [26/Jun/2022:06:20:42 -0400] "GET //struts2-showcase/hhh1.jsp HTTP/1.1" 403 642 192.168.85.1 - - [26/Jun/2022:06:20:46 -0400] "GET //struts2-showcase/hhh.jsp HTTP/1.1" 403 642 192.168.85.1 - - [26/Jun/2022:06:20:51 -0400] "GET /struts2-showcase/hhh.jsp HTTP/1.1" 403 642
07-12

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值