Jersey注释的注释处理器

时间:2016-04-29 07:31:26

标签: java eclipse rest annotations jersey

我很想知道针对Jersey注释的注释处理器,例如@Path@Consumes等。我的意思是,这会由Jersey API还是javax.ws.rs-api-xxx.jar提供?

我似乎无法弄清楚这些REST注释是如何处理的,我可以通过探索代码来找到它。

单击@Path将我的定义带给我,但我需要知道处理此注释的逻辑。

我正在使用Eclipse,并且未启用项目特定的注释。

在tomcat中部署时,我的其余资源可以正常工作,这意味着某些处理器会处理注释。

2 个答案:

答案 0 :(得分:2)

注释易于处理,无需任何特殊处理器。例如,使用@Path,您可以执行类似

的操作
@Path("orders")
public class OrdersResource {}

Path annotationInstance = OrdersResource.class.getAnnotation(Path.class);
String pathValue = annotationInstance.value();

主要是从这里拿走的东西通常是你从一些Reflection获得注释的实例。然后只需调用注释上的方法来获取要处理的任何值,或者只检查marker annotations进行一些处理。

当Jersey处理资源类时,它使用注释构建内部模型。在伪代码中,它可能看起来像

@Path("orders")
public class OrdersResource {
    @GET
    @Produces("text/plain")
    public String get() {}
}

Path anno = OrdersResource.class.getAnnotation(Path.class);
String path = anno.value();
Resource resource = new Resource(path);

