框架controller找不到_SpingBoot框架知识详解

Spring boot框架

1、什么是Spring Boot?

​ Spring Boot是Spring开源组织下的子项目,是Spring组件一站式解决方案,主要是简化了使用Spring的难度,简省了繁重的配置,提供了各种启动器,开发者能快速上手。

Spring Boot官方文档:https://spring.io/projects/spring-boot/

2、Spring Boot的优点与特点

优点:

  • 独立运行
  • Spring Boot内嵌了各种servlet容器,Tomcat、Jetty等,不再需要打成war包部署到容器中,Spring Boot只要打成一个可执行的jar包就能独立运行,所有的依赖包都在一个jar包内
  • 简化配置
  • spring-boot-starter-web启动器自动依赖其他组件,简少了maven的配置。
  • 自动配置
  • Spring Boot能根据当前类路径下的类、jar包来自动配置bean,如添加一个spring-boot-starter-web启动器就能拥有web的功能,无需其他配置
  • 无代码生成和XML配置
  • Spring Boot配置过程中无代码生成,也无需XML配置文件就能完成所有配置工作,这一切都是借助于条件注解完成的,这也是Spring4.x的核心功能之一。
  • 应用监控
  • Spring Boot提供一系列端点可以监控服务及应用,做健康检测。
  • 上手容易

特点:

  • 为 Spring 开发提供一个更快、更广泛的入门体验。
  • 开箱即用,远离繁琐的配置。
  • 提供了一系列大型项目通用的非业务性功能,例如:内嵌服务器、安全管理、运行数据监控、运行状况检查和外部化配置等。
  • 绝对没有代码生成,也不需要XML配置。

3、Spring Boot的配置文件以及之间的区别

.properties 和 .yml,它们的区别主要是书写格式不同。

1).properties

app.user.name = javastack

2).yml

app:user:name: javastack

注:.yml 格式不支持 @PropertySource 注解导入配置。

4、Spring Boot的常用注解

@SpringBootApplication

申明让spring boot自动给程序进行必要的配置,这个配置等同于:@Configuration @EnableAutoConfiguration 和 @ComponentScan 三个配置。

@ResponseBody

表示该方法的返回结果直接写入HTTP response body中,一般在异步获取数据时使用,用于构建RESTful的api。在使用@RequestMapping后,返回值通常解析为跳转路径,加上@esponsebody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上@Responsebody后,会直接返回json数据。该注解一般会配合@RequestMapping一起使用。

@Controller

用于定义控制器类,在spring项目中由控制器负责将用户发来的URL请求转发到对应的服务接口(service层),一般这个注解在类中,通常方法需要配合注解@RequestMapping。

@RestController

用于标注控制层组件(如struts中的action),@ResponseBody和@Controller的合集。

@RequestMapping

提供路由信息,负责URL到Controller中的具体函数的映射。

@EnableAutoConfiguration

SpringBoot自动配置(auto-configuration):尝试根据你添加的jar依赖自动配置你的Spring应用。例如,如果你的classpath下存在HSQLDB,并且你没有手动配置任何数据库连接beans,那么我们将自动配置一个内存型(in-memory)数据库”。你可以将@EnableAutoConfiguration或者@SpringBootApplication :添加到一个@Configuration类上来选择自动配置。如果发现应用了你不想要的特定自动配置类,你可以使用@EnableAutoConfiguration注解的排除属性来禁用它们。

@ComponentScan

表示将该类自动发现扫描组件。个人理解相当于,如果扫描到有@Component、@Controller、@Service等这些注解的类,并注册为Bean,可以自动收集所有的Spring组件,包括@Configuration类。我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。可以自动收集所有的Spring组件,包括@Configuration类。我们经常使用@ComponentScan注解搜索beans,并结合@Autowired注解导入。如果没有配置的话,Spring Boot会扫描启动类所在包下以及子包下的使用了@Service,@Repository等注解的类。

@Configuration

相当于传统的xml配置文件,如果有些第三方库需要用到xml文件,建议仍然通过@Configuration类作为项目的配置主类——可以使用@ImportResource注解加载xml配置文件。

@Import:用来导入其他配置类。

@ImportResource:用来加载xml配置文件。

@Service:一般用于修饰service层的组件

@Repository:使用@Repository注解可以确保DAO或者repositories提供异常转译,这个注解修饰的DAO或者repositories类会被ComponetScan发现并配置,同时也不需要为它们提供XML配置项。

@Value:注入Spring boot application.properties配置的属性的值。示例代码:

@Inject:等价于默认的@Autowired,只是没有required属性;

@Component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注。

@Bean:相当于XML中的,放在方法的上面,而不是类,意思是产生一个bean,并交给spring管理。

@AutoWired

自动导入依赖的bean。byType方式。把配置好的Bean拿来用,完成属性、方法的组装,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。当加上(required=false)时,就算找不到bean也不报错。

@Qualifier

当有多个同一类型的Bean时,可以用@Qualifier(“name”)来指定。与@Autowired配合使用。@Qualifier限定描述符除了能根据名字进行注入,但能进行更细粒度的控制如何选择候选者,具体使用方式如下:

@Resource(name=”name”,type=”type”):没有括号内内容的话,默认byName。与@Autowired干类似的事。

5、spring boot开启的两种方式

