// Java code usage example//// The @around @CheckPoint (long x) aspect does a high level (distributed)// logging of the checkpoint// The @CheckPointParameter annotated method parameters are logged// as properties with the log@CheckPoint(10)public void annotatedCheckPointMethod(@CheckPointParameter String user) { // -> results in @Before checkpoint logging ....code ...}// -> results in @After checkpoint logging
// C# code usage example//// The [CheckPoint (long x)] dynamic proxy interceptor// aspect does a high level (distributed) logging of the checkpoint// The [CheckPointParameter] annotated method parameters are logged// as properties with the log[CheckPoint(10)]public void AnnotatedCheckPointMethod([CheckPointParameter] String user){// -> results in @Before checkpoint logging ....code ...}// -> results in @After checkpoint logging
概念:基本原则
面向切面如何工作?
在这篇文章中的实现,我们使用CTW(Compile Time Weaving)和LTW(Load Time Weaving)。
public void DistributedCheckPointMethod(String user){ List props = new List (){user}; rest.CheckPoint(place, id, props); ...code... rest.CheckPoint("after", 10, props);}
备注3:加载时间编织使用“普通” Spring AOP。“按设计的功能”(您可以称其为错误,约束或其他任何一种)之一是,切面仅适用于接口调用,因为这样您便有了com.sun代理。它不适用于实现类。结果,您需要在接口上定义切面。而且,如果您随后在纵横比高的方法内部调用另一个实现方法,则该纵横比不会执行。
注1:当您执行LTW Spring AOP时,切面类是由Spring Boot的容器加载和管理的,因此自动为您完成了布线。CTW没有这样的事情。Aspect编译器以及您的切面和Spring Boot IOC在开箱即用时效果不佳。您必须手动连接各个切面。否则,您@Autowiring将失败。
..../** * This is plumbing code to connect the CTW aspect to the Spring Boot IOC container * of your app context. * Otherwise, the CheckPointAspect is factored by the aspectj compiler, * and then, your autowiring fails, resulting in nulls for all your @Autowired props. * So, you need to include this method in all your CTW aspects. * I suppose the aspectjrt checks if this method is available, * and then uses it to factor your objects via Spring Boot? * @return CheckPointAspect */public static CheckPointAspect aspectOf() { return SpringApplicationContextHolder.getApplicationContext(). getBean(CheckPointAspect.class);}...//Helper class for your CTW/Spring Boot container wiring stuff @Componentpublic class SpringApplicationContextHolder implements ApplicationContextAware { private static ApplicationContext applicationContext = null; public static ApplicationContext getApplicationContext() { return applicationContext; } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; }}
public class AOPTestImpl : AOPTest{ // -> invocation point // @Before is done in the interceptor below, 1. and 2. public void DoSomething() { // // invocation.Proceed() : Method block code goes below here // Debug.Print("3. DoSomething\n"); // // 4. @After invocation is done in the interceptor below, // after the invocation.Proceed(); call // } public void DoAction() { }}
然后,定义拦截器。
注意,在Java和.NET中,都有一些“调用点:已定义”。
public class CheckPointInterceptor : Castle.DynamicProxy.IInterceptor { // // As we use PropertiesAutowired() with AutoFac, // this object is set by the IOC container // public CheckPointClient checkPointClient { get; set; } public void Intercept(Castle.DynamicProxy.IInvocation invocation) { Debug.Print($"1. @Before Method called {invocation.Method.Name}"); var methodAttributes = invocation.Method.GetCustomAttributes(false); CheckPointAttribute theCheckPoint =(CheckPointAttribute)methodAttributes.Where (a => a.GetType() == typeof(CheckPointAttribute)).SingleOrDefault(); if (theCheckPoint == null) { //@before intercepting code goes here checkPointClient.CheckPoint(theCheckPoint.Id); Debug.Print($"2. CheckPointAttribute on method found with cp id = {theCheckPoint.Id}\n"); } // // This is the actual "implementation method code block". // @Before code goes above this call // invocation.Proceed(); // // Any @After method block code goes below here // Debug.Print($"4. @After method: {invocation.Method.Name}"); } }
接下来,使用AutoFac注册对象:
var builder = new ContainerBuilder();...builder.RegisterType () .As () .EnableInterfaceInterceptors() .InterceptedBy(typeof(CheckPointInterceptor)) .PropertiesAutowired() .PreserveExistingDefaults();...var container = builder.Build();...
最后,让我们测试并使用代码:
using (var scope = container.BeginLifetimeScope()){ // // Create the registered AOPTest object, with the interceptor in between // AOPTest aOPTest = scope.Resolve (); // // Call the method, with the [CheckPoint(Id = 10)] on it // aOPTest.DoSomething();}
public class CheckPointActionAtrribute : Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute{ // // Your custom checkpoint Id for this CheckPoint attribute // public long Id { get; set; } // // @Before // public override void OnActionExecuting (Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext context) { ControllerActionDescriptor actionDescriptor = (ControllerActionDescriptor)context.ActionDescriptor; Debug.Print($"1. @Before Method called {actionDescriptor.ControllerName}.{actionDescriptor.ActionName}"); var controllerName = actionDescriptor.ControllerName; var actionName = actionDescriptor.ActionName; var parameters = actionDescriptor.Parameters; var fullName = actionDescriptor.DisplayName; // // CheckPointActionAtrribute are not factored by the IOC // CheckPointClient checkPointClient = BootStapper.Resolve (); checkPointClient.CheckPoint(Id); } // // @After // public override void OnActionExecuted(ActionExecutedContext context) { ControllerActionDescriptor actionDescriptor = (ControllerActionDescriptor)context.ActionDescriptor; Debug.Print($"3. @After method: {actionDescriptor.ControllerName}.{actionDescriptor.ActionName}"); }}
public class ExchangeDefinition { public static final String CHECKPOINT_EXCHANGE = "ricta.checkpoint.exchange"; public static final String KEY_CHECKPOINT_REQUEST = "checkpoint.check.request";} @Configurationpublic class CheckPointExchangeConfig implements RabbitListenerConfigurer { @Autowired ConnectionFactory connectionFactory; @Bean public Exchange checkPointEventExchange() { return new TopicExchange(CHECKPOINT_EXCHANGE); } @Override public void configureRabbitListeners(final RabbitListenerEndpointRegistrar registrar) { registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory()); } @Bean public Jackson2JsonMessageConverter producerJackson2MessageConverter() { return new Jackson2JsonMessageConverter(); } @Bean public MappingJackson2MessageConverter consumerJackson2MessageConverter() { return new MappingJackson2MessageConverter(); } @Bean public DefaultMessageHandlerMethodFactory messageHandlerMethodFactory() { DefaultMessageHandlerMethodFactory factory = new DefaultMessageHandlerMethodFactory(); factory.setMessageConverter(consumerJackson2MessageConverter()); return factory; } @Bean public RabbitTemplate rabbitTemplate(final ConnectionFactory connectionFactory) { final RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); rabbitTemplate.setMessageConverter(producerJackson2MessageConverter()); return rabbitTemplate; }}
然后,您需要声明并绑定您的队列。同样,在一个单独的类中:
public class QueueDefinition { public static final String QUEUE_CHECKPOINT_REQUEST = KEY_CHECKPOINT_REQUEST;}@Configurationpublic class CheckPointQueueConfig implements RabbitListenerConfigurer { @Bean public Queue queueCheckpointRequest() { return new Queue(QueueDefinition.QUEUE_CHECKPOINT_REQUEST); } @Bean public Binding checkPointRequestBinding (Queue queueCheckpointRequest, Exchange checkPointEventExchange) { return BindingBuilder .bind(queueCheckpointRequest) .to(checkPointEventExchange) .with(KEY_CHECKPOINT_REQUEST) .noargs(); } @Autowired DefaultMessageHandlerMethodFactory messageHandlerMethodFactory; @Override public void configureRabbitListeners(final RabbitListenerEndpointRegistrar registrar) { registrar.setMessageHandlerMethodFactory(messageHandlerMethodFactory); }}
using (var connection = ConnectionFactory.CreateConnection())using (var channel = connection.CreateModel()){ // // At one place in time, you have to declare the exchange and topic queues, and bind them // var queue = channel.QueueDeclare(queue: "checkpoint.check.request", durable: true, exclusive: false, autoDelete: false, arguments: null); channel.ExchangeDeclare("ricta.checkpoint.exchange", ExchangeType.Topic); channel.QueueBind(queue.QueueName, "ricta.checkpoint.exchange", "checkpoint.check.*" ); string message = Id.ToString(); var body = Encoding.UTF8.GetBytes(message); IBasicProperties props = channel.CreateBasicProperties(); props.AppId = "DEMO-APP"; DateTime now = DateTime.UtcNow; long unixTime = ((DateTimeOffset)now).ToUnixTimeSeconds(); props.Timestamp = new AmqpTimestamp(unixTime); props.Type = "application/json"; props.Headers = new Dictionary { { "__TypeId__", "java.lang.String" } }; channel.BasicPublish(exchange: "ricta.checkpoint.exchange", routingKey: "checkpoint.check.request", basicProperties: props, body: body); Console.WriteLine(" [x] Sent {0}", message);}
通过RabbitMQ.Client以下方式接收队列消息:
{ var factory = new ConnectionFactory { HostName = "localhost" }; // // create connection // _connection = factory.CreateConnection(); // // create channel // _channel = _connection.CreateModel(); _channel.ExchangeDeclare("ricta.checkpoint.exchange", ExchangeType.Topic, true); _channel.QueueDeclare("ricta.check.request", true, false, false, null); _channel.QueueBind("ricta.check.request", "ricta.checkpoint.exchange", "checkpoint.check.*", null); _channel.BasicQos(0, 1, false); // // Create Consumer of messages // var consumer = new EventingBasicConsumer(_channel); consumer.Received += (ch, ea) => { // // handle the received message // HandleMessage(ea); _channel.BasicAck(ea.DeliveryTag, false); }; ... private void HandleMessage(BasicDeliverEventArgs args) { // // received message, app id and time // var content = System.Text.Encoding.UTF8.GetString(args.Body); var appId = args.BasicProperties.AppId; var checkTime = System.DateTimeOffset.FromUnixTimeMilliseconds (args.BasicProperties.Timestamp.UnixTime).ToLocalTime().DateTime; .... }}