CDI(Weld)整合<2>便携式扩展(Portable extensions)

综述:便携式扩展(Portable extensions)

CDI的目的是成为一个基础框架、扩展和与集成其他技术。
因此,CDI暴露一组为开发人员使用的便携式CDI的扩展的spi。
例如,以下类型的扩展是由CDI的设计者设想:
  • 与业务流程管理引擎的集成,
  • 集成第三方框架如Spring,Seam,GWT或Wicket,
  • 在CDI编程模型基础上的新技术集成
根据规范:一个便携式的扩展可以通过集成容器来使用:
  • 提供自己的bean,拦截器和修饰符的容器
  • 使用依赖注入将依赖项注入自己的对象服务
  • 提供一个上下文实现一个自定义的范围
  • 增加或覆盖基于注解的元数据

1. Creating an Extension

创建一个便携扩展的第一步是编写一个类,实现Extension.这个标记接口不定义任何方法.
import javax.enterprise.inject.spi.Extension;

class MyExtension implements Extension 
{ ... }
接下来,我们需要注册扩展作为服务提供者通过创建一个文件META-INF/services/javax.enterprise.inject.spi.Extension,其中包含我们的扩展类的完整名称:
org.mydomain.extension.MyExtension
extension不是一个bean,确切地说,因为它是由容器在初始化过程中实例化,在任何bean或上下文之前就存在。然而,它一旦初始化过程完成,可以被注入其他bean。
@Inject 
MyBean(MyExtension myExtension) {

   myExtension.doSomething();
}
像Bean一样,extension可以有Observer方法。通常,观察者方法观察容器生命周期事件的方法。

2. Container lifecycle events

在初始化过程中,容器触发一系列的事件,包括:
  1. BeforeBeanDiscovery
  2. ProcessAnnotatedType and ProcessSyntheticAnnotatedType
  3. AfterTypeDiscovery
  4. ProcessInjectionTarget and ProcessProducer
  5. ProcessInjectionPoint
  6. ProcessBeanAttributes
  7. ProcessBean, ProcessManagedBean, ProcessSessionBean, ProcessProducerMethod and ProcessProducerField
  8. ProcessObserverMethod
  9. AfterBeanDiscovery
  10. AfterDeploymentValidation
Extensions可以观察这些事件
import javax.enterprise.inject.spi.Extension;

class MyExtension implements Extension {

   void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd) {
      Logger.global.debug("beginning the scanning process");
   }
      
   <T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> pat) {
      Logger.global.debug("scanning type: " + pat.getAnnotatedType().getJavaClass().getName());
   } 

   void afterBeanDiscovery(@Observes AfterBeanDiscovery abd) {
      Logger.global.debug("finished the scanning process");
   }
}
事实上,扩展可以做很多事情不仅仅是观察。扩展允许修改容器的元模型等等。这里有一个很简单的例子:
import javax.enterprise.inject.spi.Extension;

class MyExtension implements Extension {
     
   <T> void processAnnotatedType(@Observes @WithAnnotations({Ignore.class}) ProcessAnnotatedType<T> pat) {

      /* tell the container to ignore the type if it is annotated @Ignore */
      if ( pat.getAnnotatedType().isAnnotationPresent(Ignore.class) ) pat.veto();   
   } 
}
New in CDI 1.1
@WithAnnotations注解导致容器在传递ProcessAnnotatedType的event事件的时候只包含指定注解的类型.

Observer method 可以注入一个BeanManager,( Observer method 不允许注入其他对象)
<T> void processAnnotatedType(@Observes ProcessAnnotatedType<T> pat, BeanManager beanManager) { ... }

3. The BeanManager object

CDI有个重要的对象,那就是BeanManager Object.
BeanManager接口让我们以编程方式获取bean,拦截器,修饰符、observers 和上下文。
public interface BeanManager {

   public Object getReference(Bean<?> bean, Type beanType, CreationalContext<?> ctx);

   public Object getInjectableReference(InjectionPoint ij, CreationalContext<?> ctx);

   public <T> CreationalContext<T> createCreationalContext(Contextual<T> contextual);

   public Set<Bean<?>> getBeans(Type beanType, Annotation... qualifiers);

   public Set<Bean<?>> getBeans(String name);

   public Bean<?> getPassivationCapableBean(String id);