​ ① 继承spring-boot-starter-parent项目

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.6.RELEASE</version>xml</parent>

​ ② 导入spring-boot-dependencies项目依赖

<dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>1.5.6.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencyManagement>

Spring Boot依赖注意点

1、属性覆盖只对继承有效

​ Spring Boot依赖包里面的组件的版本都是和当前Spring Boot绑定的,如果要修改里面组件的版本,只需要添加如下属性覆盖即可,但这种方式只对继承有效,导入的方式无效。

<properties><slf4j.version>1.7.25<slf4j.version></properties>

如果导入的方式要实现版本的升级,达到上面的效果,这样也可以做到,把要升级的组件依赖放到Spring Boot之前。

<dependencyManagement><dependencies><!-- Override Spring Data release train provided by Spring Boot --><dependency><groupId>org.springframework.data</groupId><artifactId>spring-data-releasetrain</artifactId><version>Fowler-SR2</version><scope>import</scope><type>pom</type></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>1.5.6.RELEASE</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>

需要注意,要修改Spring Boot的依赖组件版本可能会造成不兼容的问题。

2、资源文件过滤问题

使用继承Spring Boot时,如果要使用Maven resource filter过滤资源文件时,资源文件里面的占位符为了使${}和Spring Boot区别开来,此时要用@...@包起来,不然无效。另外,@...@占位符在yaml文件编辑器中编译报错,所以使用继承方式有诸多问题,坑要慢慢趟。

6、Spring Boot的几种运行方式

  • 打包用命令或者放到容器中运行
  • 用 Maven/ Gradle 插件运行
  • 直接执行 main 方法运行

7、Spring Boot实现热部署

第一种引用devtools依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-devtools</artifactId><optional>true</optional></dependency>

第二种自定义配置热部署

# 热部署开关,false即不启用热部署spring.devtools.restart.enabled: true# 指定热部署的目录#spring.devtools.restart.additional-paths: src/main/java# 指定目录不更新spring.devtools.restart.exclude: test/**

第三种Intellij Idea修改

​ 1、勾上自动编译或者手动重新编译

​ File > Settings > Compiler-Build Project automatically

​ 2、注册

​ ctrl + shift + alt + / > Registry > 勾选Compiler autoMake allow when app running

注意事项:

1、生产环境devtools将被禁用,如java -jar方式或者自定义的类加载器等都会识别为生产环境。

2、打包应用默认不会包含devtools,除非你禁用SpringBoot Maven插件的 excludeDevtools属性。

3、Thymeleaf无需配置 spring.thymeleaf.cache:false,devtools默认会自动设置,参考完整属性。

4、devtools会在windows资源管理器占用java进程,在开发工具里面杀不掉,只能手动kill掉,不然重启会选成端口重复绑定报错。

更多详细用法,参考发官方文档:https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-devtools.html

8、保护Spring Boot的应用方法

  1. 在生产中使用HTTPS
  2. 使用Snyk检查你的依赖关系
  3. 升级到最新版本
  4. 启动CSRF保护
  5. 使用内容安全策略防止XSS攻击
  6. 使用OpenID Connect进行身份验证
  7. 管理密码使用面密码哈希
  8. 安全地存储秘密
  9. 使用OWASP的ZAP测试应用程序
  10. 安全团队进行代码审查

9、Spring Boot配置加载顺序(2.0)

1、properties文件;

2、YAML文件;

3、系统环境变量;

4、命令行参数;

等等……

配置属性加载的顺序如下:

1、开发者工具 `Devtools` 全局配置参数;2、单元测试上的 `@TestPropertySource` 注解指定的参数;3、单元测试上的 `@SpringBootTest` 注解指定的参数;4、命令行指定的参数,如 `java -jar springboot.jar --name="Java技术栈"`;5、命令行中的 `SPRING_APPLICATION_JSONJSON` 指定参数, 如 `java -Dspring.application.json='{"name":"Java技术栈"}' -jar springboot.jar`6、`ServletConfig` 初始化参数;7、`ServletContext` 初始化参数;8、JNDI参数(如 `java:comp/env/spring.application.json`);9、Java系统参数(来源:`System.getProperties()`);10、操作系统环境变量参数;11、`RandomValuePropertySource` 随机数,仅匹配:`ramdom.*`;12、JAR包外面的配置文件参数(`application-{profile}.properties(YAML)`)13、JAR包里面的配置文件参数(`application-{profile}.properties(YAML)`)14、JAR包外面的配置文件参数(`application.properties(YAML)`)15、JAR包里面的配置文件参数(`application.properties(YAML)`)16、`@Configuration`配置文件上 `@PropertySource` 注解加载的参数;17、默认参数(通过 `SpringApplication.setDefaultProperties` 指定);

10、Spring Boot日志集成

Spring Boot日志框架

Spring Boot支持Java Util Logging,Log4j2,Lockback作为日志框架,如果你使用starters启动器,Spring Boot将使用Logback作为默认日志框架。无论使用哪种日志框架,Spring Boot都支持配置将日志输出到控制台或者文件中。

spring-boot-starter启动器包含spring-boot-starter-logging启动器并集成了slf4j日志抽象及Logback日志框架。

11、Spring Boot读取配置的几种方式

读取application文件

在application.yml或者properties文件中添加:

