`

javax.management.StandardMBean: When and Why. (Reposted)

    博客分类:
  • Java
 
阅读更多

Q: When is a Standard MBean not a Standard MBean?
A: When it's a StandardMBean.

A few month ago, we have added an Advanced JMX Example to JDK 6. This example builds up on the JMX Best Practices and discusses some usual JMX patterns and pitfalls to avoid when designing a Management Interface with JMX.

In this article I will give a few additional tips concerning the StandardMBean class, and in particular, when and why it becomes interesting to use it.

Standard MBean

Standard MBeans are the simplest type of MBeans. They're described in the JMX tutorial trail here.

Standard MBeans must have a management interface that follows strict naming conventions: to be a compliant MBean, a standard MBean implementation class named <package-name>.Y must implement an interface named <package-name>.Y MBean - or must extend a class which does follow these conventions.

 

---------------------------------------------------------------------------

// Interface is named after implementation name. It must be in the same 
// package.
package <package-name>;
public interface YMBean {...}

---------------------------------------------------------------------------

package <package-name>;
public class Y implements YMBean {...}

public static void main(String[] args) ... {
   MBeanServer server = ...;
   ObjectName  name   = ...;
   Y mbean = new Y(...);
   // mbean is a standard MBean.
   server.registerMBean(mbean,name);
   ...
}

---------------------------------------------------------------------------

 

Sometimes however, you'd like to be able to put interfaces and implementations in different packages, so that you could deploy only the interfaces on the client side. Interfaces are interesting to have on the client side because they allow you to create MBean proxies, which can simplify the client code. In the general case however, you don't need to have also the implementation classes on the client side, and it might even not be desirable to deploy those on the client end.

For such use cases, being forced to put the MBean interface YMBean in the same package than the MBean implementation - Y may become quite constraining.

Getting rid of the constraining name conventions

To get rid of these naming convention constraints, you have two main choices:

Using MXBeans

If you're running on the greatest JDK release ever aka JDK 6 ;-), you can turn your MBean into an MXBean. For that, you only need to end your interface name with MXBean. It does no longer need to remain in the same package than the class that implements it.

 

---------------------------------------------------------------------------

// Interface can be in any package.
package <interface-package-name>;
// Must end with MXBean - or must use @MXBean annotations.
// Y doesn't need to be related to the implementation class
public interface YMXBean {...}

---------------------------------------------------------------------------

package <package-name>;
import <interface-package-name>.YMXBean;
public class YImpl implements YMXBean {...}

public static void main(String[] args) ... {
   final MBeanServer server = ...;
   final ObjectName  name   = ...;
   final YImpl mbean = new YImpl(...);

   // mbean is an MXBean.
   server.registerMBean(mbean,name);
}

---------------------------------------------------------------------------

 

There are however some cases where using an MXBean is not feasible - for instance, if your MBean uses types which are not supported by the MXBean framework (for instance some methods use Object as return typess or parameter types).

Using StandardMBean

If you are working with an earlier Java release than JDK 6, or if your MBean can't be turned into an MXBean, then you have another possibility: you can wrap your MBean in a StandardMBean.

The javax.management.StandardMBean class will let you turn your standard MBean into a DynamicMBean, and select its Management Interface. The interface must be implemented by the implementation class, but its name doesn't need to follow any naming convention. It simply needs to be a valid MBean interface.

 

---------------------------------------------------------------------------
// Interface can be in any package.
package <interface-package-name>;

// No name convention required! You can keep the name YMBean, or change it
// to whatever you like, like YInterface
public interface YInterface {...}

---------------------------------------------------------------------------

package <package-name>;
import <interface-package-name>.YInterface;
public class YImpl implements YInterface {...}

public static void main(String[] args) {
   final MBeanServer server = ...;
   final ObjectName  name   = ...;
   final YImpl impl = new YImpl(...);
   final StandardMBean mbean = new StandardMBean(impl,YInterface.class);

   // mbean is an instance of StandardMBean: it's a DynamicMBean.
   server.registerMBean(mbean,name);
}

---------------------------------------------------------------------------

 

Interestingly, you can also use StandardMBean to wrap an MXBean. Since MXBeans do not have any constraints with regards to their interface names, this use case is obviously different. We will see in the next section some of the odd cases that might incite you into wrapping an MXBean inside a StandardMBean.

StandardMBean and MXBeans

The laws by which the MBeanServer decides whether an MBean is a DynamicMBean, a standard MBean or an MXBean may appear to have some strange side effects. Let's see how the MBeanServer discriminates between these three kinds of MBeans:

First, the MBeanServer looks whether the provided MBean implements the DynamicMBean interface. If so, it's a Dynamic MBean, end of story.
Then the MBeanServer determines whether the provided MBean can be a standard MBean. If it does, or if one of its superclass does, then it's a standard MBean.
Lastly, if the MBean is neither a DynamicMBean nor a standard MBean, the MBeanServer looks whether it could be an MXBean.
If neither case apply, the MBeanServer will throw a NotCompliantMBeanException.

This leads to the following situations:

 

---------------------------------------------------------------------------
package example;

// Let's define some interfaces
public interface OneMBean { }
public interface TwoMBean extends OneMBean { }
public interface ThreeMXBean extends TwoMBean { }
public interface FourMBean extends ThreeMXBean { }

// Now let's define M(X)Beans that implement them...

// 'One' is a standard MBean
// Its management interface is 'OneMBean'.
public class One implements OneMBean { }

// 'Two' is a standard MBean, not an MXBean
// Its management interface is 'TwoMBean'.
public class Two implements ThreeMXBean { }

// 'TwoAndHalf' is an MXBean, not a standard MBean
// Its management interface is defined by 'ThreeMXBean'.
public class TwoAndHalf implements ThreeMXBean { }

// 'Three' is a standard MBean, not an MXBean
// Its management interface is 'TwoMBean'.
public class Three extends Two implements ThreeMXBean { }

// 'ThreeAndHalf' is an MXBean, not a standard MBean
// Its management interface is defined by 'ThreeMXBean'.
public class ThreeAndHalf extends TwoAndHalf;

// 'Four' is a standard MBean, not an MXBean
// Its management interface is 'FourMBean'.
public class Four extends ThreeAndHalf implements FourMBean;

---------------------------------------------------------------------------

 

The reasons why it is so have been explained above:

  • Two is an MBean because it implements TwoMBean. The pair (class example.Two, interface example.TwoMBean) thus satisfies the standard MBean pattern.
  • TwoAndHalf is not a standard MBean because it doesn't implement any interface called TwoAndHalfMBean, and none of its superclass is a standard MBean. However, TwoAndHalf implements ThreeMXBean. Therefore it's an MXBean.
  • Three is a standard MBean because one of its superclass (Two) is itself an MBean.
  • ThreeAndHalf is not a standard MBean because it doesn't implement any interface called ThreeAndHalfMBean, and none of its superclass is a standard MBean. However, ThreeAndHalf implements ThreeMXBean. Therefore it's an MXBean.
  • Finally Four is an MBean because it implements FourMBean. The pair (class example.Four, interface example.FourMBean) thus satisfies the standard MBean pattern.

 

This is where the class StandardMBean becomes interesting. Since StandardMBean removes all constraints on the naming of standard MBean interfaces, then StandardMBean also lets you decide whether to wrap your MBean as a standard MBean, or as an MXBean. Indeed StandardMBean has a constructor that will let you pass an isMxBean flag which tells whether the management interface you're selecting should be interpreted as a standard MBean interface or as an MXBean interface.

 

    final Three three = new Three(...);
    final StandardMBean mbean = 
        new StandardMBean(three,ThreeMXBean.class,true);
    
    // The MBean registered under 'name1' is a standard MBean.
    // Its interface is TwoMBean.
    server.registerMBean(three,name1);
    
    // The MBean registered under name2 is an MXBean. 
    // It's interface is ThreeMXBean
    server.registerMBean(mbean,name2);

 

The use case presented above is however a corner case, and it's very unlikely that you will ever need to use StandardMBean in this way. The next section will discuss some more interesting usages of the StandardMBean class, which apply both to wrapped MBean and wrapped MXBeans.

Customizing MBeanInfo

The principal use case for using StandardMBean, aside from selecting explicitely the management interface that should be exposed, is customization of the names and description exposed by the MBeanInfo.

Indeed the StandardMBean class defines a set of protected methods which provide convenient customization hooks to supply customized descriptions, parameter names, and operation impacts.

Having your MBean extend StandardMBean

To implement the customization hooks, the simplest way is to have your MBean simply extend StandardMBean:

 

---------------------------------------------------------------------------

public interface ThingMBean {
    public String getSomeItem();
    public String doWhatsIt(String thingummyjig);
}

---------------------------------------------------------------------------

public class Thing extends StandardMBean implements ThingMBean {

    Thing() throws NotCompliantMBeanException {
        super(ThingMBean.class);
    }
 
    public String getSomeItem() { ... }
    public String doWhatsIt(String thingummyjig) { ... }
    
    // Override customization hook:
    // Supply a customized description for MBeanInfo.getDescription();
    //
    protected String getDescription(MBeanInfo info) {
        return "A ThingMBean is an MBean that performs things.";
    }
    
    // Override customization hook:
    // Supply a customized description for attribute "SomeItem"
    //
    protected String getDescription(MBeanAttributeInfo info) {
        String description = null;
        if (info.getName().equals("SomeItem")) {
            description = 
               "This attribute represents some item obtained from a thing.";
        }
        return description;
    }
    
    // Override customization hook:
    // Supply a customized description for parameters of
    // of "doWhatsIt"
    //
    protected String getDescription(MBeanOperationInfo op,
            MBeanParameterInfo param,
            int sequence) {
        if (op.getName().equals("doWhatsIt")) {
            switch (sequence) {
                // 0 is first parameter: "thingummyjig"
                case 0: return "A contraption used for whatsIt";
                default : return null;
            }
        }
        return null;
    }
    
    // Override customization hook:
    // Supply a customized name for parameters of
    // of "doWhatsIt"
    //
    protected String getParameterName(MBeanOperationInfo op,
            MBeanParameterInfo param,
            int sequence) {
        if (op.getName().equals("doWhatsIt")) {
            switch (sequence) {
                // 0 is first parameter: "thingummyjig"
                case 0: return "thingummyjig";
                default : return null;
            }
        }
        return null;
    }
    
    // Override customization hook:
    // Supply a customized name for operation "doWhatsIt"
    //
    protected String getDescription(MBeanOperationInfo info) {
        String description = null;
        MBeanParameterInfo[] params = info.getSignature();
        String[] signature = new String[params.length];
        for (int i = 0; i < params.length; i++)
            signature[i] = params[i].getType();
        String[] methodSignature;
        
        methodSignature = new String[] {
            java.lang.String.class.getName()
            };
        if (info.getName().equals("doWhatsIt") && 
            Arrays.equals(signature, methodSignature)) {
            description = "Performs whatsit with a thingummyjig";
        }
        
        return description;
    }
}

---------------------------------------------------------------------------

 

The table below shows what you see in JConsole before and after customization:

 

Before Customization
After Customization

 

If you are using the JMX plugin for NetBeans, and if you use the wizzard to create a JMX MBean that extends StandardMBean, then NetBeans will automatically generate the implementations of these hooks for you, based on the various descriptions that you supply in the JMX MBean creation wizzard.

A small variation of the above use case is discussed below.
Rather than having your MBean extend StandardMBean, you can also choose to implement a subclass of StandardMBean dedicated to your MBean interface:

 

---------------------------------------------------------------------------

public interface ThingMBean {
    public String getSomeItem();
    public String doWhatsIt(String thingummyjig);
}

---------------------------------------------------------------------------

public class Thing implements ThingMBean {
    public Thing() { ... } 
    public String getSomeItem() { ... };
    public String doWhatsIt(String thingummyjig) { ... };
}

---------------------------------------------------------------------------

public class StandardThing extends StandardMBean {
    public StandardThing(ThingMBean athing) throws NotCompliantMBeanException {
        super(athing,ThingMBean.class);
    }
    
    // All customization hooks implemented as shown above.
    // 
    protected String getDescription(....) { ... }
}

---------------------------------------------------------------------------
                

 

Finally, you could also create a generic subclass of StandardMBean that would load all descriptions from e.g. a localized ResourceBundle.

Create a generic subclass of StandardMBean

Since this blog is starting to become quite long, I will not give the full implementation of such a class here. This might be the subject of another blog. Let me however give an outline:

 

---------------------------------------------------------------------------

public class LocalizedStandardMBean extends StandardMBean {

    private final ResourceBundle bundle;
    
    private static String getBundleNameFor(Class<?> interfaceClass) {
        return ....;
    }
    
    public <T> LocalizedStandardMBean(T impl, Class<T> interfaceClass, 
                  boolean isMxBean, Locale locale) {
        super(impl,interfaceClass,isMxBean);
        bundle=ResourceBundle.getBundle(getBundleNameFor(interfaceClass),locale);
    }
    
    protected String getDescription(....) { 
        // Use 'bundle' to extract localized description.
        return ... ;
    }
    ...
}

---------------------------------------------------------------------------

 

However, creating a generic subclass of StandardMBean has its own limitations. Since you will need to accomodate both MBeans which are NotificationEmitters and MBeans which are not, you will also most probably need to create a similar subclass of StandardEmitterMBean too. This point is exposed below.

Other use cases

An other common use case for using StandardMBean is when you want to intercept all calls to a particular MBean in order to perform some pre/post operations. For instance, let's assume that I want to log all setAttribute actions performed on a particularly sensitive MBean. One way to do that would be to wrap that MBean in a subclass of StandardMBean, in which I would override the setAttribute method.

 

---------------------------------------------------------------------------

public class SetLoggingStandardMBean extends StandardMBean {
    private final Logger logger;
    public SetLoggingStandardMBean(T impl, Class<T> interfaceClass, 
                  boolean isMxBean) {
        super(impl,interfaceClass,isMxBean);
        logger = ....;
    }
   
    // This method is defined in the DynamicMBean interface, and
    // implemented by StandardMBean. Don't forget that StandardMBean turns 
    // your standard MBean into a DynamicMBean!
    // We override this method here to perform some logging before and 
    // after setting an attribute.
    //
    public void setAttribute(Attribute attr) 
                  throws AttributeNotFoundException,
                         InvalidAttributeValueException,
                         MBeanException,
                         ReflectionException {
        // Log...
        logger.config("Setting attribute: " + attr.getName() + "=" + 
                    attr.getValue());
        Exception failed = null;
        try {
            super.setAttribute(attr);
        } catch (AttributeNotFoundException x) {
            failed=x; throw x;
        } catch (InvalidAttributeValueException x) {
            failed=x; throw x;
        } catch (MBeanException x) {
            failed=x; throw x;
        } catch (ReflectionException x) {
            failed=x; throw x;
        } catch (RuntimeException x) {
            failed=x; throw x;
        } finally {
            if (failed == null) { // set was OK
                logger.config("attribute "+ attr.getName() + " successfully set");
            } else { // set failed
                logger.config("failed to set attribute "+ attr.getName() + 
                        ": " + failed);
            }
        }
    }
}

---------------------------------------------------------------------------

 

Known Hassles and Limitations

As I have already hinted in the previous paragraph, all is not completely trivial with StandardMBean. In this section we will discuss some points that need to be taken into account.

Notification Emitters

If your MBean is a NotificationEmitter, then you must not wrap it directly using StandardMBean. You must use StandardEmitterMBean instead.

Indeed, StandardMBean does not implement the NotificationEmitter interface. Otherwise, any MBean wrapped in a StandardMBean would be seen as a NotificationEmitter. Therefore the rule is:

  • If your MBean is not a notification emitter, use StandardMBean, or a subclass of StandardMBean which does not implement the NotificationEmitter interface.
  • If your MBean is a notification emitter, use StandardEmitterMBean, or a subclass of StandardEmitterMBean.

StandardEmitterMBean is a subclass of StandardMBean, and therefore all the tips I have given for StandardMBean in this blog also apply to StandardEmitterMBean.

A side effect of needing two classes (StandardMBean, StandardEmitterMBean) depending on whether an MBean is a NotificationEmitter or not, is that each time you find a use case that requires to extend StandardMBean, you're probably going to need to extend StandardEmitterMBean as well. Eventually, this can lead to quite complex combinatory situations. I would therefore advise to analyze the situation carefully before deciding to provide a generic purpose subclass of StandardMBean. In my next blog, I'll show how this pitfall can sometime be avoided.

MBeanRegistration

Another point worth noting is that StandardMBean does not forward callbacks from the MBeanRegistration interface to its wrapped MBean implementation. If your MBean implements MBeanRegistration, and if you wrap it in a StandardMBean - or in a StandardEmitterMBean, its preRegister method etc... will not be called. There's already RFE 6450834 logged for that - and it will hopefully be fixed in JDK 7.

In the mean time you may want to use your own subclass of StandardMBean if you need to work around that, or wait for my next blog, which will provide a more generic solution.

Constructors

You may not have noticed, but wrapping an MBean in a StandardMBean (using new StandardMBean(...);) instead of registering it directly, has the side effect of making all MBeanConstructorInfo disapear from its MBeanInfo. This is not a bug, but a conscious choice. The constructors exposed are those of the wrapped implementation, and would not allow to re-create an MBean wrapped in a StandardMBean instance.

The only case where constructors are kept is when you make your own MBean extend StandardMBean, and when the wrapped implementation is this. In that case, the StandardMBean is the MBean, and may therefore be reconstructed using createMBean.

ClassLoaders

Finally, here is the most obscure point. Sometimes, a JMXConnectorServer will need to figure out which ClassLoader to use when deserializing parameters sent by a remote client. It usually calls MBeanServer.getClassLoaderFor() for that purpose. The MBeanServer will return the class loader from which the MBean class was loaded. So if your MBean is a Thing, the MBeanServer will return Thing.getClass().getClassLoader(). However, if your MBean was wrapped in a StandardMBean, what is returned is StandardMBean.class.getClassLoader(), which happens to be null (on JDK 5 and later) since StandardMBean is in rt.jar. So if your MBean happens to use custom types, is not an MXBean, and is not on the CLASSPATH, it usually result in a ClassNotFoundException fired back to the client.

One way to work around this is usually to create an anonymous subclass of StandardMBean when registering your MBean - e.g. something like:

 

    // Creating an anonymous subclass works only if you create it from a class
    // that was loaded by a ClassLoader that has access to your MBean class
    // ('Thing' in this case).
    // This is obviously the case in this snippet of code, because we were
    // able to write: final Thing thing = new Thing(...);
    //
    final Thing thing = new Thing(...);
    final StandardMBean mbean = new StandardMBean(thing, ThingMBean.class) { };
    server.registerMBean(mbean,name);

 

Don't be too worried, it's unlikely that you will need to resort to such tricks.

The End

So, this is the end of my long blog, and I hope you have enjoyed your journey in the bowels of StandardMBean.

Next time I'll expose a smart StandardMBeanFactory that will work around the MBeanRegistration problem.

Stay tuned to JMX, SNMP, Java, et caetera!

Cheers
-- daniel

分享到:
评论

相关推荐

    图片处理 javax.imageio.IIOException: Unsupported Image Type

    在Java编程环境中,图片处理是一项常见的任务,但有时可能会遇到“javax.imageio.IIOException: Unsupported Image Type”的错误。这个异常通常表示Java的`ImageIO`类库无法识别或支持所尝试读取或写入的图像格式。...

    javax.servlet-3.0.0.v201112011016-API文档-中文版.zip

    赠送jar包:javax.servlet-3.0.0.v201112011016.jar; 赠送原API文档:javax.servlet-3.0.0.v201112011016-javadoc.jar; 赠送源代码:javax.servlet-3.0.0.v201112011016-sources.jar; 赠送Maven依赖信息文件:...

    javax.servlet-3.0.0.v201112011016-API文档-中英对照版.zip

    赠送jar包:javax.servlet-3.0.0.v201112011016.jar; 赠送原API文档:javax.servlet-3.0.0.v201112011016-javadoc.jar; 赠送源代码:javax.servlet-3.0.0.v201112011016-sources.jar; 赠送Maven依赖信息文件:...

    javax.activation.UnsupportedDataTypeException: no object DCH for MIME type

    在Oracle 11.2.0.4中调用 javax.mail 发送邮件时可能会遇到以下错误: ORA-29532: Java call terminated by uncaught Java exception: javax.mail.MessagingException: IOException while sending message; nested ...

    javax.annotation-api-1.3.2-API文档-中文版.zip

    赠送jar包:javax.annotation-api-1.3.2.jar; 赠送原API文档:javax.annotation-api-1.3.2-javadoc.jar; 赠送源代码:javax.annotation-api-1.3.2-sources.jar; 赠送Maven依赖信息文件:javax.annotation-api-...

    javax.naming.NamingException: Cannot create resource instance

    javax.naming.NamingException: Cannot create resource instance类加载异常,希望可以帮助跟我一样错误的人。

    javax.mail-1.5.6-API文档-中文版.zip

    赠送jar包:javax.mail-1.5.6.jar; 赠送原API文档:javax.mail-1.5.6-javadoc.jar; 赠送源代码:javax.mail-1.5.6-sources.jar; 赠送Maven依赖信息文件:javax.mail-1.5.6.pom; 包含翻译后的API文档:javax.mail...

    jar包_javax.annotation.zip

    在本例中,我们关注的`jar`包是`javax.annotation.zip`,它包含了`javax.annotation`相关的API,主要用于处理Java的注解(Annotation)。 `javax.annotation`是Java标准版(Java SE)的一部分,提供了标准的注解,...

    javax.mail-1.6.2-API文档-中英对照版.zip

    赠送jar包:javax.mail-1.6.2.jar; 赠送原API文档:javax.mail-1.6.2-javadoc.jar; 赠送源代码:javax.mail-1.6.2-sources.jar; 赠送Maven依赖信息文件:javax.mail-1.6.2.pom; 包含翻译后的API文档:javax.mail...

    javax.servlet-api-3.1.0.jar中文文档.zip

    javax.servlet-api-***.jar中文文档.zip,java,javax.servlet-api-***.jar,javax.servlet,javax.servlet-api,***,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,javax,servlet,api,中文API文档,手册,开发...

    javax.servlet jar包---解决找不到javax.servlet.*等问题

    当你遇到“找不到javax.servlet.*”这样的错误时,通常是因为你的项目缺少了这个库,所以需要引入`javax.servlet.jar`来解决问题。 1. **Java Servlet简介** Java Servlet是Java平台上的一个标准,用于扩展服务器...

    javax.transaction-api-1.2-API文档-中文版.zip

    赠送jar包:javax.transaction-api-1.2.jar; 赠送原API文档:javax.transaction-api-1.2-javadoc.jar; 赠送源代码:javax.transaction-api-1.2-sources.jar; 赠送Maven依赖信息文件:javax.transaction-api-1.2....

    javax.activation-1.2.0-API文档-中文版.zip

    赠送jar包:javax.activation-1.2.0.jar; 赠送原API文档:javax.activation-1.2.0-javadoc.jar; 赠送源代码:javax.activation-1.2.0-sources.jar; 赠送Maven依赖信息文件:javax.activation-1.2.0.pom; 包含...

    javax.annotation-api-1.2-API文档-中文版.zip

    赠送jar包:javax.annotation-api-1.2.jar; 赠送原API文档:javax.annotation-api-1.2-javadoc.jar; 赠送源代码:javax.annotation-api-1.2-sources.jar; 赠送Maven依赖信息文件:javax.annotation-api-1.2.pom;...

    android DES加密解密 javax.crypto.IllegalBlockSizeException: last block incomplete in

    本话题将围绕在Android中使用DES加密和解密时遇到的一个常见问题展开:`javax.crypto.IllegalBlockSizeException: last block incomplete in decryption`。 这个异常通常发生在解密过程中,当输入的数据块大小与DES...

    javax.servlet-api-4.0.1.jar中文文档.zip

    javax.servlet-api-***.jar中文文档.zip,java,javax.servlet-api-***.jar,javax.servlet,javax.servlet-api,***,jar包,Maven,第三方jar包,组件,开源组件,第三方组件,Gradle,javax,servlet,api,中文API文档,手册,开发...

    javax.xml.rpc相关jar包

    描述中提到的“解决javax.xml jar包缺失的问题,引进javax.xml.rpc-api-1.1.1.jar”,意味着在开发或运行某个Java项目时,可能会遇到由于缺少`javax.xml.rpc`相关的jar包导致的编译或运行错误。为了解决这个问题,...

    com.springsource.javax.media.jai.core-1.1.3.jar

    import javax.media.jai.JAI; import javax.media.jai.RenderedOp; jai_core-1.1.3.jar jai_codec-1.1.3.jar

    javax.websocket-api-1.1-API文档-中文版.zip

    赠送jar包:javax.websocket-api-1.1.jar; 赠送原API文档:javax.websocket-api-1.1-javadoc.jar; 赠送源代码:javax.websocket-api-1.1-sources.jar; 赠送Maven依赖信息文件:javax.websocket-api-1.1.pom; ...

    javax.activation-api-1.2.0-API文档-中文版.zip

    赠送jar包:javax.activation-api-1.2.0.jar; 赠送原API文档:javax.activation-api-1.2.0-javadoc.jar; 赠送源代码:javax.activation-api-1.2.0-sources.jar; 赠送Maven依赖信息文件:javax.activation-api-...

Global site tag (gtag.js) - Google Analytics