   public <X> Bean<? extends X> resolve(Set<Bean<? extends X>> beans);

   public void validate(InjectionPoint injectionPoint);

   public void fireEvent(Object event, Annotation... qualifiers);

   public <T> Set<ObserverMethod<? super T>> resolveObserverMethods(T event, Annotation... qualifiers);

   public List<Decorator<?>> resolveDecorators(Set<Type> types, Annotation... qualifiers);

   public List<Interceptor<?>> resolveInterceptors(InterceptionType type, Annotation... interceptorBindings);

   public boolean isScope(Class<? extends Annotation> annotationType);

   public boolean isNormalScope(Class<? extends Annotation> annotationType);

   public boolean isPassivatingScope(Class<? extends Annotation> annotationType);

   public boolean isQualifier(Class<? extends Annotation> annotationType);

   public boolean isInterceptorBinding(Class<? extends Annotation> annotationType);

   public boolean isStereotype(Class<? extends Annotation> annotationType);

   public Set<Annotation> getInterceptorBindingDefinition(Class<? extends Annotation> bindingType);

   public Set<Annotation> getStereotypeDefinition(Class<? extends Annotation> stereotype);

   public boolean areQualifiersEquivalent(Annotation qualifier1, Annotation qualifier2);

   public boolean areInterceptorBindingsEquivalent(Annotation interceptorBinding1, Annotation interceptorBinding2);

   public int getQualifierHashCode(Annotation qualifier);

   public int getInterceptorBindingHashCode(Annotation interceptorBinding);

   public Context getContext(Class<? extends Annotation> scopeType);

   public ELResolver getELResolver();

   public ExpressionFactory wrapExpressionFactory(ExpressionFactory expressionFactory);

   public <T> AnnotatedType<T> createAnnotatedType(Class<T> type);

   public <T> InjectionTarget<T> createInjectionTarget(AnnotatedType<T> type);

   public <T> InjectionTargetFactory<T> getInjectionTargetFactory(AnnotatedType<T> annotatedType);

   public <X> ProducerFactory<X> getProducerFactory(AnnotatedField<? super X> field, Bean<X> declaringBean);

   public <X> ProducerFactory<X> getProducerFactory(AnnotatedMethod<? super X> method, Bean<X> declaringBean);

   public <T> BeanAttributes<T> createBeanAttributes(AnnotatedType<T> type);

   public BeanAttributes<?> createBeanAttributes(AnnotatedMember<?> type);

   public <T> Bean<T> createBean(BeanAttributes<T> attributes, Class<T> beanClass,

   public <T, X> Bean<T> createBean(BeanAttributes<T> attributes, Class<X> beanClass, ProducerFactory<X> producerFactory);

   public InjectionPoint createInjectionPoint(AnnotatedField<?> field);

   public InjectionPoint createInjectionPoint(AnnotatedParameter<?> parameter);

   public <T extends Extension> T getExtension(Class<T> extensionClass);

}

任何bean或其他Java EE组件,可通过@Inject获得BeanManager的一个实例:

@Inject BeanManager beanManager;
另外,一个BeanManager参考可用CDI通过静态方法调用获得的。
CDI.current().getBeanManager()
Java EE组件可能获得的实例BeanManager从JNDI查找名称Java:comp / BeanManager。
让我们研究一些BeanManager公开的接口。

4. The CDI class

当应用程序组件不能通过@Inject和javax.enterprise.inject.spi JNDI查找获得BeanManager引用。CDI类可以通过静态方法调用来获取
BeanManager manager = CDI.current().getBeanManager();
那上面代码的CDI从哪里获得?可以直接使用CDI类以编程方式查找CDI bean
CDI.select(Foo.class).get()

5. The InjectionTarget interface

一个框架开发人员的第一件事就是去寻找在便携扩展SPI方式注入不在CDI的控制下的CDI bean对象。
InjectionTarget接口使得这个很容易。

请注意
我们建议框架让CDI接手的工作实际上是初始化framework-controlled对象。
这样,可以利用构造函数注入framework-controlled对象。
然而,如果构造函数的框架需要使用特殊的签名,该框架需要实例化对象本身,所以只有用方法和字段来Inject。
import javax.enterprise.inject.spi.CDI;

...

//获取一个BeanManager
BeanManager beanManager = CDI.current().getBeanManager();

//CDI采用AnnotatedType对象来读取一个类的注解
AnnotatedType<SomeFrameworkComponent> type = beanManager.createAnnotatedType(SomeFrameworkComponent.class);

//extensions使用的InjectionTarget在CDI容器中委托实例化,依赖注入 和生命周期回调
InjectionTarget<SomeFrameworkComponent> it = beanManager.createInjectionTarget(type);

//每个实例都需要它自己的CDI CreationalContext(CDI创造上下文)
CreationalContext ctx = beanManager.createCreationalContext(null);


//实例化的框架组件,并注入其依赖关系
SomeFrameworkComponent instance = it.produce(ctx);  //调用构造函数
it.inject(instance, ctx);  //调用初始化方法,并完成字段方式的注入
it.postConstruct(instance);  //call the @PostConstruct method
...

//摧毁的框架组件实例和清理依赖对象
it.preDestroy(instance);  //call the @PreDestroy method

it.dispose(instance);  //it is now safe to discard the instance

ctx.release();  //clean up dependent objects

6. The Bean interface

我们可以通过以下的方式获取BeanAttributes .
public interface BeanAttributes<T> {