info.address=USA

info.company=Spring

info.degree=high

@Value注解读物方式

import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;@Componentpublic class InfoConfig1 {@Value("${info.address}")private String address;@Value("${info.company}")private String company;@Value("${info.degree}")private String degree;public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public String getCompany() {return company;}public void setCompany(String company) {this.company = company;}public String getDegree() {return degree;}public void setDegree(String degree) {this.degree = degree;}}

@ConfigurationProperties注解读取方式

@Component@ConfigurationProperties(prefix = "info")public class InfoConfig2 {private String address;private String company;private String degree;public String getAddress() {return address;}public void setAddress(String address) {this.address = address;}public String getCompany() {return company;}public void setCompany(String company) {this.company = company;}public String getDegree() {return degree;}public void setDegree(String degree) {this.degree = degree;}}

读取指定文件

资源目录下建立config/db-config.properties:

db.username=root

db.password=123456

@PropertySource+@Value注解读取方式

@Component@PropertySource(value = { "config/db-config.properties" })public class DBConfig1 {@Value("${db.username}")private String username;@Value("${db.password}")private String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}}

注意:@PropertySource不支持yml文件读取。

@PropertySource+@ConfigurationProperties注解读取方式

@Component@ConfigurationProperties(prefix = "db")@PropertySource(value = { "config/db-config.properties" })public class DBConfig2 {private String username;private String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}}

Environment读取方式

以上所有加载出来的配置都可以通过Environment注入获取到。

@Autowiredprivate Environment env;// 获取参数String getProperty(String key);

总结:从以上示例来看,Spring Boot可以通过@PropertySource,@Value,@Environment,@ConfigurationProperties来绑定变量。

12、Spring Boot自动配置原理

① SpringBoot启动的时候加载主配置类,开启了自动配置功能@EnableAutoConfiguration。

② @EnableAutoConfiguration的作用是利用AutoConfigurationImportSelector给容器中导入一些组件。

③ 可以查看public String[] selectImports(AnnotationMetadata annotationMetadata)方法的内容。

④ 通过protected List getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes)获取候选的配置,这个是扫描所有jar包类路径下"META-INF/spring.factories";

⑤ 然后把扫描到的这些文件包装成Properties对象。

⑥ 从properties中获取到EnableAutoConfiguration.class类名对应的值,然后把他们添加在容器中。

⑦ 整个过程就是将类路径下"META-INF/spring.factories"里面配置的所有EnableAutoConfiguration的值加入到 容器中。

⑧ 每一个这样XXAutoConfiguration类都是容器中的一个组件都加入到容器中,用他们来做自动配置。

每一个自动配置类进行自动配置功能,以HttpEncodingAutoConfiguration为例解释自动配置原理

⑨根据当前不同的条件判断,决定这个配置是否生效。

13、Spring Boot中的starter

​ 首先,这个 Starter 并非什么新的技术点,基本上还是基于 Spring 已有功能来实现的。首先它提供了一个自动化配置类,一般命名为 XXXAutoConfiguration ,在这个配置类中通过条件注解来决定一个配置是否生效(条件注解就是 Spring 中原本就有的),然后它还会提供一系列的默认配置,也允许开发者根据实际情况自定义相关配置,然后通过类型安全的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性。正因为如此,很多第三方框架,我们只需要引入依赖就可以直接使用了。 当然,开发者也可以自定义 Starter,自定义 Starter 可以参考:徒手撸一个 Spring Boot 中的 Starter ,解密自动化配置黑魔法!

14、Spring Boot跨域问题

​ 跨域可以在前端通过 JSONP 来解决,但是 JSONP 只可以发送 GET 请求,无法发送其他类型的请求,在 RESTful 风格的应用中,就显得非常鸡肋,因此我们推荐在后端通过 (CORS,Cross-origin resource sharing) 来解决跨域问题。这种解决方案并非 Spring Boot 特有的,在传统的 SSM 框架中,就可以通过 CORS 来解决跨域问题,只不过之前我们是在 XML 文件中配置 CORS ,现在则是通过 @CrossOrigin 注解来解决跨域问题。关于 CORS ,小伙伴们可以参考:Spring Boot 中通过 CORS 解决跨域问题

15、Spring Boot定时任务

​ 使用spring boot创建定时任务主要有以下三种创建方式:

​ 一、基于注解@Scheduled

​ 默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时 间的影响。

​ ① 创建定时器

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class SaticScheduleTask {//3.添加定时任务@Scheduled(cron = "0/5 * * * * ?")//或直接指定时间间隔,例如:5秒//@Scheduled(fixedRate=5000)private void configureTasks() {System.err.println("执行静态定时任务时间: " + LocalDateTime.now());}
}

②启动测试

d35a442a3d73c4fb0a1cfead58076eef.png

​ 显然,使用@Scheduled 注解很方便,但缺点是当我们调整了执行周期的时候,需要重启应用才能生效,这多少有些不方便。为了达到实时生效的效果,可以使用接口来完成定时任务。

​ 二、基于接口(SchedulingConfigurer)

​ 1、导入依赖包:

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.0.4.RELEASE</version></parent><dependencies><dependency><!--添加Web依赖 --><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><!--添加MySql依赖 --><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency><dependency><!--添加Mybatis依赖 配置mybatis的一些初始化的东西--><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version></dependency><dependency><!-- 添加mybatis依赖 --><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.4.5</version><scope>compile</scope></dependency></dependencies>

