最近公司在讨论防御式编程,这里介绍一些相关信息。

防御式编程(Defensive Programming)
是一种编程方*论法**,旨在编写健壮的、高质量的软件。防御式编程的主要思想是,在程序中加入一系列的断言和异常处理机制,以防止不可预见的错误和异常情况发生,从而使程序具有更好的容错性、可靠性和安全性。
防御式编程的主要特点包括:
- 对输入进行校验和验证,确保输入的数据合法有效;
- 对数据类型进行检查和转换,防止因类型错误而引发的异常;
- 对函数和方法的参数进行检查,避免因参数错误而导致的错误;
- 对函数和方法的返回值进行检查和处理,避免因返回值错误而引发的异常;
- 对代码中可能产生错误的地方加入异常处理机制,以捕获并处理异常;
- 加入日志和调试信息,方便追踪和定位错误;
- 对代码进行单元测试和集成测试,保证代码质量和可靠性。
防御式编程举例:
- 对输入进行校验和验证:比如一个登录接口需要验证用户的用户名和密码,可以对用户名和密码进行长度、格式、特殊字符等方面的校验和验证,以确保输入的数据合法有效,从而避免因用户输入错误而导致的程序崩溃或安全问题。
- 对数据类型进行检查和转换:比如在进行类型转换时,可以对数据类型进行检查和转换,避免因类型错误而引发的异常。例如,在将字符串转换为整数时,可以先检查字符串是否为数字,再进行转换,从而避免因字符串不是数字而引发的异常。
- 对函数和方法的参数进行检查:比如在编写一个函数或方法时,可以对其参数进行检查,避免因参数错误而导致的错误。例如,在编写一个计算两个整数之和的函数时,可以先检查输入的参数是否为整数,如果不是则抛出异常,从而避免因输入参数错误而引发的异常。
- 对函数和方法的返回值进行检查和处理:比如在调用一个函数或方法时,可以对其返回值进行检查和处理,避免因返回值错误而引发的异常。例如,在调用一个读取文件的函数时,可以检查函数返回值是否为空或是否为预期的文件内容,如果不是则抛出异常或处理错误。
- 对代码中可能产生错误的地方加入异常处理机制:比如在进行文件读写操作时,可以加入异常处理机制,以捕获并处理异常。例如,在进行文件读取时,如果文件不存在或读取失败,可以使用try-catch块捕获异常并进行处理,从而避免因文件读取错误而导致的程序崩溃。
- 加入日志和调试信息:比如在编写代码时,可以加入日志和调试信息,方便追踪和定位错误。例如,在进行网络请求时,可以记录网络请求的相关信息,包括请求的URL、请求参数、响应结果等,方便在程序出现异常时进行排查和修复。
- 对代码进行单元测试和集成测试:比如在编写代码时,可以进行单元测试和集成测试,保证代码质量和可靠性。例如,在编写一个函数时,可以编写相应的单元测试用例进行测试,以确保函数的正确性和健壮性。