   public Set<Type> getTypes();

   public Set<Annotation> getQualifiers();

   public Class<? extends Annotation> getScope();

   public String getName();

   public Set<Class<? extends Annotation>> getStereotypes();

   public boolean isAlternative();
}
Bean接口扩展了BeanAttributes接口并定义所有容器需要管理的Bean实例。
public interface Bean<T> extends Contextual<T>, BeanAttributes<T> {
public interface Bean<T> extends Contextual<T>, BeanAttributes<T> {

   public Class<?> getBeanClass();

   public Set<InjectionPoint> getInjectionPoints();

   public boolean isNullable();

}
有一个简单的方法在应用程序来找出bean:
Set<Bean<?>> allBeans = beanManager.getBeans(Obect.class, new AnnotationLiteral<Any>() {});
Bean接口用便携扩展为新的Bean提供支持,除了那些CDI规范定义的.例如我们可以使用Bean接口允许对象管理由另一个框架注入bean。

7. Registering a Bean

最常见的事情是在容器中CDI便携扩展注册bean。
在这个例子中,我们做一个框架类,让SecurityManager可供注入。
为了让事情更有趣,我们将委托回容器的InjectionTarget执行实例化和注入SecurityManager实例。
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.event.Observes;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import javax.enterprise.inject.spi.InjectionPoint;
...

public class SecurityManagerExtension implements Extension {
   
    void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
           
        //use this to read annotations of the class
        AnnotatedType<SecurityManager> at = bm.createAnnotatedType(SecurityManager.class); 

        //use this to instantiate the class and inject dependencies
        final InjectionTarget<SecurityManager> it = bm.createInjectionTarget(at); 

        abd.addBean( new Bean<SecurityManager>() {

            @Override
            public Class<?> getBeanClass() {

                return SecurityManager.class;
            }

            @Override
            public Set<InjectionPoint> getInjectionPoints() {

                return it.getInjectionPoints();
            }

            @Override
            public String getName() {

                return "securityManager";
            }

            @Override
            public Set<Annotation> getQualifiers() {

                Set<Annotation> qualifiers = new HashSet<Annotation>();
                qualifiers.add( new AnnotationLiteral<Default>() {} );
                qualifiers.add( new AnnotationLiteral<Any>() {} );
                return qualifiers;
            }

            @Override
            public Class<? extends Annotation> getScope() {

                return ApplicationScoped.class;
            }

            @Override
            public Set<Class<? extends Annotation>> getStereotypes() {

                return Collections.emptySet();
            }

            @Override
            public Set<Type> getTypes() {

                Set<Type> types = new HashSet<Type>();
                types.add(SecurityManager.class);
                types.add(Object.class);
                return types;
            }

            @Override
            public boolean isAlternative() {

                return false;
            }

            @Override
            public boolean isNullable() {

                return false;
            }

            @Override
            public SecurityManager create(CreationalContext<SecurityManager> ctx) {

                SecurityManager instance = it.produce(ctx);
                it.inject(instance, ctx);
                it.postConstruct(instance);
                return instance;
            }

            @Override
            public void destroy(SecurityManager instance,CreationalContext<SecurityManager> ctx) {

                it.preDestroy(instance);
                it.dispose(instance);
                ctx.release();
            }
        } );
    }
}

8. Wrapping an AnnotatedType

最有趣的事情,是一个扩展类可以在容器建立自己的元模型前处理一个bean类的注解。 
下面是2个例子,一个是关于@Named扩展的一个例子,它提供了支持使用@Named在包级别,包级别名称是在这个包中用来限定所有bean定义的EL名称。便携扩展使用ProcessAnnotatedType事件包装AnnotatedType对象和重写 @Named 注解的 value ()。
import java.lang.reflect.Type;
import javax.enterprise.inject.spi.Extension;
import java.lang.annotation.Annotation;
...

public class QualifiedNameExtension implements Extension {