​ 2、添加数据库记录

开启本地数据库mysql,随便打开查询窗口,然后执行脚本内容,代码如下:

DROP DATABASE IF EXISTS `socks`;
CREATE DATABASE `socks`;
USE `SOCKS`;
DROP TABLE IF EXISTS `cron`;
CREATE TABLE `cron`  (`cron_id` varchar(30) NOT NULL PRIMARY KEY,`cron` varchar(30) NOT NULL  
);
INSERT INTO `cron` VALUES ('1', '0/5 * * * * ?');

然后在项目中的application.yml添加数据源:

spring:datasource:url: jdbc:mysql://localhost:3306/socksusername: rootpassword: 123456

3、创建定时器

​ 数据库准备好数据之后,我们编写定时任务,注意这里添加的是TriggerTask,目的是循环读取我们在数据库设置好的执行周期,以及执行相关定时任务的内容。

具体代码如下:

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class DynamicScheduleTask implements SchedulingConfigurer {@Mapperpublic interface CronMapper {@Select("select cron from cron limit 1")public String getCron();}@Autowired      //注入mapper@SuppressWarnings("all")CronMapper cronMapper;/*** 执行定时任务.*/@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {taskRegistrar.addTriggerTask(//1.添加任务内容(Runnable)() -> System.out.println("执行动态定时任务: " + LocalDateTime.now().toLocalTime()),//2.设置执行周期(Trigger)triggerContext -> {//2.1 从数据库获取执行周期String cron = cronMapper.getCron();//2.2 合法性校验.if (StringUtils.isEmpty(cron)) {// Omitted Code ..}//2.3 返回执行周期(Date)return new CronTrigger(cron).nextExecutionTime(triggerContext);});}}

4、启动测试

启动应用后,查看控制台,打印时间是我们预期的每10秒一次:

403ed8ecf61ffb144adf90f87837180d.png

然后打开Navicat ,将执行周期修改为每6秒执行一次,如图:

d04ba80ce21febb30ed91c1a8ee0ded8.png

查看控制台,发现执行周期已经改变,并且不需要我们重启应用,十分方便。如图:

efb9cee0f8e5533e58894dc7e8b5eb05.png

注意: 如果在数据库修改时格式出现错误,则定时任务会停止,即使重新修改正确;此时只能重新启动项目才能恢复。

三、基于注解设定多线程定时任务

1、创建多线程定时任务

//@Component注解用于对那些比较中立的类进行注释;
//相对与在持久层、业务层和控制层分别采用 @Repository、@Service 和 @Controller 对分层中的类进行注释
@Component
@EnableScheduling   // 1.开启定时任务
@EnableAsync        // 2.开启多线程
public class MultithreadScheduleTask {@Async@Scheduled(fixedDelay = 1000)  //间隔1秒public void first() throws InterruptedException {System.out.println("第一个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "rn线程 : " + Thread.currentThread().getName());System.out.println();Thread.sleep(1000 * 10);}@Async@Scheduled(fixedDelay = 2000)public void second() {System.out.println("第二个定时任务开始 : " + LocalDateTime.now().toLocalTime() + "rn线程 : " + Thread.currentThread().getName());System.out.println();}}

2、启动测试查看控制台

eb5b5913c775e8a5e96ead1bd2be8fbd.png

从控制台可以看出,第一个定时任务和第二个定时任务互不影响;

并且,由于开启了多线程,第一个任务的执行时间也不受其本身执行时间的限制,所以需要注意可能会出现重复操作导致数据异常

16、Spring Boot 全局异常处理

1、基于@ControllerAdvice注解的Controller层

创建 MyControllerAdvice,并添加 @ControllerAdvice注解。

package com.shsxt.demo.controller;import org.springframework.ui.Model;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;/*** controller 增强器* @author sam* @since 2017/7/17*/
@ControllerAdvice
public class MyControllerAdvice {/*** 应用到所有@RequestMapping注解方法,在其执行之前初始化数据绑定器* @param binder*/@InitBinderpublic void initBinder(WebDataBinder binder) {}/*** 把值绑定到Model中,使全局@RequestMapping可以获取到该值* @param model*/@ModelAttributepublic void addAttributes(Model model) {model.addAttribute("author", "Magical Sam");}/*** 全局异常捕捉处理* @param ex* @return*/@ResponseBody@ExceptionHandler(value = Exception.class)public Map errorHandler(Exception ex) {Map map = new HashMap();map.put("code", 100);map.put("msg", ex.getMessage());return map;}}

启动应用后,被 @ExceptionHandler、@InitBinder、@ModelAttribute 注解的方法,都会作用在 被 @RequestMapping 注解的方法上。

@ModelAttribute:在Model上设置的值,对于所有被 @RequestMapping 注解的方法中,都可以通过 ModelMap 获取,如下:

@RequestMapping("/home")
public String home(ModelMap modelMap) {System.out.println(modelMap.get("author"));
}//或者 通过@ModelAttribute获取@RequestMapping("/home")
public String home(@ModelAttribute("author") String author) {System.out.println(author);
}

@ExceptionHandler 拦截了异常,我们可以通过该注解实现自定义异常处理。其中,@ExceptionHandler 配置的 value 指定需要拦截的异常类型,上面拦截了 Exception.class 这种异常。

2、基于Springboot自身的全局异常统一处理,主要是实现ErrorController接口或者继承AbstractErrorController抽象类或者继承BasicErrorController类

Controller层代码:

@Controller
@RequestMapping(value = "error")
@EnableConfigurationProperties({ServerProperties.class})
public class ExceptionController implements ErrorController {private ErrorAttributes errorAttributes;@Autowiredprivate ServerProperties serverProperties;/*** 初始化ExceptionController* @param errorAttributes*/@Autowiredpublic ExceptionController(ErrorAttributes errorAttributes) {Assert.notNull(errorAttributes, "ErrorAttributes must not be null");this.errorAttributes = errorAttributes;}/*** 定义404的ModelAndView* @param request* @param response* @return*/@RequestMapping(produces = "text/html",value = "404")public ModelAndView errorHtml404(HttpServletRequest request,HttpServletResponse response) {response.setStatus(getStatus(request).value());Map<String, Object> model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));return new ModelAndView("error/404", model);}/*** 定义404的JSON数据* @param request* @return*/@RequestMapping(value = "404")@ResponseBodypublic ResponseEntity<Map<String, Object>> error404(HttpServletRequest request) {Map<String, Object> body = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));HttpStatus status = getStatus(request);return new ResponseEntity<Map<String, Object>>(body, status);}/*** 定义500的ModelAndView* @param request* @param response* @return*/@RequestMapping(produces = "text/html",value = "500")public ModelAndView errorHtml500(HttpServletRequest request,HttpServletResponse response) {response.setStatus(getStatus(request).value());Map<String, Object> model = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));return new ModelAndView("error/500", model);}/*** 定义500的错误JSON信息* @param request* @return*/@RequestMapping(value = "500")@ResponseBodypublic ResponseEntity<Map<String, Object>> error500(HttpServletRequest request) {Map<String, Object> body = getErrorAttributes(request,isIncludeStackTrace(request, MediaType.TEXT_HTML));HttpStatus status = getStatus(request);return new ResponseEntity<Map<String, Object>>(body, status);}/*** Determine if the stacktrace attribute should be included.* @param request the source request* @param produces the media type produced (or {@code MediaType.ALL})* @return if the stacktrace attribute should be included*/protected boolean isIncludeStackTrace(HttpServletRequest request,MediaType produces) {ErrorProperties.IncludeStacktrace include = this.serverProperties.getError().getIncludeStacktrace();if (include == ErrorProperties.IncludeStacktrace.ALWAYS) {return true;}if (include == ErrorProperties.IncludeStacktrace.ON_TRACE_PARAM) {return getTraceParameter(request);}return false;}/*** 获取错误的信息* @param request* @param includeStackTrace* @return*/private Map<String, Object> getErrorAttributes(HttpServletRequest request,boolean includeStackTrace) {RequestAttributes requestAttributes = new ServletRequestAttributes(request);return this.errorAttributes.getErrorAttributes(requestAttributes,includeStackTrace);}/*** 是否包含trace* @param request* @return*/private boolean getTraceParameter(HttpServletRequest request) {String parameter = request.getParameter("trace");if (parameter == null) {return false;}return !"false".equals(parameter.toLowerCase());}/*** 获取错误编码* @param request* @return*/private HttpStatus getStatus(HttpServletRequest request) {Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");if (statusCode == null) {return HttpStatus.INTERNAL_SERVER_ERROR;}try {return HttpStatus.valueOf(statusCode);}catch (Exception ex) {return HttpStatus.INTERNAL_SERVER_ERROR;}}/*** 实现错误路径,暂时无用* @see ExceptionMvcAutoConfiguration#containerCustomizer()* @return*/@Overridepublic String getErrorPath() {return "";}}

3、基于AOP也可以实现异常的全局处理

在执行切点中配置的路径中的方法有异常时,可以被捕获到

建议使用该方式:选用AOP方式主要是因为AOP不只可以做全局异常统一处理还可以统一打印接口请求入参和返回结果日志,打印接口访问性能日志,处理sql注入攻击以及处理入参特殊字符等问题

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.base.Stopwatch;
import com.google.common.collect.Lists;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;/*** @Author: smy* @Description: 调用接口打印性能日志以及接口报错之后记录错误日志* @Date: 2018/9/20* @Time: 15:16*/
@Component
@Aspect
public class InterfaceRequestErrrorAndPerformanceLog {public static final Logger logger = LoggerFactory.getLogger(InterfaceRequestErrrorAndPerformanceLog.class);@Value("${dc.log.bad.value:3000}")private int performanceBadValue;@Resourceprivate RabbitMQService rabbitMQService;@Resourceprivate InterfaceErrorService interfaceErrorService;@Pointcut("execution(* test.test.test.test.test.controller.*.*.*(..))")public void pointCut(){}@Around("pointCut()")public APIResponse handleControllerMethod(ProceedingJoinPoint pjp) throws Throwable{Stopwatch stopwatch = Stopwatch.createStarted();APIResponse apiResponse;try {logger.info("执行Controller开始: " + pjp.getSignature() + " 参数:" + Lists.newArrayList(pjp.getArgs()).toString());//处理入参特殊字符和sql注入攻击checkRequestParam(pjp);//执行访问接口操作apiResponse = (APIResponse) pjp.proceed(pjp.getArgs());try{logger.info("执行Controller结束: " + pjp.getSignature() + ", 返回值:" + JSONObject.toJSONString(apiResponse));//此处将日志打印放入try-catch是因为项目中有些对象实体bean过于复杂,导致序列化为json的时候报错,但是此处报错并不影响主要功能使用,只是返回结果日志没有打印,所以catch中也不做抛出异常处理}catch (Exception ex){logger.error(pjp.getSignature()+" 接口记录返回结果失败!,原因为:{}",ex.getMessage());}Long consumeTime = stopwatch.stop().elapsed(TimeUnit.MILLISECONDS);logger.info("耗时:" + consumeTime + "(毫秒).");//当接口请求时间大于3秒时,标记为异常调用时间,并记录入库if(consumeTime > performanceBadValue){DcPerformanceEntity dcPerformanceEntity = new DcPerformanceEntity();dcPerformanceEntity.setInterfaceName(pjp.getSignature().toString());dcPerformanceEntity.setRequestParam(Lists.newArrayList(pjp.getArgs()).toString());dcPerformanceEntity.setConsumeTime(consumeTime + "毫秒");RabbitMQMessageTarget mqTarget = RabbitMQMessageTarget.createFanoutTarget(ProjectConstants.DC_KEY_EXCHANGE_PERFORMANCE, new String[] { ProjectConstants.DC_KEY_QUEUE_PERFORMANCE});rabbitMQService.send(mqTarget, JSON.toJSONString(dcPerformanceEntity));}} catch (Exception throwable) {apiResponse = handlerException(pjp, throwable);}return apiResponse;}/*** @Author: smy* @Description: 处理接口调用异常* @Date: 15:13 2018/10/25*/private APIResponse handlerException(ProceedingJoinPoint pjp, Throwable e) {APIResponse apiResponse;if(e.getClass().isAssignableFrom(ProjectException.class) ){//ProjectException为自定义异常类,项目中Controller层会把所有的异常都catch掉,并手工封装成ProjectException抛出来,这样做的目的是ProjectException会记录抛出异常接口的路径,名称以及请求参数等等,有助于错误排查ProjectException projectException = (ProjectException)e;logger.error("捕获到ProjectException异常:",JSONObject.toJSONString(projectException.getDcErrorEntity()));RabbitMQMessageTarget mqTarget = RabbitMQMessageTarget.createFanoutTarget(ProjectConstants.DC_KEY_EXCHANGE_INTERFACE_ERROR, new String[] { ProjectConstants.DC_KEY_QUEUE_INTERFACE_ERROR});rabbitMQService.send(mqTarget, JSON.toJSONString(dataCenterException.getDcErrorEntity()));apiResponse = new APIResponse(APIResponse.FAIL,null,projectException.getDcErrorEntity().getErrorMessage());} else if (e instanceof RuntimeException) {logger.error("RuntimeException{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e);apiResponse = new APIResponse(APIResponse.FAIL,null,e.getMessage());} else {logger.error("异常{方法:" + pjp.getSignature() + ", 参数:" + pjp.getArgs() + ",异常:" + e.getMessage() + "}", e);apiResponse = new APIResponse(APIResponse.FAIL,null,e.getMessage());}return apiResponse;}/*** @Author: gmy* @Description: 处理入参特殊字符和sql注入攻击* @Date: 15:37 2018/10/25*/private void checkRequestParam(ProceedingJoinPoint pjp){String str = String.valueOf(pjp.getArgs());if (!IllegalStrFilterUtil.sqlStrFilter(str)) {logger.info("访问接口:" + pjp.getSignature() + ",输入参数存在SQL注入风险!参数为:" + Lists.newArrayList(pjp.getArgs()).toString());DcErrorEntity dcErrorEntity = interfaceErrorService.processDcErrorEntity(pjp.getSignature() + "",Lists.newArrayList(pjp.getArgs()).toString(),"输入参数存在SQL注入风险!");throw new DataCenterException(dcErrorEntity);}if (!IllegalStrFilterUtil.isIllegalStr(str)) {logger.info("访问接口:" + pjp.getSignature() + ",输入参数含有非法字符!,参数为:" + Lists.newArrayList(pjp.getArgs()).toString());DcErrorEntity dcErrorEntity = interfaceErrorService.processDcErrorEntity(pjp.getSignature() + "",Lists.newArrayList(pjp.getArgs()).toString(),"输入参数含有非法字符!");throw new DataCenterException(dcErrorEntity);}}}

IllegalStrFilterUtil代码:

import org.slf4j.LoggerFactory;import java.util.regex.Matcher;
import java.util.regex.Pattern;/*** @Author: gmy* @Description: 特殊字符检测工具(防止传入非法字符和sql注入攻击)* @Date: 2018/10/25* @Time: 15:08*/
public class IllegalStrFilterUtil {private static final org.slf4j.Logger Logger = LoggerFactory.getLogger(IllegalStrFilterUtil.class);private static final String REGX = "!|!|@|◎|#|#|($)|¥|%|%|(^)|……|(&)|※|(*)|×|(()|(|())|)|_|——|(+)|+|(|)|§ ";/*** 对常见的sql注入攻击进行拦截** @param sInput* @return*  true 表示参数不存在SQL注入风险*  false 表示参数存在SQL注入风险*/public static Boolean sqlStrFilter(String sInput) {if (sInput == null || sInput.trim().length() == 0) {return false;}sInput = sInput.toUpperCase();if (sInput.indexOf("DELETE") >= 0 || sInput.indexOf("ASCII") >= 0 || sInput.indexOf("UPDATE") >= 0 || sInput.indexOf("SELECT") >= 0|| sInput.indexOf("'") >= 0 || sInput.indexOf("SUBSTR(") >= 0 || sInput.indexOf("COUNT(") >= 0 || sInput.indexOf(" OR ") >= 0|| sInput.indexOf(" AND ") >= 0 || sInput.indexOf("DROP") >= 0 || sInput.indexOf("EXECUTE") >= 0 || sInput.indexOf("EXEC") >= 0|| sInput.indexOf("TRUNCATE") >= 0 || sInput.indexOf("INTO") >= 0 || sInput.indexOf("DECLARE") >= 0 || sInput.indexOf("MASTER") >= 0) {Logger.error("该参数怎么SQL注入风险:sInput=" + sInput);return false;}Logger.info("通过sql检测");return true;}/*** 对非法字符进行检测** @param sInput* @return*  true 表示参数不包含非法字符*  false 表示参数包含非法字符*/public static Boolean isIllegalStr(String sInput) {if (sInput == null || sInput.trim().length() == 0) {return false;}sInput = sInput.trim();Pattern compile = Pattern.compile(REGX, Pattern.CASE_INSENSITIVE);Matcher matcher = compile.matcher(sInput);Logger.info("通过字符串检测");return matcher.find();}
}

17、spring-boot-starter-parent 的作用