进一步说明
- 非空判断:在Java中,我们可以使用if语句对变量进行非空判断。例如,我们可以在代码中加入以下代码块来确保字符串不为空:
if (str != null && !str.isEmpty()) {
// do something
}
这里,我们先判断字符串是否为null,再判断字符串是否为空,这样可以避免在使用字符串的方法时抛出NullPointerException异常。
- 异常处理:在Java中,我们可以使用try-catch语句来捕获异常,并对异常进行处理。例如,我们可以在代码中加入以下代码块来处理可能出现的异常:
try {
// do something
} catch (Exception e) {
// handle exception
}
这里,我们使用try-catch语句来包含可能出现异常的代码块,并在catch块中处理异常。
- 参数检查:在Java中,我们可以使用assert语句对方法的参数进行检查。例如,我们可以在代码中加入以下代码块来检查参数是否符合要求:
public void doSomething(int param) {
assert param > 0 : "param should be greater than 0";
// do something
}
这里,我们使用assert语句来确保方法的参数大于0,如果参数不符合要求,则会抛出AssertionError异常。
- 输入验证:在Java中,我们可以使用正则表达式或第三方库对输入进行验证。例如,我们可以使用Java自带的正则表达式库来验证邮箱地址:
public boolean isEmailValid(String email) {
String regex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+#34;;
return email.matches(regex);
}
这里,我们使用正则表达式来验证邮箱地址是否符合格式要求。
- 防止SQL注入:在Java中,我们可以使用PreparedStatement和参数化查询来防止SQL注入攻击。例如,我们可以使用PreparedStatement来执行查询操作:
String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, username);
stmt.setString(2, password);
ResultSet rs = stmt*ex.e**cuteQuery();
这里,我们使用PreparedStatement来执行查询操作,并使用占位符(?)来代替查询参数,这样可以避免SQL注入攻击。
- 防止XSS攻击:在Java中,我们可以使用特殊字符转义和第三方库来防止XSS攻击。例如,我们可以使用Jsoup库来转义HTML标签:
String unsafeHtml = "<div>This is unsafe HTML with <script>alert('XSS');</script></div>";
String safeHtml = Jsoup.clean(unsafeHtml, Whitelist.basic());
这里,我们使用Jsoup库的clean方法来转义HTML标签,这样可以避免XSS攻击。