    <X> void processAnnotatedType(@Observes ProcessAnnotatedType<X> pat) {

        /* wrap this to override the annotations of the class */
        final AnnotatedType<X> at = pat.getAnnotatedType();

        /* Only wrap AnnotatedTypes for classes with @Named packages */
        Package pkg = at.getJavaClass().getPackage();

        if ( !pkg.isAnnotationPresent(Named.class) ) {

            return;
        }

        AnnotatedType<X> wrapped = new AnnotatedType<X>() {

            class NamedLiteral extends AnnotationLiteral<Named>implements Named 
            {

                @Override
                public String value() {

                    Package pkg = at.getJavaClass().getPackage();

                    String unqualifiedName = "";

                    if (at.isAnnotationPresent(Named.class)) {

                        unqualifiedName = at.getAnnotation(Named.class).value();
                    }

                    if (unqualifiedName.isEmpty()) {

                        unqualifiedName = Introspector.decapitalize(at.getJavaClass().getSimpleName());
                    }

                    final String qualifiedName;

                    if ( pkg.isAnnotationPresent(Named.class) ) {

                        qualifiedName = pkg.getAnnotation(Named.class).value() + '.' + unqualifiedName;
                    }
                    else {
                        qualifiedName = unqualifiedName;
                    }

                    return qualifiedName;
                }
            }
           
            private final NamedLiteral namedLiteral = new NamedLiteral();

            @Override
            public Set<AnnotatedConstructor<X>> getConstructors() {

                return at.getConstructors();
            }

            @Override
            public Set<AnnotatedField<? super X>> getFields() {

                return at.getFields();
            }


            @Override
            public Class<X> getJavaClass() {

                return at.getJavaClass();
            }


            @Override
            public Set<AnnotatedMethod<? super X>> getMethods() {

                return at.getMethods();
            }


            @Override
            public <T extends Annotation> T getAnnotation(final Class<T> annType) {

                if (Named.class.equals(annType)) {

                    return (T) namedLiteral;
                }
                else {

                    return at.getAnnotation(annType);
                }

            }

            @Override
            public Set<Annotation> getAnnotations() {

                Set<Annotation> original = at.getAnnotations();

                Set<Annotation> annotations = new HashSet<Annotation>();

                boolean hasNamed = false;

                for (Annotation annotation : original) {

                    if (annotation.annotationType().equals(Named.class)) {

                        annotations.add(getAnnotation(Named.class));
                        hasNamed = true;
                    }
                    else {
                        annotations.add(annotation);
                    }
                }

                if (!hasNamed) {
                    Package pkg = at.getJavaClass().getPackage();
                    if (pkg.isAnnotationPresent(Named.class)) {
                        annotations.add(getAnnotation(Named.class));
                    }
                }
                return annotations;
            }

            @Override
            public Type getBaseType() {

                return at.getBaseType();
            }

            @Override
            public Set<Type> getTypeClosure() {

                return at.getTypeClosure();
            }

            @Override
            public boolean isAnnotationPresent(Class<? extends Annotation> annType) {

                if (Named.class.equals(annType)) {
                    return true;
                }
                return at.isAnnotationPresent(annType);
            }
        };
        pat.setAnnotatedType(wrapped);
    }
}

这是第二个例子,将@Alternative注释添加到任何类,实现一个特定的服务接口。

import javax.enterprise.inject.spi.Extension;
import java.lang.annotation.Annotation;
...

class ServiceAlternativeExtension implements Extension {
      