  1. 定义了 Java 编译版本为 1.8 。
  2. 使用 UTF-8 格式编码。
  3. 继承自 spring-boot-dependencies,这个里边定义了依赖的版本,也正是因为继承了这个依赖,所以我们在写依赖时才不需要写版本号。
  4. 执行打包操作的配置。
  5. 自动化的资源过滤。
  6. 自动化的插件配置。
  7. 针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/383052.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

架构的演变

基本概念 在介绍架构之前&#xff0c;为了避免部分读者对架构设计中的一些概念不了解&#xff0c;下面对几个最基础的概念进行介绍。 1.什么是分布式&#xff1f; 系统中的多个模块在不同服务器上部署&#xff0c;即可称为分布式系统&#xff0c;如Tomcat和数据库分别部署在…

axure8.0导出页面打不开问题_excel怎么转pdf?excel打不开?转换成PDF就行了

excel转pdf怎么做&#xff1f;年底最后一天了&#xff0c;我都被一堆的Excel文件搞得头疼&#xff0c;在这些时间里&#xff0c;要让我对几个G的文件进行操作&#xff0c;我已经是忙得不可开交&#xff0c;而在最后的最后&#xff0c;我的主管还说他的电脑无法打开我的Excel 了…

python怎么爬虫理数据_Python神技能 | 使用爬虫获取汽车之家全车型数据

最近想在工作相关的项目上做技术改进&#xff0c;需要全而准的车型数据&#xff0c;寻寻觅觅而不得&#xff0c;所以就只能自己动手丰衣足食&#xff0c;到网上获&#xff08;窃&#xff09;得&#xff08;取&#xff09;数据了。汽车之家是大家公认的数据做的比较好的汽车网站…

linux运算_CentOS「linux」学习笔记22:算术运算符、逻辑运算符、关系运算符

​linux基础操作&#xff1a;主要介绍啦算术运算符、逻辑运算符、关系运算符1.算术运算符[主要用来计算数值]注意使用expr运算时运算符和数值之间需要有空格&#xff0c;其他方式运算时不能有空格。常用算术运算符号&#xff1a;表示相加&#xff0c;&#xff0d;表示相减&…

python实现小型搜索引擎设计_基于JAVA的中小型饭店餐饮管理系统的设计与实现...

好程序设计擅长JAVA(SSM,SSH,SPRINGBOOT)、PYTHON(DJANGO/FLASK)、THINKPHP、C#、安卓、微信小程序、MYSQL、SQLSERVER等&#xff0c;欢迎咨询今天将为大家分析一个中小型饭店餐饮管理系统(俗话说“民以食为天”,中国的饮食文化有着久远的历史。“吃”不仅仅指的是填饱肚子,它早…

评估报告有效期过期了怎么办_托福成绩过期了怎么办?

托福成绩是有期限的&#xff0c;考生申请美国大学的时候也只能在托福成绩有效期内。所以考托福的时候一定要关注一下托福成绩什么时候过期&#xff0c;以及大学申请的截止日期&#xff0c;提前做好安排。下面我们一起看看关于托福成绩有效期的相关问题。托福成绩有效期是多久&a…

装配组件_基于Haption力反馈系统的交互式装配仿真

在一个新工业产品的设计过程中&#xff0c;装配规划是非常重要的任务。如果规划不好将造成很大的资金浪费&#xff0c;致使组件不能正确地集成。例如典型问题&#xff1a;移动一个组件到指定位置但空间不足&#xff1b;使用工具够不到螺丝&#xff1b;操作者没有足够的视域以保…

作为唯一索引_Mysql什么情况下不走索引?

本文基于Mysql5.7版本和InnoDB存储引擎。1、InnoDB索引组织表在InnoDB引擎中&#xff0c;表都是按照主键顺序组织存放的&#xff0c;这种存放方式的表称为索引组织表。InnoDB存储引擎中的表&#xff0c;都有主键&#xff0c;如果没有显式声明主键&#xff0c;则采取以下措施&am…

r语言系统计算上是奇异的_R语言实现并行计算

Python作为多线程的编程语言在并行方面相对于R语言有很大的优势&#xff0c;然而作为占据统计分析一席之地的R语言自然不能没有并行计算的助力。那么我们来看下在R语言中有哪些并行的包&#xff1a;隐式并行&#xff1a;OpenBLAS&#xff0c;Intel MKL&#xff0c;NVIDIA cuBLA…

cansina 目录_dirmap - 一个高级web目录、文件扫描工具-华盟网

Dirmap一个高级web目录扫描工具&#xff0c;功能将会强于DirBuster、Dirsearch、cansina、御剑需求分析经过大量调研&#xff0c;总结一个优秀的web目录扫描工具至少具备以下功能&#xff1a;并发引擎能使用字典能纯爆破能爬取页面动态生成字典能fuzz扫描自定义请求自定义响应结…

唯有自己变得强大_物竞天择,适者生存,唯有强大自己,方能百毒不侵

物竞天择&#xff0c;适者生存&#xff0c;这是亘古不变的道理。面对生活中的困难&#xff0c;人生路上的挫折&#xff0c;我们只有足够坚强&#xff0c;足够勇敢&#xff0c;足够强大&#xff0c;才能战胜这一切。人活着要明白&#xff0c;你所有的负面&#xff0c;都源于你的…

树莓派c语言运行_树莓派完成简单的编程(四)

在上一篇文章中&#xff0c;我们学习了Vi文本编辑器&#xff0c;那么用它可以实现什么功能呢&#xff1f;树莓派python以及c语言编程这里我选择了最简单和很流行的两种编程语言&#xff1a;C语言和Python。实现最简单的功能&#xff0c;输出hello world。Python编程简介Python是…

python中的与或非_「Python基础」 While 循环语句

Python 编程中 while 语句用于循环执行程序&#xff0c;即在某条件下&#xff0c;循环执行某段程序&#xff0c;以处理需要重复处理的相同任务。其基本形式为&#xff1a;while 判断条件&#xff1a;执行语句……执行语句可以是单个语句或语句块。判断条件可以是任何表达式&…

go定时器 每天重复_Go语言学习基础-定时器、计时器

Timer计时器如果希望在将来的某个时间点执行Go代码&#xff0c;或者在某个时间间隔重复执行Go代码&#xff0c;使用Go内置的timer和ticker功能。先看定时器timer&#xff0c;然后再看计时器ticker。定时器代表未来的单个事件。告诉定时器需要等待多长时间&#xff0c;它返回一个…

html类名定义规则_HTML入门笔记1

HTML 是谁发明的?Tim Berners-LeeHTML起手式&#xff1a;HTML起手式 <!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0&q…

怎样检测mysql5.5安装成功_64位wiN7系统中装配MySQL5.5.17(测试安装成功哦!)

64位wiN7系统中安装mysql5.5.17(测试安装成功哦&#xff01;&#xff01;~~)下载地址&#xff1a;[url] http://www.mysql.com/downloads/mysql/[/url]下载的话需要登录,你只需按照要求注册一个账号,然后下载即可.我下载的是mysql-5.5.17-winx64.msi版本.安装步骤:Step 1. Mysq…

xcode 创建模拟器_Xcode编译WebKit

下载WebKit源码1)进入https://webkit.org/2)点击页面的 Get Started 进入新页面&#xff0c;如下图所示3)点击 Getting the code 进入新页面&#xff0c;如下图所示4)在源码下载页面&#xff0c;有多种下载方式&#xff0c;包括直接下载代码zip包&#xff0c;通过SVN下载&#…

mysql iscsi_iscsi共享存储的简单配置和应用

1、环境介绍SCSI(Small Computer System Interface)是块数据传输协议&#xff0c;在存储行业广泛应用&#xff0c;是存储设备最基本的标准协议。从根本上说&#xff0c;iSCSI协议是一种利用IP网络来传输潜伏时间短的SCSI数据块的方法&#xff0c;ISCSI使用以太网协议传送SCSI命…

request mysql 接口_TP5接口开发

开启debug调试模式(正式上线建议关闭)config.php// 应用调试模式app_debug > true,设置输出类型index.phpnamespace app\index\controller;class Index{public function index(){$data [name > steven, age > 24];return json([code > 0, msg > 操作成功, data…

命令行进入指定目录_VIM学习笔记 操作目录(Manipulate Directory)

在目录间移动使用以下命令&#xff0c;可以显示当前所在的目录&#xff1a;:pwd使用以下命令&#xff0c;在Linux下可以进入HOME目录&#xff0c;而在Windows下则显示当前所在目录&#xff1a;:cd使用以下命令&#xff0c;可以进入指定的目录&#xff1a;:cd D:tepm使用以下命令…