该篇文章为本菜鸟大一在校期间最后一次实验室考核总结
包含:
考核的一些技术总结
1.谈谈Java反射机制以及常见的使用反射的场景
标准答:Java反射机制:是Java为了更符合“动态语言”的标准而推出的可以动态获取类中的各种属性,方法,以及方法注解,方法参数等基本属性的特性。反射主要是应用于一些需要通过抽象化的类对象处理解耦合以及局部属性调用等特殊场景.
比如通过循环的方式动态遍历类中的方法或属性,这样即使后续添加了方法或属性也不需要担心会有遗漏,而且对于一些隐秘性高的方法或属性( private修饰),反射也可以很好地利用本身的特性,来直接获取。通过反射的特性对ArrayList等具备泛型的类跳过其泛型检查。植入代码的时候可以通过对私有部分的强行获取来改变一些程序的运行方式或者其他运行流程。在工厂类中通过反射的特性使对象的获取更局部化,提高程序的运行效率。
个人理解:为了在运行过程中,随时知道你想知道的任意一个类的所有属性,方法。使得能够动态获取类的信息,创建对象和方法等等。打个比方:如果老师上课要点人数,原本是叫名字,但是每个人名字,性别等等不同,现在给每个同学分配学号,可以通过学号从一报到班级最后一个人,此时只需要通过学号可以清晰知道每个同学的姓名,年龄等。但是由于工作量增大,这也导致其缺点:1.性能开销2.安全性低3.可读性差
eg.
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class Person {
private String name;
public int age;
public Person() {
System.out.println("无参构造函数被调用");
}
public Person(String name, int age) {
this.name = name;
this.age = age;
System.out.println("有参构造函数被调用,姓名:" + name + ",年龄:" + age);
}
private void privateMethod() {
System.out.println("私有方法被调用");
}
public void publicMethod(String message) {
System.out.println("公共方法被调用,消息:" + message);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 获取类对象
Class<?> clazz = Person.class;
// 获取所有构造函数
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("构造函数:" + constructor);
}
// 获取指定参数的构造函数并创建对象
Constructor<?> constructor = clazz.getConstructor(String.class, int.class);
Object person = constructor.newInstance("张三", 20);
// 获取所有方法
Method[] methods = clazz.getMethods();
for (Method method : methods) {
System.out.println("方法:" + method);
}
// 获取指定方法并调用
Method publicMethod = clazz.getMethod("publicMethod", String.class);
publicMethod.invoke(person, "这是传入的消息");
// 获取私有方法
Method privateMethod = clazz.getDeclaredMethod("privateMethod");
// 取消访问检查
privateMethod.setAccessible(true);
privateMethod.invoke(person);
// 获取属性
Field field = clazz.getDeclaredField("name");
// 取消访问检查
field.setAccessible(true);
field.set(person, "李四");
Method getNameMethod = clazz.getMethod("getName");
System.out.println("修改后的姓名:" + getNameMethod.invoke(person));
}
}
在上述示例中:
1. 通过 Class<?> clazz = Person.class; 获取了 Person 类的对象。
2. 利用反射获取了类的构造函数、方法和属性,并进行了相应的操作,如创建对象、调用方法、修改属性值等。
3. 对于私有成员,需要通过 setAccessible(true) 来取消访问限制。
测试结果
2.谈一谈对单元测试断言机制的理解
标准答:单元测试断言机制是编写单元测试时使用的一种关键工具,它允许开发人员定义期望的行为,并在测试运行时验证代码是否符合这些期望。断言通常用于检查方法的返回值、状态或行为是否符合预期,从而帮助开发人员捕获和修复代码中的错误。1.断言语句:断言语句是测试用例中用于验证代码行为的关键部分。它们通常是一些条件表达式,用于比较实际结果与期望结果之间的差异。断言语句应该清晰明了,以便在测试失败时快速定位问题。2 .期望值和实际值:在断言中,通常会将期望值与实际值进行比较。期望值是开发人员预先定义的预期结果,而实际值则是代码执行后的真实结果。通过比较这两个值,可以确定代码是否按照预期工作。3 .断言库:单元测试框架通常提供了丰富的断言库,包含各种用于比较值的方法,例如相等性断言、不等性断言、空值检查、异常断言等。开发人员可以根据需要选择合适的断言方法来编写测试用例。4.自定义断言:在某些情况下,开发人员可能需要编写自定义的断言来满足特定的测试需求。例如,针对复杂数据结构或特定业务逻辑的测试,可能需要编写定制化的断言方法来验证代码行为。
个人理解:一开始我看到是有点懵的,但是后面看了解析并查阅了方法名Assert之后,发现是我当时觉着太简单了,一眼扫过的内容。。。然后又仔细了解了一遍,发现是我以前理解错了,将该机制自以为和异常沾边。现在觉着,它更类似于黑盒实验。在开发测试阶段,类似黑盒来看写的东西有没有得到预期结果。当然,这并不完全准确,但是该机制确是注重强调测试结果。而异常抛出则是有时候我们测试一个方法,知道在某些情况下它应该抛出异常,如果真的抛出了,反而说明这部分功能可能是对的。
eg.
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class Assert {
//这里Assert为我取的类名字
@Nested
class CalculatorTest {
@Test
public void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
// 这里使用断言来验证加法的结果是否为 5
assertEquals(5, result);
}
@Test
public void testDivideByZeroThrowsException() {
Calculator calculator = new Calculator();
// 这里预期当除数为 0 时会抛出 ArithmeticException 异常
assertThrows(ArithmeticException.class, () -> calculator.divide(5, 0));
}
}
class Calculator {
public int add(int a, int b) {
return a + b;
}
public int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("除数不能为 0");
}
return a / b;
}
}
}
//别忘了在pom.xml中加JUnit依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
</dependencies>
testAddition 方法中,使用 assertEquals 断言来验证 Calculator 类的 add 方法计算 2 + 3 的结果是否为 5 。
testDivideByZeroThrowsException 方法中,使用 assertThrows 断言来验证当调用 Calculator 类的 divide 方法且除数为 0 时,是否会抛出 ArithmeticException 异常。
3.什么是SQL注入?解决办法是?
标准答:SQL注入是一种常见的网络安全攻击方式,它利用了应用程序对用户输入数据的不正确处理,导致恶意用户能够在不经意间将恶意SQL代码插入到应用程序的SQL查询中,从而执行未经授权的数据库操作。
1 .使用参数化查询( Prepared Statements )或存储过程:参数化查询能够在执行SQL语句之前将用户输入的数据作为参数传递给数据库,而不是将其直接拼接到SQL语句中。这样可以有效防止SQL注入攻击,因为数据库会将用户输入视为数据而不是可执行的SQL代码。2 .输入验证和过滤:对用户输入的数据进行验证和过滤,确保只允许预期的数据格式和内容通过。可以使用白名单过滤或者正则表达式来限制输入数据的类型和格式。
个人理解:由于SQL语句所必须的格式规范,导致如果不做保护,有可能改变原有规则。而规则改变则可能导致数据库受到攻击。例如,一个简单的登录验证查询可能是: SELECT * FROM users WHERE username = '{password}' 。如果攻击者输入用户名 ' OR 1=1 -- ,密码随意输入,那么最终执行的 SQL 语句就可能变成: SELECT * FROM users WHERE username = '' OR 1=1 --' AND password = 'any_password' 。(这里的 OR 1=1 使得条件恒成立, -- 是注释符号,用于忽略后面的内容。)这样攻击者就可能绕过正常的登录验证,获取到未经授权的访问权限。
该问题答案通俗易懂,也无需我的个人理解,直接上答案~
1. 参数化查询:使用参数化查询(如PreparedStatement 在 Java 中,或存储过程)而不是将用户输入直接拼接到 SQL 语句中。参数化查询会将输入值作为参数处理,而不是作为 SQL 语句的一部分,从而避免了恶意输入改变语句逻辑。
2. 输入验证:对用户输入的数据进行严格的验证和清理。例如,只允许特定的字符集、长度和数据类型。
3. 最小权限原则:为数据库连接使用的用户账号授予最小必要的权限,避免使用具有过高权限的账号进行数据库操作。
4. 避免动态 SQL 构建:尽量减少在运行时动态构建复杂的 SQL 语句。
5. 转义特殊字符:对用户输入中的特殊字符(如单引号、双引号、分号等)进行转义处理,确保它们不会破坏 SQL 语句的结构。
6. 使用 ORM 框架:许多对象关系映射(ORM)框架(如 Hibernate、MyBatis 等)在内部处理了防止 SQL 注入的问题,可以考虑使用这些框架来简化数据库操作。
7. 定期安全审计:定期检查和审核应用程序中的数据库操作代码,以发现潜在的安全漏洞。
8. 员工培训:对开发人员进行安全培训,提高他们对 SQL 注入等安全威胁的认识和防范意识。