在ASP.NET Core 3.0中路由配置和2.0不一样了 一、MVC 服务注册 ASP.NET Core 3.0 添加了用于注册内部的 MVC 方案的新选项Startup.ConfigureServices。 三个新的顶级扩展方法与 MVC 方案上IServiceCollection可用。 模板使用这些新方法,而不是UseMvc。 但是,AddMvc继续像它已在以前的版本。 下面的示例将添加对控制器和与 API 相关的功能,但不是视图或页面的支持。 API 模板使用此代码:
1 2 3 4 |
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); } |
下面的示例将添加对控制器、 与 API 相关的功能,和视图,但不是页面的支持。 Web 应用程序 (MVC) 模板使用此代码:
1 2 3 4 |
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); } |
下面的示例添加支持 Razor 页面和最小控制器支持。 Web 应用程序模板使用此代码:
1 2 3 4 |
public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); } |
此外可以组合的新方法。 下面的示例是等效于调用AddMvcASP.NET Core 2.2 中:
1 2 3 4 5 |
public void ConfigureServices(IServiceCollection services) { services.AddControllers(); services.AddRazorPages(); } |
二、Startup.Configure配置 一般不建议: 添加UseRouting。 如果该应用程序调用UseStaticFiles,将置于UseStaticFiles之前 UseRouting。 如果应用使用身份验证/授权功能,如AuthorizePage或[Authorize],将对UseAuthentication并UseAuthorization后 UseRouting。 如果应用使用CORS功能,如[EnableCors],将放置UseCors下一步。 替换UseMvc或UseSignalR与UseEndpoints。 以下是一种Startup.Configure典型的 ASP.NET Core 2.2 应用中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public void Configure(IApplicationBuilder app) { ... app.UseStaticFiles(); app.UseAuthentication(); app.UseSignalR(hubs => { hubs.MapHub<ChatHub>("/chat"); }); app.UseMvc(routes => { routes.MapRoute("default", "{controller=Home}/{action=Index}/{id?}"); }); } |
现在的控制器映射内发生UseEndpoints。 添加MapControllers如果应用使用属性路由。 由于路由包括对许多框架在 ASP.NET Core 3.0 或更高版本的支持,添加属性路由的控制器是参加。 将为以下内容: MapRoute 使用 MapControllerRoute MapAreaRoute 使用 MapAreaControllerRoute 由于路由现在包括对不止是 MVC 的支持,已更改了术语进行明确说明他们所做的这些方法。 如传统路由MapControllerRoute / MapAreaControllerRoute / MapDefaultControllerRoute它们要添加的顺序应用。 将第一位更具体的路由 (如某一区域的路由)。 如下示例中: […]
View Details在电商、支付等领域,往往会有这样的场景,用户下单后放弃支付了,那这笔订单会在指定的时间段后进行关闭操作,细心的你一定发现了像某宝、某东都有这样的逻辑,而且时间很准确,误差在1s内;那他们是怎么实现的呢? 一般的做法有如下几种 定时任务关闭订单 rocketmq延迟队列 rabbitmq死信队列 时间轮算法 redis过期监听 一、定时任务关闭订单(最low) 一般情况下,最不推荐的方式就是关单方式就是定时任务方式,原因我们可以看下面的图来说明 我们假设,关单时间为下单后10分钟,定时任务间隔也是10分钟;通过上图我们看出,如果在第1分钟下单,在第20分钟的时候才能被扫描到执行关单操作,这样误差达到10分钟,这在很多场景下是不可接受的,另外需要频繁扫描主订单号造成网络IO和磁盘IO的消耗,对实时交易造成一定的冲击,所以PASS 二、rocketmq延迟队列方式 延迟消息 生产者把消息发送到消息服务器后,并不希望被立即消费,而是等待指定时间后才可以被消费者消费,这类消息通常被称为延迟消息。 在RocketMQ开源版本中,支持延迟消息,但是不支持任意时间精度的延迟消息,只支持特定级别的延迟消息。 消息延迟级别分别为1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h,共18个级别。 发送延迟消息(生产者)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
/** * 推送延迟消息 * @param topic * @param body * @param producerGroup * @return boolean */ public boolean sendMessage(String topic, String body, String producerGroup) { try { Message recordMsg = new Message(topic, body.getBytes()); producer.setProducerGroup(producerGroup); //设置消息延迟级别,我这里设置14,对应就是延时10分钟 // "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h" recordMsg.setDelayTimeLevel(14); // 发送消息到一个Broker SendResult sendResult = producer.send(recordMsg); // 通过sendResult返回消息是否成功送达 log.info("发送延迟消息结果:======sendResult:{}", sendResult); DateFormat format =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); log.info("发送时间:{}", format.format(new Date())); return true; } catch (Exception e) { e.printStackTrace(); log.error("延迟消息队列推送消息异常:{},推送内容:{}", e.getMessage(), body); } return false; } |
消费延迟消息(消费者)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * 接收延迟消息 * * @param topic * @param consumerGroup * @param messageHandler */ public void messageListener(String topic, String consumerGroup, MessageListenerConcurrently messageHandler) { ThreadPoolUtil.execute(() -> { try { DefaultMQPushConsumer consumer = new DefaultMQPushConsumer(); consumer.setConsumerGroup(consumerGroup); consumer.setVipChannelEnabled(false); consumer.setNamesrvAddr(address); //设置消费者拉取消息的策略,*表示消费该topic下的所有消息,也可以指定tag进行消息过滤 consumer.subscribe(topic, "*"); //消费者端启动消息监听,一旦生产者发送消息被监听到,就打印消息,和rabbitmq中的handlerDelivery类似 consumer.registerMessageListener(messageHandler); consumer.start(); log.info("启动延迟消息队列监听成功:" + topic); } catch (MQClientException e) { log.error("启动延迟消息队列监听失败:{}", e.getErrorMessage()); System.exit(1); } }); } |
实现监听类,处理具体逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
/** * 延迟消息监听 * */ @Component public class CourseOrderTimeoutListener implements ApplicationListener<ApplicationReadyEvent> { @Resource private MQUtil mqUtil; @Resource private CourseOrderTimeoutHandler courseOrderTimeoutHandler; @Override public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { // 订单超时监听 mqUtil.messageListener(EnumTopic.ORDER_TIMEOUT, EnumGroup.ORDER_TIMEOUT_GROUP, courseOrderTimeoutHandler); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
/** * 实现监听 */ @Slf4j @Component public class CourseOrderTimeoutHandler implements MessageListenerConcurrently { @Override public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list, ConsumeConcurrentlyContext consumeConcurrentlyContext) { for (MessageExt msg : list) { // 得到消息体 String body = new String(msg.getBody()); JSONObject userJson = JSONObject.parseObject(body); TCourseBuy courseBuyDetails = JSON.toJavaObject(userJson, TCourseBuy.class); // 处理具体的业务逻辑,,,,, DateFormat format =new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); log.info("消费时间:{}", format.format(new Date())); return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; } } |
这种方式相比定时任务好了很多,但是有一个致命的缺点,就是延迟等级只有18种(商业版本支持自定义时间),如果我们想把关闭订单时间设置在15分钟该如何处理呢?显然不够灵活。 三、rabbitmq死信队列的方式 Rabbitmq本身是没有延迟队列的,只能通过Rabbitmq本身队列的特性来实现,想要Rabbitmq实现延迟队列,需要使用Rabbitmq的死信交换机(Exchange)和消息的存活时间TTL(Time To Live) 死信交换机 一个消息在满足如下条件下,会进死信交换机,记住这里是交换机而不是队列,一个交换机可以对应很多队列。 一个消息被Consumer拒收了,并且reject方法的参数里requeue是false。也就是说不会被再次放在队列里,被其他消费者使用。 上面的消息的TTL到了,消息过期了。 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上。 死信交换机就是普通的交换机,只是因为我们把过期的消息扔进去,所以叫死信交换机,并不是说死信交换机是某种特定的交换机 消息TTL(消息存活时间) 消息的TTL就是消息的存活时间。RabbitMQ可以对队列和消息分别设置TTL。对队列设置就是队列没有消费者连着的保留时间,也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信。如果队列设置了,消息也设置了,那么会取值较小的。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的TTL,因为它才是实现延迟任务的关键。
1 2 3 4 |
byte[] messageBodyBytes = "Hello, world!".getBytes(); AMQP.BasicProperties properties = new AMQP.BasicProperties(); properties.setExpiration("60000"); channel.basicPublish("my-exchange", "queue-key", properties, messageBodyBytes); |
可以通过设置消息的expiration字段或者x-message-ttl属性来设置时间,两者是一样的效果。只是expiration字段是字符串参数,所以要写个int类型的字符串:当上面的消息扔到队列中后,过了60秒,如果没有被消费,它就死了。不会被消费者消费到。这个消息后面的,没有“死掉”的消息对顶上来,被消费者消费。死信在队列中并不会被删除和释放,它会被统计到队列的消息数中去 处理流程图 创建交换机(Exchanges)和队列(Queues) 创建死信交换机 如图所示,就是创建一个普通的交换机,这里为了方便区分,把交换机的名字取为:delay 创建自动过期消息队列 这个队列的主要作用是让消息定时过期的,比如我们需要2小时候关闭订单,我们就需要把消息放进这个队列里面,把消息过期时间设置为2小时 创建一个一个名为delay_queue1的自动过期的队列,当然图片上面的参数并不会让消息自动过期,因为我们并没有设置x-message-ttl参数,如果整个队列的消息有消息都是相同的,可以设置,这里为了灵活,所以并没有设置,另外两个参数x-dead-letter-exchange代表消息过期后,消息要进入的交换机,这里配置的是delay,也就是死信交换机,x-dead-letter-routing-key是配置消息过期后,进入死信交换机的routing-key,跟发送消息的routing-key一个道理,根据这个key将消息放入不同的队列 创建消息处理队列 这个队列才是真正处理消息的队列,所有进入这个队列的消息都会被处理 消息队列的名字为delay_queue2 消息队列绑定到交换机 进入交换机详情页面,将创建的2个队列(delayqueue1和delayqueue2)绑定到交换机上面 自动过期消息队列的routing key 设置为delay 绑定delayqueue2 delayqueue2 的key要设置为创建自动过期的队列的x-dead-letter-routing-key参数,这样当消息过期的时候就可以自动把消息放入delay_queue2这个队列中了 绑定后的管理页面如下图: 当然这个绑定也可以使用代码来实现,只是为了直观表现,所以本文使用的管理平台来操作 发送消息
1 2 3 4 5 6 |
String msg = "hello word"; MessageProperties messageProperties = newMessageProperties(); messageProperties.setExpiration("6000"); messageProperties.setCorrelationId(UUID.randomUUID().toString().getBytes()); Message message = newMessage(msg.getBytes(), messageProperties); rabbitTemplate.convertAndSend("delay", "delay",message); |
设置了让消息6秒后过期 注意:因为要让消息自动过期,所以一定不能设置delay_queue1的监听,不能让这个队列里面的消息被接受到,否则消息一旦被消费,就不存在过期了 接收消息 接收消息配置好delay_queue2的监听就好了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
package wang.raye.rabbitmq.demo1; import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.Queue; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration publicclassDelayQueue{ /** 消息交换机的名字*/ publicstaticfinalString EXCHANGE = "delay"; /** 队列key1*/ publicstaticfinalString ROUTINGKEY1 = "delay"; /** 队列key2*/ publicstaticfinalString ROUTINGKEY2 = "delay_key"; /** * 配置链接信息 * @return */ @Bean publicConnectionFactory connectionFactory() { CachingConnectionFactory connectionFactory = newCachingConnectionFactory("120.76.237.8",5672); connectionFactory.setUsername("kberp"); connectionFactory.setPassword("kberp"); connectionFactory.setVirtualHost("/"); connectionFactory.setPublisherConfirms(true); // 必须要设置 return connectionFactory; } /** * 配置消息交换机 * 针对消费者配置 FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念 HeadersExchange :通过添加属性key-value匹配 DirectExchange:按照routingkey分发到指定队列 TopicExchange:多关键字匹配 */ @Bean publicDirectExchange defaultExchange() { returnnewDirectExchange(EXCHANGE, true, false); } /** * 配置消息队列2 * 针对消费者配置 * @return */ @Bean publicQueue queue() { returnnewQueue("delay_queue2", true); //队列持久 } /** * 将消息队列2与交换机绑定 * 针对消费者配置 * @return */ @Bean @Autowired publicBinding binding() { returnBindingBuilder.bind(queue()).to(defaultExchange()).with(DelayQueue.ROUTINGKEY2); } /** * 接受消息的监听,这个监听会接受消息队列1的消息 * 针对消费者配置 * @return */ @Bean @Autowired publicSimpleMessageListenerContainer messageContainer2(ConnectionFactory connectionFactory) { SimpleMessageListenerContainer container = newSimpleMessageListenerContainer(connectionFactory()); container.setQueues(queue()); container.setExposeListenerChannel(true); container.setMaxConcurrentConsumers(1); container.setConcurrentConsumers(1); container.setAcknowledgeMode(AcknowledgeMode.MANUAL); //设置确认模式手工确认 container.setMessageListener(newChannelAwareMessageListener() { publicvoid onMessage(Message message, com.rabbitmq.client.Channel channel) throwsException{ byte[] body = message.getBody(); System.out.println("delay_queue2 收到消息 : "+ newString(body)); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //确认消息成功消费 } }); return container; } } |
[…]
View Details这一节虽然简单,但是很繁琐,可以先了解 @RequestMapping 的使用,不是很明白也没有关系,先继续往下学习,等你回头再看一遍的时候,你会发现 @RequestMapping 竟然是如此的简单! @RequestMapping可以在控制器类上或者控制器方法上使用。 在类的级别上的注解会将一个特定请求或者请求模式映射到一个控制器之上。之后你还可以另外添加方法级别的注解来进一步指定到处理方法的映射关系。 基础用法: 下面的 @RequestMapping("/index") 等同于 @RequestMapping(value = "/index")
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
package com.pudding.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller @RequestMapping("/index") public class HelloWorldController { @RequestMapping(value = "/hello", method = RequestMethod.GET) public String hello() { return "/WEB-INF/views/success.jsp"; } @RequestMapping(value = "/world", method = RequestMethod.POST) public String world() { return "/WEB-INF/views/success.jsp"; } } |
method 参数支持:GET, PUT, POST, DELETE 以及 PATCH。使用 method 可以限制接受的请求类型。 hello() 方法将只接受请求方式为 GET 方式,请求地址为:/index/hello 的请求。 world() 方法将只接受请求方式为 POST 方式,请求地址为:/index/world 的请求。 @GetMapping("/hello") 等同于 @RequestMapping(value="/hello", method=RequestMethod.GET) @PostMapping("/world") 等同于 @RequestMapping(value="/world", method=RequestMethod.POST) 映射多个地址: @RequestMapping 还可以将多个请求映射到一个方法上,只需要给 value 来指定一个包含多个路径的列表。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.pudding.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller @RequestMapping("/index") public class HelloWorldController { @RequestMapping(value = {"/hello", "/world", "/helloworld"}) public String hello() { return "/WEB-INF/views/success.jsp"; } } |
URI模板: URI模板可以为快速访问 @RequestMapping 中指定的URL的一个特定的部分提供很大的便利。 使用 @PathVariable 可以获取到 {name} 的值,并在控制台进行输出。比如请求地址为:http://localhost:8080/SpringMVC/hello/jack 那么控制台上将会把 jack 进行输出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.pudding.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloWorldController { @RequestMapping("/hello/{name}") public String hello(@PathVariable String name) { System.out.println(name); return "/WEB-INF/views/success.jsp"; } } |
如果路径中的URI变量和方法中的参数名不一样的话,那么需要在 @PathVariable 中显示的绑定参数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.pudding.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloWorldController { @RequestMapping("/hello/{name}") public String hello(@PathVariable("name") String username) { System.out.println(username); return "/WEB-INF/views/success.jsp"; } } |
一个方法可以拥有任意数量的 @PathVariable 注解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.pudding.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloWorldController { @RequestMapping("/hello/{name}/age/{age}") public String hello(@PathVariable String name, @PathVariable int age) { System.out.println("name:" + name + ",age:" + age); return "/WEB-INF/views/success.jsp"; } } |
@PathVariable 可以被应用于所有 简单类型 的参数上,比如 int、long、Date 等类型。Spring会自动地帮你把参数转化成合适的类型,如果转换失败,就抛出一个 TypeMismatchException。如果你需要处理其他数据类型的转换,也可以注册自己的类。 带正则表达式的URI模板: 你可以使用正则表达式来准确的描述可以接受的请求路径:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package com.pudding.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloWorldController { @RequestMapping("/hello/{name:[a-z]+}/age/{age}") public String hello(@PathVariable String name, @PathVariable int age) { System.out.println("name:" + name + ",age:" + age); return "/WEB-INF/views/success.jsp"; } } |
Ant风格的路径模式: 除了URI模板外,@RequestMapping注解还支持Ant风格的路径模式(如/hello/*.do等)。不仅如此,还可以把URI模板变量和Ant风格的glob组合起来使用(比如/hello/*/user/{userId}这样的用法等)。其中*则表示任意字符串。但是遇到 / 那么就会认为是下一部分的URI,所以 * 中不能有 / 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.pudding.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloWorldController { // 匹配的地址:http://localhost:8080/SpringMVC/hello/jack/user/18 @RequestMapping("/hello/*/user/{userId}") public String hello(@PathVariable String userId) { System.out.println(userId); return "/WEB-INF/views/success.jsp"; } } |
那么想要匹配带 / 的路径那该怎么办?可以使用 ** 来进行匹配。例如 /hello/**/user/{userId} 则会匹配 /hello/ 和 /user/{userId} 之间的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
package com.pudding.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class HelloWorldController { // 匹配的地址:http://localhost:8080/SpringMVC/hello/jack/tom/cat/user/18 @RequestMapping("/hello/**/user/{userId}") public String hello(@PathVariable String userId) { System.out.println(userId); return "/WEB-INF/views/success.jsp"; } } |
路径样式的匹配(Path Pattern Comparison): 当一个URL同时匹配多个模板(pattern)时,我们将需要一个算法来决定其中最匹配的一个。 URI模板变量的数目和通配符数量的总和最少的那个路径模板更准确。举个例子,/hotels/{hotel}/*这个路径拥有一个URI变量和一个通配符,而/hotels/{hotel}/**这个路径则拥有一个URI变量和两个通配符,因此,我们认为前者是更准确的路径模板。 如果两个模板的URI模板数量和通配符数量总和一致,则路径更长的那个模板更准确。举个例子,/foo/bar*就被认为比/foo/*更准确,因为前者的路径更长。 如果两个模板的数量和长度均一致,则那个具有更少通配符的模板是更加准确的。比如,/hotels/{hotel}就比/hotels/*更精确。 […]
View DetailsIntelliJ IDEA 简称 IDEA,被业界公认为最好的 Java 集成开发工具,尤其在智能代码助手、代码自动提示、代码重构、代码版本管理(Git、SVN、Maven)、单元测试、代码分析等方面有着亮眼的发挥。IDEA 产于捷克,开发人员以严谨著称的东欧程序员为主。IDEA 分为社区版和付费版两个版本。 idea2021免费激活教程: https://www.jb51.net/article/195962.htm https://www.jb51.net/article/196349.htm 下面开始今天的正文介绍: 很多文章介绍IntelliJ IDEA开启热部署功能都会写到在IntelliJ IDEA中的注册表中开启compiler.automake.allow.when.app.running选项,此选项在IntelliJ IDEA 2021.2之后的版本迁移到高级设置中。如下图所示: 如果你安装了中文语言包,那么它在这里 到此这篇关于解决IDEA2021版compiler.automake.allow.when.app.running不存在的问题的文章就介绍到这了,更多相关idea2021compiler.automake.allow.when.app.running不存在内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家! from:https://www.jb51.net/article/223927.htm
View Details1.SLF4J(Simple logging Facade for Java) 意思为简单日志门面,它是把不同的日志系统的实现进行了具体的抽象化,只提供了统一的日志使用接口,使用时只需要按照其提供的接口方法进行调用即可,由于它只是一个接口,并不是一个具体的可以直接单独使用的日志框架,所以最终日志的格式、记录级别、输出方式等都要通过接口绑定的具体的日志系统来实现,这些具体的日志系统就有log4j,logback,java.util.logging等,它们才实现了具体的日志系统的功能。 如何使用SLF4J? 既然SLF4J只是一个接口,那么实际使用时必须要结合具体的日志系统来使用,我们首先来看SLF4J和各个具体的日志系统进行绑定时的框架原理图: 其实slf4j原理很简单,他只提供一个核心slf4j api(就是slf4j-api.jar包),这个包只有日志的接口,并没有实现,所以如果要使用就得再给它提供一个实现了些接口的日志包,比 如:log4j,common logging,jdk log日志实现包等,但是这些日志实现又不能通过接口直接调用,实现上他们根本就和slf4j-api不一致,因此slf4j又增加了一层来转换各日志实现包的使 用,当然slf4j-simple除外。其结构如下: slf4j-api(接口层) | 各日志实现包的连接层( slf4j-jdk14, slf4j-log4j) | 各日志实现包 所以,结合各日志实现包使用时提供的jar包情况为: SLF4J和logback结合使用时需要提供的jar:slf4j-api.jar,logback-classic.jar,logback-core.jar SLF4J和log4j结合使用时需要提供的jar:slf4j-api.jar,slf4j-log412.jar,log4j.jar SLF4J和JDK中java.util.logging结合使用时需要提供的jar:slf4j-api.jar,slf4j-jdk14.jar SLF4J和simple(SLF4J本身提供的一个接口的简单实现)结合使用时需要提供的jar:slf4j-api.jar,slf4j-simple.jar 当然还有其他的日志实现包,以上是经常会使用到的一些。 注意,以上slf4j和各日志实现包结合使用时最好只使用一种结合,不然的话会提示重复绑定日志,并且会导致日志无法输出。 slf4j-api.jar:对外提供统一的日志调用接口,该接口具体提供的调用方式和方法举例说明: public class Test { private static final Logger logger = LoggerFactory.getLogger(Tester.class); //通过LoggerFactory获取Logger实例 public static void main(String[] args) { //接口里的统一的调用方法,各具体的日志系统都有实现这些方法 logger.info("testlog: {}", "test"); logger.debug("testlog: {}", "test"); logger.error("testlog: {}", "test"); logger.trace("testlog: {}", "test"); logger.warn("testlog: {}", "test"); } } 如果系统中之前已经使用了log4j做日志输出,想使用slf4j作为统一的日志输出,该怎么办呢? 如果之前系统中是单独使用log4j做为日志输出的,这时再想使用slf4j做为日志输出时,如果系统中日志比较多,此时更改日志输出方法肯定是不太现实的,这个时候就可以使用log4j-over-slf4j.jar将使用log4j日志框架输出的日志路由到slf4j上来统一采用slf4j来输出日志。 为什么要使用SLF4J? slf4j是一个日志接口,自己没有具体实现日志系统,只提供了一组标准的调用api,这样将调用和具体的日志实现分离,使用slf4j后有利于根据自己实际的需求更换具体的日志系统,比如,之前使用的具体的日志系统为log4j,想更换为logback时,只需要删除log4j相关的jar,然后加入logback相关的jar和日志配置文件即可,而不需要改动具体的日志输出方法,试想如果没有采用这种方式,当你的系统中日志输出有成千上万条时,你要更换日志系统将是多么庞大的一项工程。如果你开发的是一个面向公众使用的组件或公共服务模块,那么一定要使用slf4的这种形式,这有利于别人在调用你的模块时保持和他系统中使用统一的日志输出。 slf4j日志输出时可以使用{}占位符,如,logger.info("testlog: {}", "test"),而如果只使用log4j做日志输出时,只能以logger.info("testlog:"+"test")这种形式,前者要比后者在性能上更好,后者采用+连接字符串时就是new 一个String 字符串,在性能上就不如前者。 2.log4j(log for java) Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。 如何使用? 引入jar,使用log4j时需要的jar为:log4j.jar。 定义配置文件log4j.properties或log4j.xml 在具体的类中进行使用: 在需要日志输出的类中加入:private static final Logger logger = Logger.getLogger(Tester.class); //通过Logger获取Logger实例 在需要输出日志的地方调用相应方法即可:logger.debug(“System […]
View Detailslog4net .NET Core 版使用,log4net 2.0.7版发布也有一段时间了,从2.0.6 版开始就已经支持.NET Core。 之前有介绍NLog .NET Core版的使用,ASP.NET Core 开发-Logging 使用NLog 写日志文件。 ASP.NET Core已经内置了日志支持,可以轻松输出到控制台。使用log4net 将日志写入到文件及输出控制台。 .NET Core项目使用 新建一个 .NET Core 项目,选择控制台应用程序。 添加引用: Install-Package log4net 使用简单配置,将日志输出至控制台,跟之前.NET 版本略微有些区别,需要指定Repository 。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public static void Main(string[] args) { ILoggerRepository repository = LogManager.CreateRepository("NETCoreRepository"); // 默认简单配置,输出至控制台 BasicConfigurator.Configure(repository); ILog log = LogManager.GetLogger(repository.Name,"NETCorelog4net"); log.Info("NETCorelog4net log"); log.Info("test log"); log.Error("error"); log.Info("linezero"); Console.ReadKey(); } |
运行程序显示如下: 下面增加配置,让其输出至文件。 在项目中添加一个配置文件,这里添加一个log4net.config ,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
<?xml version="1.0" encoding="utf-8" ?> <configuration> <!-- This section contains the log4net configuration settings --> <log4net> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender"> <layout type="log4net.Layout.PatternLayout" value="%date [%thread] %-5level %logger - %message%newline" /> </appender> <appender name="FileAppender" type="log4net.Appender.FileAppender"> <file value="log-file.log" /> <appendToFile value="true" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> </appender> <appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender"> <file value="logfile/" /> <appendToFile value="true" /> <rollingStyle value="Composite" /> <staticLogFileName value="false" /> <datePattern value="yyyyMMdd'.log'" /> <maxSizeRollBackups value="10" /> <maximumFileSize value="1MB" /> <layout type="log4net.Layout.PatternLayout"> <conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> </appender> <!-- Setup the root category, add the appenders and set the default level --> <root> <level value="ALL" /> <appender-ref ref="ConsoleAppender" /> <appender-ref ref="FileAppender" /> <appender-ref ref="RollingLogFileAppender" /> </root> </log4net> </configuration> |
这里定义了三个 Appender,都会起作用,然后日志级别ALL 所有的日志都会记录。 更改Program.cs 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
public static void Main(string[] args) { ILoggerRepository repository = LogManager.CreateRepository("NETCoreRepository"); <strong>XmlConfigurator.Configure(repository, new FileInfo("log4net.config"</strong><strong>));</strong> ILog log = LogManager.GetLogger(repository.Name,"NETCorelog4net"); log.Info("NETCorelog4net log"); log.Info("test log"); log.Error("error"); log.Info("linezero"); Console.ReadKey(); } |
加了指定配置文件。运行程序会生成一个文件夹和一个文件,因为重新定义了ConsoleAppender,控制台输出也有所不同。 ASP.NET Core项目使用 在ASP.NET Core 使用也是一样,可以在Program.cs或Startup.cs 中指定Repository,然后控制器或中间件中获取对象使用。 以下代码是在Startup 构造函数中初始化log4net
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Startup { public static ILoggerRepository repository { get; set; } public Startup(IHostingEnvironment env) { var builder = new ConfigurationBuilder() .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) .AddEnvironmentVariables(); Configuration = builder.Build(); repository = LogManager.CreateRepository("NETCoreRepository"); XmlConfigurator.Configure(repository, new FileInfo("log4net.config")); } |
然后在Configure 中加了一个记录日志
1 2 3 4 |
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { var log = LogManager.GetLogger(repository.Name,typeof(Startup)); log.Info("test"); |
在HomeController 也增加了:
1 2 3 4 5 6 7 |
private ILog log = LogManager.GetLogger(Startup.repository.Name, typeof(HomeController)); public IActionResult Index() { log.Info("index view"); log.Error("Controller Error"); return View(); } |
运行应用结果如下,然后对应的文件会有日志记录。 对于输出中文乱码,需要增加编码引用,以及代码。NLog 那篇文章有介绍。写到日志文件是没有问题的。 参考文档: http://logging.apache.org/log4net/release/manual/configuration.html http://logging.apache.org/log4net/release/config-examples.html 如果你觉得本文对你有帮助,请点击“推荐”,谢谢。 ASP.NET Core 3.1 新书发布 《ASP.NET Core项目开发实战入门》 京东当当淘宝 GitHub:https://github.com/linezero 博客示例代码GitHub:https://github.com/linezero/Blog from:https://www.cnblogs.com/linezero/p/log4net.html
View Details“System.IO.FileNotFoundException:“Could not load file or assembly 'ClassLibrary2, Culture=neutral, PublicKeyToken=null'. 系统找不到指定的文件。”” 在 .net 程序开发中我们会经常用到反射,最近在开始慢慢接触 .netCore。Core 反射的语法也是一样的,所以写下来也没有什么问题,但是一运行,就弹出了开头的 异常。 项目结构如下 ClassLibrary3 是一个接口
1 2 3 4 5 6 7 |
namespace ClassLibrary3 { public interface IClass1 { void Print(); } } |
ClassLibrary2 是 ClassLibrary3 的实现
1 2 3 4 5 6 7 8 9 10 |
namespace ClassLibrary2 { public class Class1:IClass1 { public void Print() { Console.WriteLine("Print....."); } } } |
主程序通过反射获取到到类型并创建对象
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class Program { static void Main(string[] args) { Console.WriteLine("Hello World!"); Assembly assembly = Assembly.Load(new AssemblyName("ClassLibrary2")); Type type = assembly.GetType("ClassLibrary2.Class1"); var instance = (IClass1)Activator.CreateInstance(type); instance.Print(); Console.ReadLine(); } } |
这代码都这里没有问题,但是执行的时候却抛出了 开篇的异常。仔细检查了下,ClassLibrary2 生成的 dll 确认已经是复制到了主程序目录下。 刚开始接触Core 也没有系统去熟悉,最后经过多方努力后,发现在主程序项目下有个 deps.json 的文件 文件内容是这样的(里面没有 ClassLibrary2 因为我们没有在项目中引用 ClassLibrary2) 然后想着 那异常提示找不到路径,那我们在这里配置下不久可以了? 然后改成如下: 这时候我们运行程序: 完美! from:https://www.cnblogs.com/mengtree/p/7139108.html
View Details摘要
想要把一个复杂的微服务项目部署到K8S上去,首先我们得学会把单个SpringBoot应用部署上去。今天我们来讲下如何把SpringBoot应用部署到K8S上去,和使用Docker Compose部署非常类似,希望对大家有所帮助!
学前准备
学习本文需要有一些K8S基础,对K8S还不了解的朋友可以参考如下的文章。
《K8S太火了!花10分钟玩转它不香么?》
《自从上了K8S,项目更新都不带停机的!》
工作之余。技术?。记是不可能记住的。 只有写点东西 才能维持得了生活这样子的。好早就像写一篇关于任务调度的文章。终究是太懒了 一、Quartz.NET介绍 Quartz.NET是一个强大、开源、轻量的作业调度框架,是 OpenSymphony 的 Quartz API 的.NET移植,用C#改写,可用于winform和asp.net应用中。它灵活而不复杂。你能够用它来为执行一个作业而创建简单的或复杂的作业调度。它有很多特征,如:数据库支持,集群,插件,支持cron-like表达式等等。 官网:http://www.quartz-scheduler.net/ 源码:https://github.com/quartznet/quartznet 二、Quartz.NET用途 一,可以定时发邮件通知。 二 , 电商网站的定时打折活动。(比如规定11月11日 淘宝购买女朋友打八折) 三, 定时对数据更新 或者添加。 四,自己朋友生日。 可以定时发生日祝福。 等等 (我也不一一举例了) 三、Quartz.NET安装 我使用的VS版本是2015的 选择工具 – NuGet包管理 – 管理解决方案的NuGet包 输入 Quartz.NET 安装 还可以通过NuGet控制台 通过安装命令
1 |
Install-Package Quartz |
1 |
四、Quartz.NET实现思路 一,继承并实现IJob接口,在Execute 方法中写你要做的事情(切记 ) 二,使用Quartz 中的API 定义好 工作 触发器 以及工厂 三,加入可视化(远程管理) 四,建立配置的通讯。 五,在全局类(Global.asax)Application_Start注册并开启定时任务 五、上代码 其中JobWork 是我的定义的工作文件 里面有你要执行的工作已经对应的触发器(一个工作对应一个触发器) 首先 我写了一个向文本文件中插入文字的方法已经触发器 (注意 必须继承IJob 要做的事情写在 Execute 中)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class AddMassagejob : IJob { public void Execute(IJobExecutionContext context) { var reportDirectory = string.Format("~/text/{0}/", DateTime.Now.ToString("yyyy-MM-ssss")); reportDirectory = System.Web.Hosting.HostingEnvironment.MapPath(reportDirectory); if (!Directory.Exists(reportDirectory)) { Directory.CreateDirectory(reportDirectory); } var dailyReportFullPath = string.Format("{0}text_{1}.log", reportDirectory, DateTime.Now.Day); var logContent = string.Format("{0}-{1}-{2}", DateTime.Now, "滴 滴滴", Environment.NewLine); if (logContent == null) { JobExecutionException jobex = new JobExecutionException("写入失败"); } File.AppendAllText(dailyReportFullPath, logContent); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class AddMasagerTriggerServer { public ITrigger AddMasagerTrigger() { var trigger = TriggerBuilder.Create() .WithIdentity("添加消息到日志", "作业触发器") .WithSimpleSchedule(x => x //.WithIntervalInSeconds(5) // .WithIntervalInHours(5) .WithIntervalInMinutes(5) //每五分钟执行一次 .RepeatForever()) .Build(); return trigger; } } |
我这里设置的是5分钟执行一次。 你也可以让他5秒 5小时执行一次。 关于时间配置 官方一些常用的实例 0 0 12 * * ? 每天12点触发 0 15 10 ? * * 每天10点15分触发 0 15 […]
View Detailsjava -Dfile.encoding=utf-8 -jar demo.jar 添加编码即可 from:https://www.cnblogs.com/provence666/p/10743551.html
View Details