Method[] methods = OrdersResource.class.getDeclaredMethods();
for (Method method: methods) {
    Annotation[] methodAnnos = method.getAnnotations();
    if (arrayContains(methodAnnos, (@GET, @POST, @PUT, etc)) {
        String httpMethod = getMethod(methodAnnos);
        ResourceMethod resourceMethod = new ResourceMethod(httpMethod);
        Produces producesAnno = method.getAnnotation(Produces.class);
        if (produces != null) {
            resourceMethod.setProduces(producesAnno.value());
        }
        resource.addResourceMethod(resourceMethod);
    }
}

使用此资源模型,Jersey使用它来处理请求。

现在上面都是伪代码。反射代码是真实的,但ResourceResourceMethod的API是虚构的。但泽西岛真的有那些用来模拟资源的类。例如,您可以

Resource resource = Resource.from(OrdersResource.class);

就像那样,我们有模特。使用我们可以做的模型

String path = resource.getPath();
ResourceMethod method = resource.getResourceMethods();

另请参阅: Introspecting Jersey resource model Jersey 2.x以获取有关如何内省模型的更多说明。

当你说Resource resource = Resource.from(OrdersResource.class)时,处理注释的如何的确切实现。我认为那是内部私人的东西。我不知道是否暴露了。但它几乎肯定会使用反射。唯一的另一种方法是内省字节代码。我不认为这是怎么做的(虽然不引用我)。

答案 1 :(得分:1)

根据泽西版本,当部署描述符加载jersey servlet时,servlet会扫描资源,并在内部servlet调用ResourceConfig,并完成所有注释处理。例如:

<servlet>
    <servlet-name>jersey-spring</servlet-name>
    <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
    <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>com.myproject.resources</param-value>
        </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>

现在基于上面的web.xml,springServlet扫描所有资源。然后它调用ResourceConfig,这就是所有注释如路径等的注意事项:

/**
 * The resource configuration for configuring a web application.
 */
public abstract class ResourceConfig extends Application implements FeaturesAndProperties {
    private static final Logger LOGGER = 
            Logger.getLogger(ResourceConfig.class.getName());


    public static final String FEATURE_NORMALIZE_URI 
            = "com.sun.jersey.config.feature.NormalizeURI";


    public static final String FEATURE_CANONICALIZE_URI_PATH 
            = "com.sun.jersey.config.feature.CanonicalizeURIPath";


    public static final String FEATURE_REDIRECT 
            = "com.sun.jersey.config.feature.Redirect";


    public static final String FEATURE_MATCH_MATRIX_PARAMS 
            = "com.sun.jersey.config.feature.IgnoreMatrixParams";


    public static final String FEATURE_TRACE
            = "com.sun.jersey.config.feature.Trace";


    public static final String FEATURE_TRACE_PER_REQUEST
            = "com.sun.jersey.config.feature.TracePerRequest";


    public static final String PROPERTY_MEDIA_TYPE_MAPPINGS
            = "com.sun.jersey.config.property.MediaTypeMappings";


    public static final String PROPERTY_LANGUAGE_MAPPINGS
            = "com.sun.jersey.config.property.LanguageMappings";


    public static final String PROPERTY_DEFAULT_RESOURCE_COMPONENT_PROVIDER_FACTORY_CLASS
            = "com.sun.jersey.config.property.DefaultResourceComponentProviderFactoryClass";


    public static final String PROPERTY_CONTAINER_NOTIFIER = 
            "com.sun.jersey.spi.container.ContainerNotifier";

    public static final String PROPERTY_CONTAINER_REQUEST_FILTERS = 
            "com.sun.jersey.spi.container.ContainerRequestFilters";

    /**
     * If set the list of {@link ContainerResponseFilter} that are applied
     * to filter the response. When applying the list of response filters to
     * a response each response filter is applied, in order, from the first to
     * the last entry in the list.
     * <p>
     * The instance may be a String[] or String that contains one or more fully
     * qualified class name of a request filter class separated by ';', ','
     * or ' ' (space).
     * Otherwise the instance may be List containing instances of String,
     * String[], Class&lt;? extends ContainerResponseFilter;&gt; or instances
     * of ContainerResponseFilter.
     * <p>
     * If a String[] or String of fully qualified class names or a Class then
     * each class is instantiated as a singleton. Thus, if there is more than one
     * class registered for this property or the same class is also registered for
     * the {@link #PROPERTY_CONTAINER_REQUEST_FILTERS} property then only
     * one instance will be instatiated.
     *
     * @see com.sun.jersey.api.container.filter
     */
    public static final String PROPERTY_CONTAINER_RESPONSE_FILTERS = 
            "com.sun.jersey.spi.container.ContainerResponseFilters";

    /**
     * If set the list of {@link ResourceFilterFactory} that are applied
     * to resources. When applying the list of resource filters factories to a
     * request each resource filter factory is applied, in order, from the first
     * to last entry in the list.
     * <p>
     * The instance may be a String[] or String that contains one or more fully
     * qualified class name of a response filter class separated by ';', ','
     * or ' ' (space).
     * Otherwise the instance may be List containing instances of String,
     * String[], Class&lt;? extends ResourceFilterFactory;&gt; or instances
     * of ResourceFilterFactory.
     * <p>
     * If a String[] or String of fully qualified class names or a Class then
     * each class is instantiated as a singleton. Thus, if there is more than one
     * class registered for this property one instance will be instatiated.
     * 
     * @see com.sun.jersey.api.container.filter
     */
    public static final String PROPERTY_RESOURCE_FILTER_FACTORIES =
            "com.sun.jersey.spi.container.ResourceFilters";

    /**
     * If set the wadl generator configuration that provides a {@link WadlGenerator}.
     * <p>
     * The type of this property must be a subclass or an instance of a subclass of
     * {@link com.sun.jersey.api.wadl.config.WadlGeneratorConfig}.
     * </p>
     * <p>
     * If this property is not set the default wadl generator will be used for generating wadl.
     * </p>
     */
    public static final String PROPERTY_WADL_GENERATOR_CONFIG = 
            "com.sun.jersey.config.property.WadlGeneratorConfig";

    /**
     * Common delimiters used by various properties.
     */
    public static final String COMMON_DELIMITERS = " ,;";

    /**
     * Get the map of features associated with the Web application.
     *
     * @return the features.
     *         The returned value shall never be null.
     */
    public abstract Map<String, Boolean> getFeatures();

    /**
     * Get the value of a feature.
     *
     * @param featureName the feature name.
     * @return true if the feature is present and set to true, otherwise false
     *         if the feature is present and set to false or the feature is not 
     *         present.
     */
    public abstract boolean getFeature(String featureName);

    /**
     * Get the map of properties associated with the Web application.
     *
     * @return the properties.
     *         The returned value shall never be null.
     */
    public abstract Map<String, Object> getProperties();

    /**
     * Get the value of a property.
     *
     * @param propertyName the property name.
     * @return the property, or null if there is no property present for the
     *         given property name.
     */
    public abstract Object getProperty(String propertyName);

    /**
     * Get a map of file extension to media type. This is used to drive 
     * URI-based content negotiation such that, e.g.:
     * <pre>GET /resource.atom</pre>
     * <p>is equivalent to:</p>
     * <pre>GET /resource
     *Accept: application/atom+xml</pre>
     * <p>
     * The default implementation returns an empty map.
     *
     * @return a map of file extension to media type
     */
    public Map<String, MediaType> getMediaTypeMappings() {
        return Collections.emptyMap();
    }

    /**
     * Get a map of file extension to language. This is used to drive 
     * URI-based content negotiation such that, e.g.:
     * <pre>GET /resource.english</pre>
     * <p>is equivalent to:</p>
     * <pre>GET /resource
     *Accept-Language: en</pre>
     * <p>
     * The default implementation returns an empty map.
     * 
     * @return a map of file extension to language
     */
    public Map<String, String> getLanguageMappings() {
        return Collections.emptyMap();
    }

    /**
     * Get a map of explicit root resource classes and root resource singleton
     * instances. The default lifecycle for root resource class instances is
     * per-request.
     * <p>
     * The root resource path template is declared using the key in the map. This
     * is a substitute for the declaration of a {@link Path} annotation on a root
     * resource class or singleton instance. The key has the same semantics as the
     * {@link Path#value() }. If such a {@link Path} annotation is present
     * it will be ignored.
     * <p>
     * For example, the following will register two root resources, first
     * a root resource class at the path "class" and a root resource singleton
     * at the path "singleton":
     * <blockquote><pre>
     *     getExplicitRootResources().put("class", RootResourceClass.class);
     *     getExplicitRootResources().put("singleton", new RootResourceSingleton());
     * </pre></blockquote>
     *
     * @return a map of explicit root resource classes and root resource 
     *         singleton instances.
     */
    public Map<String, Object> getExplicitRootResources() {
        return Collections.emptyMap();
    }

    /**
     * Validate the set of classes and singletons.
     * <p>
     * A registered class is removed from the set of registered classes
     * if an instance of that class is a member of the set of registered
     * singletons.
     * <p>
     * A registered class that is an interface or an abstract class
     * is removed from the registered classes.
     * <p>
     * File extension to media type and language mappings in the properties
     * {@link #PROPERTY_MEDIA_TYPE_MAPPINGS} and {@link #PROPERTY_LANGUAGE_MAPPINGS},
     * respectively, are processed and key/values pairs added to the maps
     * returned from {@link #getMediaTypeMappings() } and 
     * {@link #getLanguageMappings() }, respectively. The characters of file
     * extension values will be contextually encoded according to the set of 
     * valid characters defined for a path segment.
     * 
     * @throws IllegalArgumentException if the set of registered singletons 
     *         contains more than one instance of the same root resource class,
     *         or validation of media type and language mappings failed.
     */
    public void validate() {
        // Remove any registered classes if instances exist in registered 
        // singletons
        Iterator<Class<?>> i = getClasses().iterator();
        while (i.hasNext()) {
            Class<?> c = i.next();
            for (Object o : getSingletons()) {
                if (c.isInstance(o)) {
                    i.remove();
                    LOGGER.log(Level.WARNING, 
                            "Class " + c.getName() + 
                            " is ignored as an instance is registered in the set of singletons");                    
                }
            }            
        }

        // Find conflicts
        Set<Class<?>> objectClassSet = new HashSet<Class<?>>();
        Set<Class<?>> conflictSet = new HashSet<Class<?>>();
        for (Object o : getSingletons()) {
            if (o.getClass().isAnnotationPresent(Path.class)) {
                if (objectClassSet.contains(o.getClass())) {
                    conflictSet.add(o.getClass());
                } else {
                    objectClassSet.add(o.getClass());
                }
            }
        }

        if (!conflictSet.isEmpty()) {
            for (Class<?> c : conflictSet) {
                LOGGER.log(Level.SEVERE, 
                        "Root resource class " + c.getName() + 
                        " is instantiated more than once in the set of registered singletons");
            }
            throw new IllegalArgumentException(
                    "The set of registered singletons contains " +
                    "more than one instance of the same root resource class");
        }

        // parse and validate mediaTypeMappings set thru PROPERTY_MEDIA_TYPE_MAPPINGS property
        parseAndValidateMappings(ResourceConfig.PROPERTY_MEDIA_TYPE_MAPPINGS,
                getMediaTypeMappings(), new TypeParser<MediaType>() {
            public MediaType valueOf(String value) {
                return MediaType.valueOf(value);
            }
        });

        // parse and validate language mappings set thru PROPERTY_LANGUAGE_MAPPINGS property
        parseAndValidateMappings(ResourceConfig.PROPERTY_LANGUAGE_MAPPINGS,
                getLanguageMappings(), new TypeParser<String>() {
            public String valueOf(String value) {
                return LanguageTag.valueOf(value).toString();
            }
        });

        // encode key values of mediaTypeMappings and languageMappings maps
        encodeKeys(getMediaTypeMappings());
        encodeKeys(getLanguageMappings());
    }

    private interface TypeParser<T> {
        public T valueOf(String s);
    }

    private <T> void parseAndValidateMappings(String property,
            Map<String, T> mappingsMap, TypeParser<T> parser) {
        Object mappings = getProperty(property);
        if (mappings == null)
            return;

        if (mappings instanceof String) {
            parseMappings(property, (String) mappings, mappingsMap, parser);
        } else if (mappings instanceof String[]) {
            final String[] mappingsArray = (String[])mappings;
            for (int i = 0; i < mappingsArray.length; i++)
                parseMappings(property, mappingsArray[i], mappingsMap, parser);
        } else {
            throw new IllegalArgumentException("Provided " + property +
                    " mappings is invalid. Acceptable types are String" +
                    " and String[].");
        }
    }

    private <T> void parseMappings(String property, String mappings,
            Map<String, T> mappingsMap, TypeParser<T> parser) {
        if (mappings == null)
            return;

        String[] records = mappings.split(",");

        for(int i = 0; i < records.length; i++) {
            String[] record = records[i].split(":");
            if (record.length != 2)
                throw new IllegalArgumentException("Provided " + property +
                        " mapping \"" + mappings + "\" is invalid. It " +
                        "should contain two parts, key and value, separated by ':'.");

            String trimmedSegment = record[0].trim();
            String trimmedValue = record[1].trim();

            if (trimmedSegment.length() == 0)
                throw new IllegalArgumentException("The key in " + property +
                        " mappings record \"" + records[i] + "\" is empty.");
            if (trimmedValue.length() == 0)
                throw new IllegalArgumentException("The value in " + property +
                        " mappings record \"" + records[i] + "\" is empty.");

            mappingsMap.put(trimmedSegment, parser.valueOf(trimmedValue));
        }
    }

    private <T> void encodeKeys(Map<String, T> map) {
        Map<String, T> tempMap = new HashMap<String, T>();
        for(Map.Entry<String, T> entry : map.entrySet())
            tempMap.put(UriComponent.contextualEncode(entry.getKey(), UriComponent.Type.PATH_SEGMENT), entry.getValue());
        map.clear();
        map.putAll(tempMap);
    }

    /**
     * Get the set of root resource classes.
     * <p>
     * A root resource class is a registered class that is annotated with
     * Path.
     * 
     * @return the set of root resource classes.
     */
    public Set<Class<?>> getRootResourceClasses() {
        Set<Class<?>> s = new LinkedHashSet<Class<?>>();

        for (Class<?> c : getClasses()) {
            if (isRootResourceClass(c))
                s.add(c);
        }

        return s;
    }

    /**
     * Get the set of provider classes.
     * <p>
     * A provider class is a registered class that is not annotated with
     * Path.
     * 
     * @return the set of provider classes.
     */
    public Set<Class<?>> getProviderClasses() {
        Set<Class<?>> s = new LinkedHashSet<Class<?>>();

        for (Class<?> c : getClasses()) {
            if (!isRootResourceClass(c))
                s.add(c);
        }

        return s;
    }

    /**
     * Get the set of root resource singleton instances.
     * <p>
     * A root resource singleton instance is a registered instance whose class
     * is annotated with Path.
     * 
     * @return the set of root resource singleton instances.
     */
    public Set<Object> getRootResourceSingletons() {
        Set<Object> s = new LinkedHashSet<Object>();

        for (Object o : getSingletons()) {
            if (isRootResourceClass(o.getClass()))
                s.add(o);
        }

        return s;
    }

    /**
     * Get the set of provider singleton instances.
     * <p>
     * A provider singleton instances is a registered instance whose class
     * is not annotated with Path.
     * 
     * @return the set of provider singleton instances.
     */
    public Set<Object> getProviderSingletons() {
        Set<Object> s = new LinkedHashSet<Object>();

        for (Object o : getSingletons()) {
            if (!isRootResourceClass(o.getClass()))
                s.add(o);
        }

        return s;
    }

    /**
     * Determine if a class is a root resource class.
     *
     * @param c the class.
     * @return true if the class is a root resource class, otherwise false
     *         (including if the class is null).
     */
    public static boolean isRootResourceClass(Class<?> c) {
        if (c == null)
            return false;

        if (c.isAnnotationPresent(Path.class)) return true;

        for (Class i : c.getInterfaces())
            if (i.isAnnotationPresent(Path.class)) return true;

        return false;
    }

    /**
     * Determine if a class is a provider class.
     *
     * @param c the class.
     * @return true if the class is a provider class, otherwise false
     *         (including if the class is null)
     */
    public static boolean isProviderClass(Class<?> c) {
        return c != null && c.isAnnotationPresent(Provider.class);
    }

    /**
     * Get the list of container request filters.
     * <p>
     * This list may be modified to add or remove filter elements.
     * See {@link #PROPERTY_CONTAINER_REQUEST_FILTERS} for the valid elements
     * of the list.
     *
     * @return the list of container request filters.
     *         An empty list will be returned if no filters are present.
     */
    public List getContainerRequestFilters() {
        return getFilterList(PROPERTY_CONTAINER_REQUEST_FILTERS);
    }

    /**
     * Get the list of container response filters.
     * <p>
     * This list may be modified to add or remove filter elements.
     * See {@link #PROPERTY_CONTAINER_RESPONSE_FILTERS} for the valid elements
     * of the list.
     *
     * @return the list of container response filters.
     *         An empty list will be returned if no filters are present.
     */
    public List getContainerResponseFilters() {
        return getFilterList(PROPERTY_CONTAINER_RESPONSE_FILTERS);
    }

    /**
     * Get the list of resource filter factories.
     * <p>
     * This list may be modified to add or remove filter elements.
     * See {@link #PROPERTY_RESOURCE_FILTER_FACTORIES} for the valid elements
     * of the list.
     *
     * @return the list of resource filter factories.
     *         An empty list will be returned if no filters are present.
     */
    public List getResourceFilterFactories() {
        return getFilterList(PROPERTY_RESOURCE_FILTER_FACTORIES);
    }

    private List getFilterList(String propertyName) {
        final Object o = getProperty(propertyName);
        if (o == null) {
            final List l = new ArrayList();
            getProperties().put(propertyName, l);
            return l;
        } else if (o instanceof List) {
            return (List)o;
        } else {
            final List l = new ArrayList();
            l.add(o);
            getProperties().put(propertyName, l);
            return l;
        }
    }

    /**
     * Set the properties and features given a map of entries.
     *
     * @param entries the map of entries. All entries are added as properties.
     * Properties are only added if an existing property does not currently exist.
     *
     * Any entry with a value that is an instance of Boolean is added as a
     * feature with the feature name set to the entry name and the feature value
     * set to the entry value. Any entry with a value that is an instance String
     * and is equal (ignoring case and white space) to "true" or "false" is added
     * as a feature with the feature name set to the entry name and the feature
     * value set to the Boolean value of the entry value. Features are only added
     * if an existing feature does not currently exist.
     */
    public void setPropertiesAndFeatures(Map<String, Object> entries) {
        for (Map.Entry<String, Object> e : entries.entrySet()) {
            if (!getProperties().containsKey(e.getKey())) {
                getProperties().put(e.getKey(), e.getValue());
            }

            if (!getFeatures().containsKey(e.getKey())) {
                Object v = e.getValue();
                if (v instanceof String) {
                    String sv = ((String)v).trim();
                    if (sv.equalsIgnoreCase("true")) {
                        getFeatures().put(e.getKey(), true);
                    } else if (sv.equalsIgnoreCase("false")) {
                        getFeatures().put(e.getKey(), false);
                    }
                } else if (v instanceof Boolean) {
                    getFeatures().put(e.getKey(), (Boolean)v);
                }
            }
        }
    }

    /**
     * Add the state of an {@link Application} to this instance.
     *
     * @param app the application.
     */
    public void add(Application app) {
        if (app.getClasses() != null)
            addAllFirst(getClasses(), app.getClasses());
        if (app.getSingletons() != null)
            addAllFirst(getSingletons(), app.getSingletons());

        if (app instanceof ResourceConfig) {
            ResourceConfig rc = (ResourceConfig)app;

            getExplicitRootResources().putAll(rc.getExplicitRootResources());

            getLanguageMappings().putAll(rc.getLanguageMappings());
            getMediaTypeMappings().putAll(rc.getMediaTypeMappings());

            getFeatures().putAll(rc.getFeatures());
            getProperties().putAll(rc.getProperties());
        }
    }

    private <T> void addAllFirst(Set<T> a, Set<T> b) {
        Set<T> x = new LinkedHashSet<T>();
        x.addAll(b);
        x.addAll(a);

        a.clear();
        a.addAll(x);
    }

    /**
     * Clone this resource configuration.
     * <p>
     * The set of classes, set of singletons, map of explicit root resources,
     * map of language mappings, map of media type mappings, map of features and
     * map of properties will be cloned.
     *
     * @return a cloned instance of this resource configuration.
     */
    @SuppressWarnings({"CloneDoesntCallSuperClone", "CloneDoesntDeclareCloneNotSupportedException"})
    @Override
    public ResourceConfig clone() {
        ResourceConfig that = new DefaultResourceConfig();

        that.getClasses().addAll(this.getClasses());
        that.getSingletons().addAll(this.getSingletons());

        that.getExplicitRootResources().putAll(this.getExplicitRootResources());

        that.getLanguageMappings().putAll(this.getLanguageMappings());
        that.getMediaTypeMappings().putAll(this.getMediaTypeMappings());

        that.getFeatures().putAll(this.getFeatures());
        that.getProperties().putAll(this.getProperties());

        return that;
    }

    /**
     * Get a canonical array of String elements from a String array
     * where each entry may contain zero or more elements separated by ';'.
     *
     * @param elements an array where each String entry may contain zero or more
     *        ';' separated elements.
     * @return the array of elements, each element is trimmed, the array will
     *         not contain any empty or null entries.
     */
    public static String[] getElements(String[] elements) {
        // keeping backwards compatibility
        return getElements(elements, ";");
    }

    /**
     * Get a canonical array of String elements from a String array
     * where each entry may contain zero or more elements separated by characters
     * in delimiters string.
     *
     * @param elements an array where each String entry may contain zero or more
     *        delimiters separated elements.
     * @param delimiters string with delimiters, every character represents one
     *        delimiter.
     * @return the array of elements, each element is trimmed, the array will
     *         not contain any empty or null entries.
     */
    public static String[] getElements(String[] elements, String delimiters) {
        List<String> es = new LinkedList<String>();
        for (String element : elements) {
            if (element == null) continue;
            element = element.trim();
            if (element.length() == 0) continue;
            for (String subElement : getElements(element, delimiters)) {
                if (subElement == null || subElement.length() == 0) continue;
                es.add(subElement);
            }
        }
        return es.toArray(new String[es.size()]);
    }

    /**
     * Get a canonical array of String elements from a String
     * that may contain zero or more elements separated by characters in
     * delimiters string.
     *
     * @param elements a String that may contain zero or more
     *        delimiters separated elements.
     * @param delimiters string with delimiters, every character represents one
     *        delimiter.
     * @return the array of elements, each element is trimmed.
     */
    private static String[] getElements(String elements, String delimiters) {
        String regex = "[";
        for(char c : delimiters.toCharArray())
            regex += Pattern.quote(String.valueOf(c));
        regex += "]";

        String[] es = elements.split(regex);
        for (int i = 0; i < es.length; i++) {
            es[i] = es[i].trim();
        }
        return es;
    }
}