   <T extends Service> void processAnnotatedType(@Observes ProcessAnnotatedType<T> pat) {

      final AnnotatedType<T> type = pat.getAnnotatedType();

      /* if the class implements Service, make it an @Alternative */
      AnnotatedType<T> wrapped = new AnnotatedType<T>() {

         class AlternativeLiteral extends AnnotationLiteral<Alternative> implements Alternative {}
       
         private final AlternativeLiteral alternativeLiteral = new AlternativeLiteral();

         @Override
         public <X extends Annotation> X getAnnotation(final Class<X> annType) {

            return (X) (annType.equals(Alternative.class) ?  alternativeLiteral : type.getAnnotation(annType));
         }

         @Override
         public Set<Annotation> getAnnotations() {

            Set<Annotation> annotations = new HashSet<Annotation>(type.getAnnotations());
            annotations.add(alternativeLiteral);
            return annotations;
         }
         
         @Override
         public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {

            return annotationType.equals(Alternative.class) ? true : type.isAnnotationPresent(annotationType);
         }
            
         /* remaining methods of AnnotatedType */
         ...
      }

      pat.setAnnotatedType(wrapped);
   } 
}

AnnotatedType 不是唯一一个能被extension.包装的.


9. Overriding attributes of a bean by wrapping BeanAttributes

包装一个AnnotatedType用来对CDI元数据进行添加、删除或替换注解,是一种低级的方法,效率也不高.代码也写的多.CDI 1.1提供了更好的办法.
public interface BeanAttributes<T> {

   public Set<Type> getTypes();

   public Set<Annotation> getQualifiers();

   public Class<? extends Annotation> getScope();

   public String getName();

   public Set<Class<? extends Annotation>> getStereotypes();

   public boolean isAlternative();
}

BeanAttributes接口暴露bean的属性。对于每个已经启用的bean容器都会Fire ProcessBeanAttributes event,这个事件允许修改bean的属性或完全veto(否决)bean。

public interface ProcessBeanAttributes<T> {

    public Annotated getAnnotated();

    public BeanAttributes<T> getBeanAttributes();

    public void setBeanAttributes(BeanAttributes<T> beanAttributes);

    public void addDefinitionError(Throwable t);

    public void veto();
}

BeanManager提供了两创建BeanAttributes对象的方法:

public <T> BeanAttributes<T> createBeanAttributes(AnnotatedType<T> type);

public BeanAttributes<?> createBeanAttributes(AnnotatedMember<?> type);

10. Wrapping an InjectionTarget

InjectionTarget接口exposes了创建、销毁组件实例的操作,注入其依赖并调用其生命周期的方法。portable extension可以将支持injection任何Java EE组件的InjectionTarget封装起来,当container调用这些操作的时候,就可以进行干预。 
      这个CDI portable extension 从properties 文件中读取键值,然后对Java EE组件(servlets, EJBs, managed beans, interceptors 之类的)进行配置。在这个例子中,为class org.mydomain.blog.Blogger 准备的配置文件是资源目录下的 org/mydomain/blog/Blogger.properties, 并且property的名称必须和field的名称一致, 所以Blogger.properties的内容如下: 
firstName=Gavin

lastName=King

portable extension 的工作方式就是封装InjectionTarget,然后在 inject() 方法中对field进行赋值。 
import javax.enterprise.event.Observes;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.InjectionPoint;

public class ConfigExtension implements Extension {

