我很想知道针对Jersey注释的注释处理器,例如@Path
,@Consumes
等。我的意思是,这会由Jersey API
还是javax.ws.rs-api-xxx.jar
提供?
我似乎无法弄清楚这些REST注释是如何处理的,我可以通过探索代码来找到它。
单击@Path
将我的定义带给我,但我需要知道处理此注释的逻辑。
我正在使用Eclipse,并且未启用项目特定的注释。
在tomcat中部署时,我的其余资源可以正常工作,这意味着某些处理器会处理注释。
答案 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使用它来处理请求。
现在上面都是伪代码。反射代码是真实的,但Resource
和ResourceMethod
的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<? extends ContainerResponseFilter;> 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<? extends ResourceFilterFactory;> 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;
}
}