配置例子
<!-- CometD Servlet --> <servlet> <servlet-name>cometd</servlet-name> <servlet-class>org.cometd.annotation.server.AnnotationCometDServlet</servlet-class> <!--liqiang todo 600000--> <init-param> <param-name>maxProcessing</param-name> <param-value>600000</param-value> </init-param> <init-param> <param-name>timeout</param-name> <param-value>20000</param-value> </init-param> <init-param> <param-name>interval</param-name> <param-value>0</param-value> </init-param> <init-param> <param-name>maxInterval</param-name> <param-value>10000</param-value> </init-param> <init-param> <param-name>handshakeReconnect</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>maxLazyTimeout</param-name> <param-value>5000</param-value> </init-param> <init-param> <param-name>long-polling.multiSessionInterval</param-name> <param-value>2000</param-value> </init-param> <init-param> <param-name>services</param-name> <param-value>org.cometd.examples.ChatService</param-value> </init-param> <init-param> <param-name>ws.cometdURLMapping</param-name> <param-value>/cometd/*</param-value> </init-param> <!--容器启动时调用init方法初始化 而不是第一次调用时--> <load-on-startup>1</load-on-startup> <async-supported>true</async-supported> </servlet>
基于注解的使用例子
@Service("chat") //comted注解 public class ChatService { //保存用户信息 不同房间 private final ConcurrentMap<String, Map<String, String>> _members = new ConcurrentHashMap<>(); @Inject //comted注解注入 private BayeuxServer _bayeux; @Session //comted注解注入 private ServerSession _session; //初始化 "/chat/**", "/members/**" 渠道 @Configure({"/chat/**", "/members/**"}) protected void configureChatStarStar(ConfigurableServerChannel channel) { DataFilterMessageListener noMarkup = new DataFilterMessageListener(new NoMarkupFilter(), new BadWordFilter()); channel.addListener(noMarkup); channel.addAuthorizer(GrantAuthorizer.GRANT_ALL); } //初始化 "/service/members" 渠道 @Configure("/service/members") protected void configureMembers(ConfigurableServerChannel channel) { channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH); channel.setPersistent(true); } /** * 渠道监听器 * @param client * @param message */ @Listener("/service/members") public void handleMembership(ServerSession client, ServerMessage message) { //获得消息 Map<String, Object> data = message.getDataAsMap(); //获得房间信息 String room = ((String)data.get("room")).substring("/chat/".length()); //获得房间的用户信息 Map<String, String> roomMembers = _members.get(room); if (roomMembers == null) { Map<String, String> new_room = new ConcurrentHashMap<>(); //加入房间 roomMembers = _members.putIfAbsent(room, new_room); if (roomMembers == null) { roomMembers = new_room; } } Map<String, String> members = roomMembers; //获得房间的用户信息 String userName = (String)data.get("user"); //用户信息与clientId映射 members.put(userName, client.getId()); //添加监听器 此客户端断开连接后 告诉当前房间所有人连接词用户下线 以及清除连接信息 client.addListener((ServerSession.RemovedListener)(s, m, t) -> { //清除此房间连接信息 members.values().remove(s.getId()); //通知所有用户 broadcastMembers(room, members.keySet()); }); //建立连接后推送订阅者最新的成员名单 broadcastMembers(room, members.keySet()); } private void broadcastMembers(String room, Set<String> members) { // Broadcast the new members list 推送订阅此房间的所有用户告知用户下线 ClientSessionChannel channel = _session.getLocalSession().getChannel("/members/" + room); channel.publish(members); } @Configure("/service/privatechat") protected void configurePrivateChat(ConfigurableServerChannel channel) { DataFilterMessageListener noMarkup = new DataFilterMessageListener(new NoMarkupFilter(), new BadWordFilter()); channel.setPersistent(true); channel.addListener(noMarkup); channel.addAuthorizer(GrantAuthorizer.GRANT_PUBLISH); } @Listener("/service/privatechat") public void privateChat(ServerSession client, ServerMessage message) { Map<String, Object> data = message.getDataAsMap(); String room = ((String)data.get("room")).substring("/chat/".length()); Map<String, String> membersMap = _members.get(room); if (membersMap == null) { Map<String, String> new_room = new ConcurrentHashMap<>(); membersMap = _members.putIfAbsent(room, new_room); if (membersMap == null) { membersMap = new_room; } } String[] peerNames = ((String)data.get("peer")).split(","); ArrayList<ServerSession> peers = new ArrayList<>(peerNames.length); for (String peerName : peerNames) { String peerId = membersMap.get(peerName); if (peerId != null) { ServerSession peer = _bayeux.getSession(peerId); if (peer != null) { peers.add(peer); } } } if (peers.size() > 0) { Map<String, Object> chat = new HashMap<>(); String text = (String)data.get("chat"); chat.put("chat", text); chat.put("user", data.get("user")); chat.put("scope", "private"); ServerMessage.Mutable forward = _bayeux.newMessage(); forward.setChannel("/chat/" + room); forward.setId(message.getId()); forward.setData(chat); // test for lazy messages if (text.lastIndexOf("lazy") > 0) { forward.setLazy(true); } for (ServerSession peer : peers) { if (peer != client) { peer.deliver(_session, forward, Promise.noop()); } } client.deliver(_session, forward, Promise.noop()); } } class BadWordFilter extends JSONDataFilter { @Override protected Object filterString(ServerSession session, ServerChannel channel, String string) { if (string.contains("dang")) { throw new DataFilter.AbortException(); } return string; } } }
源码
<1>
org.cometd.annotation.server.AnnotationCometDServlet#init
@Override public void init() throws ServletException { //<2>继承了comet 这里是先对comte进行初始化 super.init(); //基于注解方式初始化处理类对象的Processor new ServerAnnotationProcessor(bayeuxServer); processor = newServerAnnotationProcessor(getBayeux()); //获得servlet initParameter的Service配置 String servicesParam = getInitParameter("services"); if (servicesParam != null && servicesParam.length() > 0) { for (String serviceClass : servicesParam.split(",")) { //<3>创建处理类对象 也就是xml配置的chatServerce Object service = processService(processor, serviceClass.trim()); services.add(service); //设置当前Service到requestAttribute registerService(service); } } } protected ServerAnnotationProcessor newServerAnnotationProcessor(BayeuxServer bayeuxServer) { return new ServerAnnotationProcessor(bayeuxServer); }
<3>
org.cometd.annotation.server.AnnotationCometDServlet#processService
protected Object processService(ServerAnnotationProcessor processor, String serviceClassName) throws ServletException { try { //反射根据类的全名称创建类的对象 Object service = newService(serviceClassName); //<4>初始化 processor.process(service); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Processed annotated service {}", service); } return service; } catch (Exception x) { LOGGER.warn("Failed to create annotated service " + serviceClassName, x); throw new ServletException(x); } } protected Object newService(String serviceClassName) throws Exception { return Loader.loadClass(getClass(), serviceClassName).newInstance(); }
<4>
org.cometd.annotation.server.ServerAnnotationProcessor#process
public boolean process(Object bean) { //<5>基于注解依赖注入主要处理 @Inject 和@Session注解 boolean result = processDependencies(bean); //<11>根据@Configure注解的方法初始化channel result |= processConfigurations(bean); //<13>处理Subscription 订阅渠道 result |= processCallbacks(bean); //<15>调用@PostConstruct 进行初始化操作 result |= processPostConstruct(bean); return result; }
<5>
org.cometd.annotation.server.ServerAnnotationProcessor#processDependencies
public boolean processDependencies(Object bean) { if (bean == null) { return false; } Class<?> klass = bean.getClass(); //首选需要打了comted的@Service注解 Service serviceAnnotation = klass.getAnnotation(Service.class); if (serviceAnnotation == null) { return false; } //容器中bean对象 List<Object> injectables = new ArrayList<>(Arrays.asList(this.injectables)); //默认增加是bayeuxServer 所以支持@Inject进行 bayeuxServer注入 injectables.add(0, bayeuxServer); //<6>针对 bean的@Inject注解进行 注入 boolean result = processInjectables(bean, injectables); //<7>针对Service初始化loaclSession 同时初始化一个serverSession与bayeuxServer模拟建立连接 LocalSession session = findOrCreateLocalSession(bean, serviceAnnotation.value()); //<10>session注入到bean 后续我们可以通过操作session发送消息 result |= processSession(bean, session); return result; }
<6>
org.cometd.annotation.AnnotationProcessor#processInjectables
protected boolean processInjectables(Object bean, List<Object> injectables) { boolean result = false; //遍历容器 for (Object injectable : injectables) { result |= processInjectable(bean, injectable); } return result; } protected boolean processInjectable(Object bean, Object injectable) { boolean result = false; for (Class<?> c = bean.getClass(); c != Object.class; c = c.getSuperclass()) { //反射获得fields 获得某个类的所有声明的字段,即包括public、private和proteced,但是不包括父类的申明字段。 Field[] fields = c.getDeclaredFields(); for (Field field : fields) { //如果打了@Inject注解 if (field.getAnnotation(Inject.class) != null) { /** * 如果是A.isAssignableFrom(B) 确定一个类(B)是不是继承来自于另一个父类(A),一个接口(A)是不是实现了另外一个接口(B), * 或者两个类相同。主要,这里比较的维度不是实例对象,而是类本身,因为这个方法本身就是Class类的方法,判断的肯定是和类信息相关的。 */ if (field.getType().isAssignableFrom(injectable.getClass())) { Object value = getField(bean, field); if (value != null) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Avoid injection of field {} on bean {}, it's already injected with {}", field, bean, value); } continue; } //完成注入 setField(bean, field, injectable); result = true; if (LOGGER.isDebugEnabled()) { LOGGER.debug("Injected {} to field {} on bean {}", injectable, field, bean); } } } } } //针对打在set方法上 的注入 List<Method> methods = findAnnotatedMethods(bean, Inject.class); for (Method method : methods) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { if (parameterTypes[0].isAssignableFrom(injectable.getClass())) { invokePrivate(bean, method, injectable); result = true; if (LOGGER.isDebugEnabled()) { LOGGER.debug("Injected {} to method {} on bean {}", injectable, method, bean); } } } } return result; }
<7>
org.cometd.annotation.server.ServerAnnotationProcessor#findOrCreateLocalSession
private LocalSession findOrCreateLocalSession(Object bean, String name) { LocalSession session = sessions.get(bean); if (session == null) { //<8>创建一个Local session session = bayeuxServer.newLocalSession(name); //bean的名字作为key维护放入sessions LocalSession existing = sessions.putIfAbsent(bean, session); if (existing != null) { session = existing; } else { //<9>默认握手加入bayeuxServer session.handshake(); } } return session; }
<8>
org.cometd.server.BayeuxServerImpl#newLocalSession
@Override public LocalSession newLocalSession(String idHint) { return new LocalSessionImpl(this, idHint); }
<9>
org.cometd.server.LocalSessionImpl#handshake
@Override public void handshake(Map<String, Object> template, ClientSession.MessageListener callback) { if (_session != null) { throw new IllegalStateException("Method handshake() invoke multiple times for local session " + this); } ServerSessionImpl session = new ServerSessionImpl(_bayeux, this, _idHint); ServerMessage.Mutable hsMessage = newMessage(); if (template != null) { hsMessage.putAll(template); } String messageId = newMessageId(); hsMessage.setId(messageId); hsMessage.setChannel(Channel.META_HANDSHAKE); registerCallback(messageId, callback); doSend(session, hsMessage, Promise.from(hsReply -> { if (hsReply != null && hsReply.isSuccessful()) { _session = session; ServerMessage.Mutable cnMessage = newMessage(); cnMessage.setId(newMessageId()); cnMessage.setChannel(Channel.META_CONNECT); cnMessage.getAdvice(true).put(Message.INTERVAL_FIELD, -1L); cnMessage.setClientId(session.getId()); doSend(session, cnMessage, Promise.from(cnReply -> { // Nothing more to do. }, failure -> messageFailure(cnMessage, failure))); } }, failure -> messageFailure(hsMessage, failure))); }
<10>
org.cometd.annotation.server.ServerAnnotationProcessor#processSession
private boolean processSession(Object bean, LocalSession localSession) { ServerSession serverSession = localSession.getServerSession(); boolean result = false; for (Class<?> c = bean.getClass(); c != Object.class; c = c.getSuperclass()) { Field[] fields = c.getDeclaredFields(); for (Field field : fields) { if (field.getAnnotation(Session.class) != null) { Object value = null; if (field.getType().isAssignableFrom(localSession.getClass())) { value = localSession; } else if (field.getType().isAssignableFrom(serverSession.getClass())) { value = serverSession; } if (value != null) { setField(bean, field, value); result = true; if (LOGGER.isDebugEnabled()) { LOGGER.debug("Injected {} to field {} on bean {}", value, field, bean); } } } } } List<Method> methods = findAnnotatedMethods(bean, Session.class); for (Method method : methods) { Class<?>[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { Object value = null; if (parameterTypes[0].isAssignableFrom(localSession.getClass())) { value = localSession; } else if (parameterTypes[0].isAssignableFrom(serverSession.getClass())) { value = serverSession; } if (value != null) { invokePrivate(bean, method, value); result = true; if (LOGGER.isDebugEnabled()) { LOGGER.debug("Injected {} to method {} on bean {}", value, method, bean); } } } } return result; }
<11>
org.cometd.annotation.server.ServerAnnotationProcessor#processConfigurations
public boolean processConfigurations(Object bean) { if (bean == null) { return false; } Class<?> klass = bean.getClass(); //首选需要打了@Service注解 Service serviceAnnotation = klass.getAnnotation(Service.class); if (serviceAnnotation == null) { return false; } //首先需要打了打了@Configure的方法 List<Method> methods = findAnnotatedMethods(bean, Configure.class); if (methods.isEmpty()) { return false; } for (Method method : methods) { Configure configure = method.getAnnotation(Configure.class); //@Configure配置的value为channelName String[] channels = configure.value(); for (String channelName : channels) { //定义一个Initializer接口的匿名方法 内部会调用调用@Configure注解方法 Initializer init = channel -> { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Configure channel {} with method {} on bean {}", channel, method, bean); } invokePrivate(bean, method, channel); }; //<12>针对@Configure创建Channel MarkedReference<ServerChannel> initializedChannel = bayeuxServer.createChannelIfAbsent(channelName, init); //内部初始化成功会将marked设置为true if (!initializedChannel.isMarked()) { //是否配置了configureIfExists 为true 默认false 如果为true则会调用打了@Configure的方法 if (configure.configureIfExists()) { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Configure again channel {} with method {} on bean {}", channelName, method, bean); } init.configureChannel(initializedChannel.getReference()); } else if (configure.errorIfExists()) { throw new IllegalStateException("Channel already configured: " + channelName); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Channel {} already initialized. Not called method {} on bean {}", channelName, method, bean); } } } } } return true; }
<12>
org.cometd.server.BayeuxServerImpl#createChannelIfAbsent
@Override public MarkedReference<ServerChannel> createChannelIfAbsent(String channelName, Initializer... initializers) { ChannelId channelId; boolean initialized = false; //尝试根据channelName获取 判断是否存在 ServerChannelImpl channel = _channels.get(channelName); if (channel == null) { // Creating the ChannelId will also normalize the channelName. //尝试通过处理过的channelId获取 channelId = new ChannelId(channelName); String id = channelId.getId(); if (!id.equals(channelName)) { channelName = id; channel = _channels.get(channelName); } } else { channelId = channel.getChannelId(); } //表示没有被初始化 if (channel == null) { //新建一个channel ServerChannelImpl candidate = new ServerChannelImpl(this, channelId); //放入_channels channel = _channels.putIfAbsent(channelName, candidate); if (channel == null) { // My candidate channel was added to the map, so I'd better initialize it channel = candidate; if (_logger.isDebugEnabled()) { _logger.debug("Added channel {}", channel); } try { //通知 Initializer实现 可以对ServerChannelImpl做自定义配置 for (Initializer initializer : initializers) { notifyConfigureChannel(initializer, channel); } //调用listeners中ChannelListener的configureChannel方法可以对channel进行自定义配置 for (BayeuxServer.BayeuxServerListener listener : _listeners) { if (listener instanceof ServerChannel.Initializer) { notifyConfigureChannel((Initializer)listener, channel); } } } finally { channel.initialized(); } //调用listeners中ChannelListener的channelAdded表示已经被初始化 for (BayeuxServer.BayeuxServerListener listener : _listeners) { if (listener instanceof BayeuxServer.ChannelListener) { notifyChannelAdded((ChannelListener)listener, channel); } } initialized = true; } } else { channel.resetSweeperPasses(); // Double check if the sweeper removed this channel between the check at the top and here. // This is not 100% fool proof (e.g. this thread is preempted long enough for the sweeper // to remove the channel, but the alternative is to have a global lock) _channels.putIfAbsent(channelName, channel); } // Another thread may add this channel concurrently, so wait until it is initialized channel.waitForInitialized(); return new MarkedReference<>(channel, initialized); }
<13>
org.cometd.annotation.server.ServerAnnotationProcessor#processCallbacks
public boolean processCallbacks(Object bean) { if (bean == null) { return false; } Class<?> klass = bean.getClass(); Service serviceAnnotation = klass.getAnnotation(Service.class); if (serviceAnnotation == null) { return false; } if (!Modifier.isPublic(klass.getModifiers())) { throw new IllegalArgumentException("Service class " + klass.getName() + " must be public"); } //<7>根据打了Service的创建Session 如果创建过了就不会再创建 LocalSession session = findOrCreateLocalSession(bean, serviceAnnotation.value()); //<14>处理@@Listener注解往渠道新增监听器 监听渠道 boolean result = processListener(bean, session); //处理Subscription 往渠道新增订阅器 订阅渠道 result |= processSubscription(bean, session); //liqiangtodo @RemoteCall 好像也是监听的一种信息 处理类似RPC请求参考 result |= processRemoteCall(bean, session); return result; }
<14>
org.cometd.annotation.server.ServerAnnotationProcessor#processListener
private static final Class<?>[] signature = new Class<?>[]{ServerSession.class, ServerMessage.Mutable.class}; private boolean processListener(Object bean, LocalSession localSession) { //这里主要是做个检查针对打了@Listener的方法不是public的则报错 AnnotationProcessor.checkMethodsPublic(bean, Listener.class); boolean result = false; Method[] methods = bean.getClass().getMethods(); for (Method method : methods) { if (method.getDeclaringClass() == Object.class) { continue; } Listener listener = method.getAnnotation(Listener.class); if (listener != null) { //获得@Listener 打了@Param的方法的value List<String> paramNames = processParameters(method); //检查方法的注入 必须注入signature的类型,以及@Param注解的变量必须是String AnnotationProcessor.checkSignaturesMatch(method, ListenerCallback.signature, paramNames); //@Listener监听的渠道名称 String[] channels = listener.value(); for (String channel : channels) { ChannelId channelId = new ChannelId(channel); if (channelId.isTemplate()) { List<String> parameters = channelId.getParameters(); if (parameters.size() != paramNames.size()) { throw new IllegalArgumentException("Wrong number of template parameters in annotation @" + Listener.class.getSimpleName() + " on method " + method.getDeclaringClass().getName() + "." + method.getName() + "(...)"); } if (!parameters.equals(paramNames)) { throw new IllegalArgumentException("Wrong parameter names in annotation @" + Listener.class.getSimpleName() + " on method " + method.getDeclaringClass().getName() + "." + method.getName() + "(...)"); } channel = channelId.getRegularPart() + "/" + (parameters.size() < 2 ? ChannelId.WILD : ChannelId.DEEPWILD); } //渠道不存在的创建渠道 MarkedReference<ServerChannel> initializedChannel = bayeuxServer.createChannelIfAbsent(channel); ListenerCallback listenerCallback = new ListenerCallback(localSession, bean, method, paramNames, channelId, channel, listener.receiveOwnPublishes()); //在渠道里面新增此监听器获取消息回调 initializedChannel.getReference().addListener(listenerCallback); List<ListenerCallback> callbacks = listeners.get(bean); if (callbacks == null) { callbacks = new CopyOnWriteArrayList<>(); List<ListenerCallback> existing = listeners.putIfAbsent(bean, callbacks); if (existing != null) { callbacks = existing; } } callbacks.add(listenerCallback); result = true; if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered listener for channel {} to method {} on bean {}", channel, method, bean); } } } } return result; }
<15>
org.cometd.annotation.AnnotationProcessor#processPostConstruct
protected boolean processPostConstruct(Object bean) { if (bean == null) { return false; } List<Method> postConstructs = findLifeCycleMethods(bean, PostConstruct.class); boolean result = false; for (Method method : postConstructs) { invokePrivate(bean, method); result = true; } return result; }