以 Java 为例,结合上述七点防御式编程原则,举例如何实现防御式编程:
- 检查参数的有效性
在 Java 中,可以使用断言(assert)机制来检查参数的有效性,例如:
public int divide(int dividend, int divisor) {
assert divisor != 0 : "Divisor cannot be zero";
return dividend / divisor;
}
如果 divisor 为零,则该方法会抛出 AssertionError 异常。
- 避免使用魔数
在 Java 中,可以使用常量来避免使用魔数,例如:
public static final int MAX_LENGTH = 100;
在编程中使用常量 MAX_LENGTH,而不是直接使用数字 100。
- 防止空指针异常
在 Java 中,可以使用 Optional 类来避免空指针异常,例如:
Optional<String> optional = Optional.ofNullable(str);
if (optional.isPresent()) {
String value = optional.get();
// do something with value
} else {
// handle null case
}
这样就可以避免直接对 str 进行空指针判断。
- 避免重复代码
在 Java 中,可以使用继承、接口、组合等方式来避免重复代码,例如:
public interface Shape {
double getArea();
}
public class Rectangle implements Shape {
private double width;
private double height;
// constructor, getters and setters
@Override
public double getArea() {
return width * height;
}
}
public class Circle implements Shape {
private double radius;
// constructor, getters and setters
@Override
public double getArea() {
return Math.PI * radius * radius;
}
}
- 处理异常情况
在 Java 中,可以使用 try-catch-finally 语句块来处理异常情况,例如:
try {
// some code that may throw exception
} catch (SomeException e) {
// handle exception
} finally {
// code that will always be executed, regardless of whether an exception is thrown or not
}
- 使用日志记录程序运行状态
在 Java 中,可以使用日志框架(如 log4j、slf4j)来记录程序运行状态,例如:
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
public void doSomething() {
logger.debug("Entering doSomething method");
// some code
logger.debug("Leaving doSomething method");
}
这样可以记录程序的运行状态,有助于调试和排查问题。
- 使用正则表达式来校验输入
在 Java 中,可以使用正则表达式来校验输入是否符合要求,例如:
Pattern pattern = Pattern.compile("^[a-zA-Z0-9]+#34;);
Matcher matcher = pattern.matcher(input);
if (!matcher.matches()) {
// input does not match pattern
}
这样就可以确保输入只包含字母和数字,而不包含其他字符。

springboot接口开发,怎么应用防御式编程
在 Spring Boot 接口开发中应用防御式编程,可以采取以下措施:
- 验证请求参数:在控制层对请求参数进行验证,避免输入不合法的参数或攻击向量,可以使用 Spring 提供的校验注解或者第三方校验库,如 Hibernate Validator。
- 处理异常:在编写接口时,需要考虑到可能会出现的异常情况,比如请求参数不合法、业务逻辑错误、数据库操作异常等等,通过捕获异常并进行处理,可以避免因为一个接口的异常导致整个系统崩溃。
- 对数据进行安全处理:避免 SQL 注入和 XSS 攻击等,可以使用防注入框架和过滤器。
- 对系统进行安全加固:对于 Spring Boot 应用,可以使用 Spring Security 进行安全认证和授权,设置访问权限,避免未授权的访问和操作。
- 应用配置的安全:避免敏感信息泄漏,比如数据库连接信息、密码等等,可以采用加密方式存储或者使用安全的配置管理工具。
- 记录日志:记录接口的请求和响应日志,便于排查问题和分析攻击行为。
- 定期进行安全审计:定期对系统进行安全审计,发现漏洞和问题及时进行修复,提高系统的安全性和可靠性。
在 Spring Boot 接口开发中应用防御式编程:
- 参数校验
在接口中,应该对传入参数进行校验,以防止恶意用户输入无效数据或输入可能导致错误的数据。可以使用 Spring Validation 框架来实现这个功能。例如,使用注解 @Valid 标注参数,然后在方法中处理验证异常:
@PostMapping("/user")
public ResponseEntity createUser(@Valid @RequestBody User user) {
// 处理用户创建请求
}
- 异常处理
应该在接口中处理所有可能出现的异常,并向用户提供有用的错误信息,以避免向攻击者透露系统敏感信息。可以使用 Spring 的异常处理机制来处理异常。例如,可以创建一个全局异常处理器,处理所有未处理的异常并返回适当的错误响应:
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ResponseEntity handleException(Exception ex) {
// 处理异常,并返回错误响应
}
}
- 安全措施
在接口中应该采取安全措施,以防止恶意用户进行攻击,例如 SQL 注入、跨站脚本攻击等。可以使用 Spring Security 框架来实现这个功能。例如,可以配置基本身份验证和授权策略,以确保只有授权用户才能访问受保护的资源:
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/user/**").hasRole("USER")
.anyRequest().authenticated()
.and()
.httpBasic();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.withUser("admin").password("password").roles("ADMIN")
.and()
.withUser("user").password("password").roles("USER");
}
}
- 日志记录
在接口中应该记录所有重要事件和操作,以便在出现问题时进行跟踪和调试。可以使用 Spring 的 AOP(面向切面编程)机制和日志框架,如 Log4j、Logback 等来实现这个功能。例如,可以创建一个切面来记录所有方法调用和异常:
@Aspect
@Component
public class LoggingAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(LoggingAspect.class);
@Around("execution(* com.example.*.*(..))")
public Object logMethodCall(ProceedingJoinPoint joinPoint) throws Throwable {
LOGGER.debug("Calling method: {}", joinPoint.getSignature().toShortString());
Object result = joinPoint.proceed();
LOGGER.debug("Method returned: {}", result);
return result;
}
@AfterThrowing(pointcut = "execution(* com.example.*.*(..))", throwing = "ex")
public void logException(Exception ex) {
LOGGER.error
防御式编程,在springboot中,是在control层,还是service层或者其他层进行防御呢?
防御式编程可以在各个层面进行,包括控制层、服务层、持久层等。通常情况下,控制层是应用程序的入口,因此应该在控制层实现防御式编程来保证对输入参数进行有效性验证。同时,在服务层或持久层也可以对参数进行验证和过滤,以确保数据的安全性和有效性。
在Spring Boot中,通常建议在控制层和服务层都实现防御式编程。控制层应该对接口传入的参数进行校验和过滤,以避免恶意数据的输入和注入攻击。服务层则应该对从控制层接收到的参数进行进一步的验证,并对数据库操作进行安全保护,例如防止SQL注入攻击。
同时,在Spring Boot中,还可以使用各种第三方库和框架来实现防御式编程。例如,可以使用Hibernate Validator对请求参数进行校验,使用Spring Security实现身份验证和授权等。