    <X> void processInjectionTarget(@Observes ProcessInjectionTarget<X> pit) {        

          /* wrap this to intercept the component lifecycle */
        final InjectionTarget<X> it = pit.getInjectionTarget();        

        final Map<Field, Object> configuredValues = new HashMap<Field, Object>();        

        /* use this to read annotations of the class and its members */
        AnnotatedType<X> at = pit.getAnnotatedType();       

        /* read the properties file */
        String propsFileName = at.getJavaClass().getSimpleName() + ".properties";

        InputStream stream = at.getJavaClass().getResourceAsStream(propsFileName);

        if (stream!=null) {

            try {
                Properties props = new Properties();
                props.load(stream);

                for (Map.Entry<Object, Object> property : props.entrySet()) {

                    String fieldName = property.getKey().toString();
                    Object value = property.getValue();

                    try {
                        Field field = at.getJavaClass().getDeclaredField(fieldName);
                        field.setAccessible(true);
                        if ( field.getType().isAssignableFrom( value.getClass() ) ) {

                            configuredValues.put(field, value);
                        }
                        else {
                            /* TODO: do type conversion automatically */
                            pit.addDefinitionError( new InjectionException("field is not of type String: " + field ) );
                        }
                    }
                    catch (NoSuchFieldException nsfe) {

                        pit.addDefinitionError(nsfe);
                    }
                    finally {
                        stream.close();
                    }
                }
            }
            catch (IOException ioe) {
                pit.addDefinitionError(ioe);
            }
        }
        

        InjectionTarget<X> wrapped = new InjectionTarget<X>() {

            @Override
            public void inject(X instance, CreationalContext<X> ctx) {

                it.inject(instance, ctx);

                /* set the values onto the new instance of the component */
                for (Map.Entry<Field, Object> configuredValue: configuredValues.entrySet()) {

                    try {
                        configuredValue.getKey().set(instance, configuredValue.getValue());
                    }
                    catch (Exception e) {
                        throw new InjectionException(e);
                    }
                }
            }

            @Override
            public void postConstruct(X instance) {
                it.postConstruct(instance);
            }


            @Override
            public void preDestroy(X instance) {
                it.dispose(instance);
            }


            @Override
            public void dispose(X instance) {

                it.dispose(instance);
            }

            @Override
            public Set<InjectionPoint> getInjectionPoints() {

                return it.getInjectionPoints();
            }

            @Override
            public X produce(CreationalContext<X> ctx) {
                return it.produce(ctx);
            }
        };

        pit.setInjectionTarget(wrapped);
    }    
}

11. Overriding InjectionPoint

CDI provides a way to override the metadata of an InjectionPoint. This works similarly to how metadata of a bean may be overridden using BeanAttributes.
For every injection point of each component supporting injection Weld fires an event of type javax.enterprise.inject.spi.ProcessInjectionPoint
public interface ProcessInjectionPoint<T, X> {

    public InjectionPoint getInjectionPoint();

    public void setInjectionPoint(InjectionPoint injectionPoint);

    public void addDefinitionError(Throwable t);
}
An extension may either completely override the injection point metadata or alter it by wrapping the InjectionPoint object obtained from ProcessInjectionPoint.getInjectionPoint()
There's a lot more to the portable extension SPI than what we've discussed here. Check out the CDI spec or Javadoc for more information. For now, we'll just mention one more extension point.

12 Manipulating interceptors, decorators and alternatives enabled for an application

An event of type javax.enterprise.inject.spi.AfterTypeDiscovery is fired when the container has fully completed the type discovery process and before it begins the bean discovery process.
public interface AfterTypeDiscovery {

    public List<Class<?>> getAlternatives();

    public List<Class<?>> getInterceptors();

    public List<Class<?>> getDecorators();

    public void addAnnotatedType(AnnotatedType<?> type, String id);
}
This event exposes a list of enabled alternatives, interceptors and decorators. Extensions may manipulate these collections directly to add, remove or change the order of the enabled records.
In addition, an AnnotatedType can be added to the types which will be scanned during bean discovery, with an identifier, which allows multiple annotated types, based on the same underlying type, to be defined.

13. The Context and AlterableContext interfaces

上下文和AlterableContext接口支持添加新的CDI范围,或扩展内置的范围。
public interface Context {

   public Class<? extends Annotation> getScope();

   public <T> T get(Contextual<T> contextual, CreationalContext<T> creationalContext);

   public <T> T get(Contextual<T> contextual);

   boolean isActive();

}
例如,我们可能会实现上下文添加一个业务流程范围CDI,或将支持conversation作用域添加到一个应用程序,该应用程序使用Wicket。
import javax.enterprise.context.spi.Context;

public interface AlterableContext extends Context {

    public void destroy(Contextual<?> contextual);

}
AlterableContext CDI 1.1中引入的。销毁方法允许应用程序从一个上下文删除上下文对象的实例。
一般人也不会用这个功能,除非特殊的需求,
具体的创建请看 http://in.relation.to/Bloggers/CreatingACustomScope

Over.

转载于:https://my.oschina.net/zhaoqian/blog/266171

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值